forked from netlify/cli
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdetect-server.js
More file actions
271 lines (241 loc) · 9.93 KB
/
Copy pathdetect-server.js
File metadata and controls
271 lines (241 loc) · 9.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
const fs = require('fs')
const path = require('path')
const process = require('process')
const chalk = require('chalk')
const fuzzy = require('fuzzy')
const getPort = require('get-port')
const inquirer = require('inquirer')
const inquirerAutocompletePrompt = require('inquirer-autocomplete-prompt')
const { NETLIFYDEVLOG, NETLIFYDEVWARN } = require('./logo')
const serverSettings = async (devConfig, flags, projectDir, log) => {
let settings = {}
const detectorsFiles = fs
.readdirSync(path.join(__dirname, '..', 'detectors'))
// only accept .js detector files
.filter((filename) => filename.endsWith('.js'))
if (typeof devConfig.framework !== 'string') throw new Error('Invalid "framework" option provided in config')
if (flags.dir) {
settings = await getStaticServerSettings(settings, flags, projectDir, log)
;['command', 'targetPort'].forEach((property) => {
if (flags[property]) {
throw new Error(
`"${property}" option cannot be used in conjunction with "dir" flag which is used to run a static server`,
)
}
})
} else if (devConfig.framework === '#auto' && !(devConfig.command && devConfig.targetPort)) {
const settingsArr = []
const detectors = detectorsFiles.map((det) => {
try {
return loadDetector(det)
} catch (error) {
console.error(error)
return null
}
})
for (const detector of detectors) {
const detectorResult = detector(projectDir)
if (detectorResult) settingsArr.push(detectorResult)
}
if (settingsArr.length === 1) {
const [firstSettings] = settingsArr
settings = firstSettings
settings.args = chooseDefaultArgs(settings.possibleArgsArrs)
} else if (settingsArr.length > 1) {
/** multiple matching detectors, make the user choose */
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
const scriptInquirerOptions = formatSettingsArrForInquirer(settingsArr)
const { chosenSetting } = await inquirer.prompt({
name: 'chosenSetting',
message: `Multiple possible start commands found`,
type: 'autocomplete',
source(_, input) {
if (!input || input === '') {
return scriptInquirerOptions
}
// only show filtered results
return filterSettings(scriptInquirerOptions, input)
},
})
// finally! we have a selected option
settings = chosenSetting
log(
`Add \`framework = "${chosenSetting.framework}"\` to [dev] section of your netlify.toml to avoid this selection prompt next time`,
)
}
} else if (devConfig.framework === '#custom' || (devConfig.command && devConfig.targetPort)) {
settings.framework = '#custom'
if (
devConfig.framework &&
!['command', 'targetPort'].every((property) => Object.prototype.hasOwnProperty.call(devConfig, property))
) {
throw new Error('"command" and "targetPort" properties are required when "framework" is set to "#custom"')
}
if (devConfig.framework !== '#custom' && devConfig.command && devConfig.targetPort) {
throw new Error(
'"framework" option must be set to "#custom" when specifying both "command" and "targetPort" options',
)
}
} else if (devConfig.framework === '#static') {
// Do nothing
} else {
const detectorName = detectorsFiles.find((dt) => dt === `${devConfig.framework}.js`)
if (!detectorName)
throw new Error(
'Unsupported value provided for "framework" option in config. Please use "#custom"' +
` if you're using a framework not intrinsically supported by Netlify Dev. E.g. with "command" and "targetPort" options.` +
` Or use one of following values: ${detectorsFiles
.map((detectorFile) => `"${path.parse(detectorFile).name}"`)
.join(', ')}`,
)
const detector = loadDetector(detectorName)
const detectorResult = detector(projectDir)
if (!detectorResult)
throw new Error(
`Specified "framework" detector "${devConfig.framework}" did not pass requirements for your project`,
)
settings = detectorResult
settings.args = chooseDefaultArgs(detectorResult.possibleArgsArrs)
}
if (settings.command === 'npm' && !['start', 'run'].includes(settings.args[0])) {
settings.args.unshift('run')
}
if (!settings.noCmd && devConfig.command) {
console.log(
`${NETLIFYDEVLOG} Overriding ${chalk.yellow('command')} with setting derived from netlify.toml [dev] block: ${
devConfig.command
}`,
)
const [devConfigCommand, ...devConfigArgs] = devConfig.command.split(/\s+/)
settings.command = devConfigCommand
settings.args = devConfigArgs
}
settings.dist = flags.dir || devConfig.publish || settings.dist
if (devConfig.targetPort) {
if (devConfig.targetPort && typeof devConfig.targetPort !== 'number') {
throw new Error('Invalid "targetPort" option specified. The value of "targetPort" option must be an integer')
}
if (devConfig.targetPort === devConfig.port) {
throw new Error(
'"port" and "targetPort" options cannot have same values. Please consult the documentation for more details: https://cli.netlify.com/netlify-dev#netlifytoml-dev-block',
)
}
if (!settings.command)
throw new Error(
'No "command" specified or detected. The "command" option is required to use "targetPort" option.',
)
if (flags.dir)
throw new Error(
'"targetPort" option cannot be used in conjunction with "dir" flag which is used to run a static server.',
)
settings.frameworkPort = devConfig.targetPort
}
if (devConfig.port && devConfig.port === settings.frameworkPort) {
throw new Error(
'The "port" option you specified conflicts with the port of your application. Please use a different value for "port"',
)
}
if (!settings.command && !settings.framework && !settings.noCmd) {
settings = await getStaticServerSettings(settings, flags, projectDir, log)
}
if (!settings.frameworkPort) throw new Error('No "targetPort" option specified or detected.')
if (devConfig.port && typeof devConfig.port !== 'number') {
throw new Error('Invalid "port" option specified. The value of "port" option must be an integer')
}
if (devConfig.port && devConfig.port === settings.frameworkPort) {
throw new Error(
'The "port" option you specified conflicts with the port of your application. Please use a different value for "port"',
)
}
const triedPort = devConfig.port || DEFAULT_PORT
settings.port = await getPort({ port: triedPort })
if (triedPort !== settings.port && devConfig.port) {
throw new Error(`Could not acquire required "port": ${triedPort}`)
}
settings.jwtSecret = devConfig.jwtSecret || 'secret'
settings.jwtRolePath = devConfig.jwtRolePath || 'app_metadata.authorization.roles'
settings.functions = devConfig.functions || settings.functions
if (settings.functions) {
settings.functionsPort = await getPort({ port: settings.functionsPort || 0 })
}
return settings
}
const DEFAULT_PORT = 8888
const getStaticServerSettings = async function (settings, flags, projectDir, log) {
let { dist } = settings
if (flags.dir) {
log(`${NETLIFYDEVWARN} Using simple static server because --dir flag was specified`)
dist = flags.dir
} else {
log(`${NETLIFYDEVWARN} No app server detected and no "command" specified`)
}
if (!dist) {
log(`${NETLIFYDEVLOG} Using current working directory`)
log(`${NETLIFYDEVWARN} Unable to determine public folder to serve files from`)
log(`${NETLIFYDEVWARN} Setup a netlify.toml file with a [dev] section to specify your dev server settings.`)
log(`${NETLIFYDEVWARN} See docs at: https://cli.netlify.com/netlify-dev#project-detection`)
dist = process.cwd()
}
log(`${NETLIFYDEVWARN} Running static server from "${path.relative(path.dirname(projectDir), dist)}"`)
return {
noCmd: true,
frameworkPort: await getPort({ port: flags.staticServerPort || DEFAULT_STATIC_PORT }),
dist,
}
}
const DEFAULT_STATIC_PORT = 3999
const loadDetector = function (detectorName) {
try {
// eslint-disable-next-line node/global-require, import/no-dynamic-require
return require(path.join(__dirname, '..', 'detectors', detectorName))
} catch (error) {
throw new Error(
`Failed to load detector: ${chalk.yellow(
detectorName,
)}, this is likely a bug in the detector, please file an issue in netlify-cli\n ${error}`,
)
}
}
const chooseDefaultArgs = function (possibleArgsArrs) {
// vast majority of projects will only have one matching detector
// just pick the first one
const [args] = possibleArgsArrs
if (!args) {
const { scripts } = JSON.parse(fs.readFileSync('package.json', { encoding: 'utf8' }))
const err = new Error(
'Empty args assigned, this is an internal Netlify Dev bug, please report your settings and scripts so we can improve',
)
err.scripts = scripts
err.possibleArgsArrs = possibleArgsArrs
throw err
}
return args
}
/** utilities for the inquirer section above */
const filterSettings = function (scriptInquirerOptions, input) {
const filteredSettings = fuzzy.filter(
input,
scriptInquirerOptions.map((scriptInquirerOption) => scriptInquirerOption.name),
)
const filteredSettingNames = new Set(
filteredSettings.map((filteredSetting) => (input ? filteredSetting.string : filteredSetting)),
)
return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name))
}
/** utiltities for the inquirer section above */
const formatSettingsArrForInquirer = function (settingsArr) {
return [].concat(
...settingsArr.map((setting) =>
setting.possibleArgsArrs.map((args) => ({
name: `[${chalk.yellow(setting.framework)}] ${setting.command} ${args.join(' ')}`,
value: { ...setting, args },
short: `${setting.framework}-${args.join(' ')}`,
})),
),
)
}
module.exports = {
serverSettings,
loadDetector,
chooseDefaultArgs,
}