UNPKG

5.15 kBJavaScriptView Raw
1const path = require('path')
2const chalk = require('chalk')
3const { NETLIFYDEVLOG } = require('./logo')
4const inquirer = require('inquirer')
5const fuzzy = require('fuzzy')
6const fs = require('fs')
7
8module.exports.serverSettings = async devConfig => {
9 const detectors = fs
10 .readdirSync(path.join(__dirname, '..', 'detectors'))
11 .filter(x => x.endsWith('.js')) // only accept .js detector files
12 .map(det => {
13 try {
14 return require(path.join(__dirname, '..', `detectors/${det}`))
15 } catch (err) {
16 console.error(
17 `failed to load detector: ${chalk.yellow(
18 det
19 )}, this is likely a bug in the detector, please file an issue in netlify-dev-plugin`,
20 err
21 )
22 return null
23 }
24 })
25 .filter(Boolean)
26 let settingsArr = []
27 let settings = null
28 for (const i in detectors) {
29 const detectorResult = detectors[i]()
30 if (detectorResult) settingsArr.push(detectorResult)
31 }
32 if (settingsArr.length === 1) {
33 // vast majority of projects will only have one matching detector
34 settings = settingsArr[0]
35 settings.args = settings.possibleArgsArrs[0] // just pick the first one
36 if (!settings.args) {
37 const { scripts } = JSON.parse(fs.readFileSync('package.json', { encoding: 'utf8' }))
38 // eslint-disable-next-line no-console
39 console.error(
40 'empty args assigned, this is an internal Netlify Dev bug, please report your settings and scripts so we can improve',
41 { scripts, settings }
42 )
43 // eslint-disable-next-line no-process-exit
44 process.exit(1)
45 }
46 } else if (settingsArr.length > 1) {
47 /** multiple matching detectors, make the user choose */
48 // lazy loading on purpose
49 inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
50 const scriptInquirerOptions = formatSettingsArrForInquirer(settingsArr)
51 const { chosenSetting } = await inquirer.prompt({
52 name: 'chosenSetting',
53 message: `Multiple possible start commands found`,
54 type: 'autocomplete',
55 source: async function(_, input) {
56 if (!input || input === '') {
57 return scriptInquirerOptions
58 }
59 // only show filtered results
60 return filterSettings(scriptInquirerOptions, input)
61 }
62 })
63 settings = chosenSetting // finally! we have a selected option
64 // TODO: offer to save this setting to netlify.toml so you dont keep doing this
65 }
66
67 /** everything below assumes we have settled on one detector */
68 const tellUser = settingsField => dV =>
69 // eslint-disable-next-line no-console
70 console.log(
71 `${NETLIFYDEVLOG} Overriding ${chalk.yellow(settingsField)} with setting derived from netlify.toml [dev] block: `,
72 dV
73 )
74
75 if (devConfig) {
76 settings = settings || {}
77 if (devConfig.command) {
78 settings.command = assignLoudly(devConfig.command.split(/\s/)[0], settings.command || null, tellUser('command')) // if settings.command is empty, its bc no settings matched
79 let devConfigArgs = devConfig.command.split(/\s/).slice(1)
80 if (devConfigArgs[0] === 'run') devConfigArgs = devConfigArgs.slice(1)
81 settings.args = assignLoudly(devConfigArgs, settings.command || null, tellUser('command')) // if settings.command is empty, its bc no settings matched
82 }
83 if (devConfig.port) settings.port = devConfig.port
84 if (devConfig.targetPort) {
85 settings.proxyPort = devConfig.targetPort
86 settings.urlRegexp = devConfig.urlRegexp || new RegExp(`(http://)([^:]+:)${devConfig.targetPort}(/)?`, 'g')
87 }
88 if (devConfig.functionsPort) settings.functionsPort = devConfig.functionsPort
89 settings.dist = devConfig.publish || settings.dist // dont loudassign if they dont need it
90
91 if (devConfig.jwtRolePath) settings.jwtRolePath = devConfig.jwtRolePath
92 }
93 return settings
94}
95
96/** utilities for the inquirer section above */
97function filterSettings(scriptInquirerOptions, input) {
98 const filteredSettings = fuzzy.filter(input, scriptInquirerOptions.map(x => x.name))
99 const filteredSettingNames = filteredSettings.map(x => (input ? x.string : x))
100 return scriptInquirerOptions.filter(t => filteredSettingNames.includes(t.name))
101}
102
103/** utiltities for the inquirer section above */
104function formatSettingsArrForInquirer(settingsArr) {
105 let ans = []
106 settingsArr.forEach(setting => {
107 setting.possibleArgsArrs.forEach(args => {
108 ans.push({
109 name: `[${chalk.yellow(setting.type)}] ${setting.command} ${args.join(' ')}`,
110 value: { ...setting, args },
111 short: setting.type + '-' + args.join(' ')
112 })
113 })
114 })
115 return ans
116}
117// if first arg is undefined, use default, but tell user about it in case it is unintentional
118function assignLoudly(
119 optionalValue,
120 defaultValue,
121 // eslint-disable-next-line no-console
122 tellUser = dV => console.log(`No value specified, using fallback of `, dV)
123) {
124 if (defaultValue === undefined) throw new Error('must have a defaultValue')
125 if (defaultValue !== optionalValue && optionalValue === undefined) {
126 tellUser(defaultValue)
127 return defaultValue
128 }
129 return optionalValue
130}