netlify-cli 15.10.0-rc.1 → 15.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/bin/run.mjs +5 -6
  2. package/npm-shrinkwrap.json +3 -6
  3. package/package.json +2 -1
  4. package/src/commands/base-command.mjs +116 -295
  5. package/src/commands/build/build.mjs +1 -9
  6. package/src/commands/deploy/deploy.mjs +9 -23
  7. package/src/commands/dev/dev.mjs +17 -22
  8. package/src/commands/functions/functions-create.mjs +89 -118
  9. package/src/commands/functions/functions-invoke.mjs +7 -10
  10. package/src/commands/functions/functions-list.mjs +2 -2
  11. package/src/commands/init/init.mjs +1 -1
  12. package/src/commands/link/link.mjs +5 -5
  13. package/src/commands/serve/serve.mjs +6 -10
  14. package/src/commands/sites/sites-create-template.mjs +1 -1
  15. package/src/commands/sites/sites-create.mjs +1 -1
  16. package/src/lib/edge-functions/internal.mjs +3 -5
  17. package/src/lib/edge-functions/proxy.mjs +3 -27
  18. package/src/lib/functions/netlify-function.mjs +26 -1
  19. package/src/lib/functions/registry.mjs +14 -26
  20. package/src/lib/functions/runtimes/js/worker.mjs +1 -1
  21. package/src/lib/spinner.mjs +1 -1
  22. package/src/recipes/vscode/index.mjs +6 -24
  23. package/src/utils/command-helpers.mjs +7 -16
  24. package/src/utils/detect-server-settings.mjs +245 -133
  25. package/src/utils/framework-server.mjs +5 -6
  26. package/src/utils/functions/functions.mjs +5 -8
  27. package/src/utils/get-repo-data.mjs +6 -5
  28. package/src/utils/init/config-github.mjs +2 -2
  29. package/src/utils/init/config-manual.mjs +7 -24
  30. package/src/utils/init/frameworks.mjs +23 -0
  31. package/src/utils/init/utils.mjs +63 -62
  32. package/src/utils/proxy-server.mjs +4 -7
  33. package/src/utils/proxy.mjs +3 -4
  34. package/src/utils/read-repo-url.mjs +0 -4
  35. package/src/utils/run-build.mjs +32 -58
  36. package/src/utils/shell.mjs +7 -24
  37. package/src/utils/state-config.mjs +1 -5
  38. package/src/utils/static-server.mjs +0 -4
  39. package/src/utils/build-info.mjs +0 -100
@@ -1,28 +1,26 @@
1
1
  // @ts-check
2
2
  import { readFile } from 'fs/promises'
3
3
  import { EOL } from 'os'
4
- import { dirname, relative, resolve } from 'path'
5
-
6
- import { getFramework, getSettings } from '@netlify/build-info'
4
+ import path from 'path'
5
+ import process from 'process'
6
+
7
+ import { Project } from '@netlify/build-info'
8
+ // eslint-disable-next-line import/extensions, n/no-missing-import
9
+ import { NodeFS } from '@netlify/build-info/node'
10
+ import { getFramework, listFrameworks } from '@netlify/framework-info'
11
+ import fuzzy from 'fuzzy'
7
12
  import getPort from 'get-port'
13
+ import inquirer from 'inquirer'
14
+ import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
8
15
 
9
- import { detectFrameworkSettings } from './build-info.mjs'
10
16
  import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs'
11
17
  import { acquirePort } from './dev.mjs'
12
18
  import { getInternalFunctionsDir } from './functions/functions.mjs'
13
- import { getPluginsToAutoInstall } from './init/utils.mjs'
19
+ import { reportError } from './telemetry/report-error.mjs'
14
20
 
15
- /** @param {string} str */
16
21
  const formatProperty = (str) => chalk.magenta(`'${str}'`)
17
- /** @param {string} str */
18
22
  const formatValue = (str) => chalk.green(`'${str}'`)
19
23
 
20
- /**
21
- * @param {object} options
22
- * @param {string} options.keyFile
23
- * @param {string} options.certFile
24
- * @returns {Promise<{ key: string, cert: string, keyFilePath: string, certFilePath: string }>}
25
- */
26
24
  const readHttpsSettings = async (options) => {
27
25
  if (typeof options !== 'object' || !options.keyFile || !options.certFile) {
28
26
  throw new TypeError(
@@ -40,43 +38,43 @@ const readHttpsSettings = async (options) => {
40
38
  throw new TypeError(`Certificate file configuration should be a string`)
41
39
  }
42
40
 
43
- const [key, cert] = await Promise.allSettled([readFile(keyFile, 'utf-8'), readFile(certFile, 'utf-8')])
41
+ const [{ reason: keyError, value: key }, { reason: certError, value: cert }] = await Promise.allSettled([
42
+ readFile(keyFile, 'utf-8'),
43
+ readFile(certFile, 'utf-8'),
44
+ ])
44
45
 
45
- if (key.status === 'rejected') {
46
- throw new Error(`Error reading private key file: ${key.reason}`)
46
+ if (keyError) {
47
+ throw new Error(`Error reading private key file: ${keyError.message}`)
47
48
  }
48
- if (cert.status === 'rejected') {
49
- throw new Error(`Error reading certificate file: ${cert.reason}`)
49
+ if (certError) {
50
+ throw new Error(`Error reading certificate file: ${certError.message}`)
50
51
  }
51
52
 
52
- return { key: key.value, cert: cert.value, keyFilePath: resolve(keyFile), certFilePath: resolve(certFile) }
53
+ return { key, cert, keyFilePath: path.resolve(keyFile), certFilePath: path.resolve(certFile) }
53
54
  }
54
55
 
55
- /**
56
- * Validates a property inside the devConfig to be of a given type
57
- * @param {import('../commands/dev/types.js').DevConfig} devConfig The devConfig
58
- * @param {keyof import('../commands/dev/types.js').DevConfig} property The property to validate
59
- * @param {'string' | 'number'} type The type it should have
60
- */
61
- function validateProperty(devConfig, property, type) {
62
- // eslint-disable-next-line valid-typeof
63
- if (devConfig[property] && typeof devConfig[property] !== type) {
56
+ const validateStringProperty = ({ devConfig, property }) => {
57
+ if (devConfig[property] && typeof devConfig[property] !== 'string') {
64
58
  const formattedProperty = formatProperty(property)
65
59
  throw new TypeError(
66
- `Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be of type ${type}`,
60
+ `Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be a string`,
61
+ )
62
+ }
63
+ }
64
+
65
+ const validateNumberProperty = ({ devConfig, property }) => {
66
+ if (devConfig[property] && typeof devConfig[property] !== 'number') {
67
+ const formattedProperty = formatProperty(property)
68
+ throw new TypeError(
69
+ `Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be an integer`,
67
70
  )
68
71
  }
69
72
  }
70
73
 
71
- /**
72
- *
73
- * @param {object} config
74
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
75
- */
76
74
  const validateFrameworkConfig = ({ devConfig }) => {
77
- validateProperty(devConfig, 'command', 'string')
78
- validateProperty(devConfig, 'port', 'number')
79
- validateProperty(devConfig, 'targetPort', 'number')
75
+ validateStringProperty({ devConfig, property: 'command' })
76
+ validateNumberProperty({ devConfig, property: 'port' })
77
+ validateNumberProperty({ devConfig, property: 'targetPort' })
80
78
 
81
79
  if (devConfig.targetPort && devConfig.targetPort === devConfig.port) {
82
80
  throw new Error(
@@ -87,11 +85,6 @@ const validateFrameworkConfig = ({ devConfig }) => {
87
85
  }
88
86
  }
89
87
 
90
- /**
91
- * @param {object} config
92
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
93
- * @param {number=} config.detectedPort
94
- */
95
88
  const validateConfiguredPort = ({ detectedPort, devConfig }) => {
96
89
  if (devConfig.port && devConfig.port === detectedPort) {
97
90
  const formattedPort = formatProperty('port')
@@ -104,22 +97,13 @@ const validateConfiguredPort = ({ detectedPort, devConfig }) => {
104
97
  const DEFAULT_PORT = 8888
105
98
  const DEFAULT_STATIC_PORT = 3999
106
99
 
107
- /**
108
- * Logs a message that it was unable to determine the dist directory and falls back to the workingDir
109
- * @param {string} workingDir
110
- */
111
- const getDefaultDist = (workingDir) => {
100
+ const getDefaultDist = () => {
112
101
  log(`${NETLIFYDEVWARN} Unable to determine public folder to serve files from. Using current working directory`)
113
102
  log(`${NETLIFYDEVWARN} Setup a netlify.toml file with a [dev] section to specify your dev server settings.`)
114
103
  log(`${NETLIFYDEVWARN} See docs at: https://cli.netlify.com/netlify-dev#project-detection`)
115
- return workingDir
104
+ return process.cwd()
116
105
  }
117
106
 
118
- /**
119
- * @param {object} config
120
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
121
- * @returns {Promise<number>}
122
- */
123
107
  const getStaticServerPort = async ({ devConfig }) => {
124
108
  const port = await acquirePort({
125
109
  configuredPort: devConfig.staticServerPort,
@@ -132,16 +116,16 @@ const getStaticServerPort = async ({ devConfig }) => {
132
116
 
133
117
  /**
134
118
  *
135
- * @param {object} config
136
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
137
- * @param {import('commander').OptionValues} config.flags
138
- * @param {string} config.workingDir
139
- * @returns {Promise<Omit<import('./types.js').BaseServerSettings, 'command'> & {command?: string}>}
119
+ * @param {object} param0
120
+ * @param {import('../commands/dev/types.js').DevConfig} param0.devConfig
121
+ * @param {import('commander').OptionValues} param0.options
122
+ * @param {string} param0.projectDir
123
+ * @returns {Promise<import('./types.js').BaseServerSettings>}
140
124
  */
141
- const handleStaticServer = async ({ devConfig, flags, workingDir }) => {
142
- validateProperty(devConfig, 'staticServerPort', 'number')
125
+ const handleStaticServer = async ({ devConfig, options, projectDir }) => {
126
+ validateNumberProperty({ devConfig, property: 'staticServerPort' })
143
127
 
144
- if (flags.dir) {
128
+ if (options.dir) {
145
129
  log(`${NETLIFYDEVWARN} Using simple static server because ${formatProperty('--dir')} flag was specified`)
146
130
  } else if (devConfig.framework === '#static') {
147
131
  log(
@@ -159,8 +143,8 @@ const handleStaticServer = async ({ devConfig, flags, workingDir }) => {
159
143
  )
160
144
  }
161
145
 
162
- const dist = flags.dir || devConfig.publish || getDefaultDist(workingDir)
163
- log(`${NETLIFYDEVWARN} Running static server from "${relative(dirname(workingDir), dist)}"`)
146
+ const dist = options.dir || devConfig.publish || getDefaultDist()
147
+ log(`${NETLIFYDEVWARN} Running static server from "${path.relative(path.dirname(projectDir), dist)}"`)
164
148
 
165
149
  const frameworkPort = await getStaticServerPort({ devConfig })
166
150
  return {
@@ -173,39 +157,144 @@ const handleStaticServer = async ({ devConfig, flags, workingDir }) => {
173
157
 
174
158
  /**
175
159
  * Retrieves the settings from a framework
176
- * @param {import('@netlify/build-info').Settings} [settings]
177
- * @returns {import('./types.js').BaseServerSettings | undefined}
160
+ * @param {import('./types.js').FrameworkInfo} framework
161
+ * @returns {import('./types.js').BaseServerSettings}
178
162
  */
179
- const getSettingsFromDetectedSettings = (settings) => {
180
- if (!settings) {
181
- return
182
- }
163
+ const getSettingsFromFramework = (framework) => {
164
+ const {
165
+ build: { directory: dist },
166
+ dev: {
167
+ commands: [command],
168
+ pollingStrategies = [],
169
+ port: frameworkPort,
170
+ },
171
+ env = {},
172
+ name: frameworkName,
173
+ plugins,
174
+ staticAssetsDirectory: staticDir,
175
+ } = framework
176
+
183
177
  return {
184
- baseDirectory: settings.baseDirectory,
185
- command: settings.devCommand,
186
- frameworkPort: settings.frameworkPort,
187
- dist: settings.dist,
188
- framework: settings.framework.name,
189
- env: settings.env,
190
- pollingStrategies: settings.pollingStrategies,
191
- plugins: getPluginsToAutoInstall(settings.plugins_from_config_file, settings.plugins_recommended),
178
+ command,
179
+ frameworkPort,
180
+ dist: staticDir || dist,
181
+ framework: frameworkName,
182
+ env,
183
+ pollingStrategies: pollingStrategies.map(({ name }) => name),
184
+ plugins,
192
185
  }
193
186
  }
194
187
 
188
+ const hasDevCommand = (framework) => Array.isArray(framework.dev.commands) && framework.dev.commands.length !== 0
189
+
195
190
  /**
196
- * @param {import('../commands/dev/types.js').DevConfig} devConfig
191
+ * The new build setting detection with build systems and frameworks combined
192
+ * @param {string} projectDir
197
193
  */
198
- const hasCommandAndTargetPort = (devConfig) => devConfig.command && devConfig.targetPort
194
+ const detectSettings = async (projectDir) => {
195
+ const fs = new NodeFS()
196
+ const project = new Project(fs, projectDir)
197
+
198
+ return await project.getBuildSettings()
199
+ }
200
+
201
+ /**
202
+ *
203
+ * @param {import('./types.js').BaseServerSettings | undefined} frameworkSettings
204
+ * @param {import('@netlify/build-info').Settings[]} newSettings
205
+ * @param {Record<string, Record<string, any>>} [metadata]
206
+ */
207
+ const detectChangesInNewSettings = (frameworkSettings, newSettings, metadata) => {
208
+ /** @type {string[]} */
209
+ const message = ['']
210
+ const [setting] = newSettings
211
+
212
+ if (frameworkSettings?.framework !== setting?.framework.name) {
213
+ message.push(
214
+ `- Framework does not match:`,
215
+ ` [old]: ${frameworkSettings?.framework}`,
216
+ ` [new]: ${setting?.framework.name}`,
217
+ '',
218
+ )
219
+ }
220
+
221
+ if (frameworkSettings?.command !== setting?.devCommand) {
222
+ message.push(
223
+ `- command does not match:`,
224
+ ` [old]: ${frameworkSettings?.command}`,
225
+ ` [new]: ${setting?.devCommand}`,
226
+ '',
227
+ )
228
+ }
229
+
230
+ if (frameworkSettings?.dist !== setting?.dist) {
231
+ message.push(`- dist does not match:`, ` [old]: ${frameworkSettings?.dist}`, ` [new]: ${setting?.dist}`, '')
232
+ }
233
+
234
+ if (frameworkSettings?.frameworkPort !== setting?.frameworkPort) {
235
+ message.push(
236
+ `- frameworkPort does not match:`,
237
+ ` [old]: ${frameworkSettings?.frameworkPort}`,
238
+ ` [new]: ${setting?.frameworkPort}`,
239
+ '',
240
+ )
241
+ }
242
+
243
+ if (message.length !== 0) {
244
+ reportError(
245
+ {
246
+ name: 'NewSettingsDetectionMismatch',
247
+ errorMessage: 'New Settings detection does not match old one',
248
+ message: message.join('\n'),
249
+ },
250
+ { severity: 'info', metadata },
251
+ )
252
+ }
253
+ }
254
+
255
+ const detectFrameworkSettings = async ({ projectDir }) => {
256
+ const projectFrameworks = await listFrameworks({ projectDir })
257
+ const frameworks = projectFrameworks.filter((framework) => hasDevCommand(framework))
258
+
259
+ if (frameworks.length === 1) {
260
+ return getSettingsFromFramework(frameworks[0])
261
+ }
262
+
263
+ if (frameworks.length > 1) {
264
+ /** multiple matching detectors, make the user choose */
265
+ inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
266
+ const scriptInquirerOptions = formatSettingsArrForInquirer(frameworks)
267
+ const { chosenFramework } = await inquirer.prompt({
268
+ name: 'chosenFramework',
269
+ message: `Multiple possible start commands found`,
270
+ type: 'autocomplete',
271
+ source(_, input) {
272
+ if (!input || input === '') {
273
+ return scriptInquirerOptions
274
+ }
275
+ // only show filtered results
276
+ return filterSettings(scriptInquirerOptions, input)
277
+ },
278
+ })
279
+ log(
280
+ `Add ${formatProperty(
281
+ `framework = "${chosenFramework.id}"`,
282
+ )} to the [dev] section of your netlify.toml to avoid this selection prompt next time`,
283
+ )
284
+
285
+ return getSettingsFromFramework(chosenFramework)
286
+ }
287
+ }
288
+
289
+ const hasCommandAndTargetPort = ({ devConfig }) => devConfig.command && devConfig.targetPort
199
290
 
200
291
  /**
201
292
  * Creates settings for the custom framework
202
- * @param {object} config
203
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
204
- * @param {string} config.workingDir
293
+ * @param {*} param0
205
294
  * @returns {import('./types.js').BaseServerSettings}
206
295
  */
207
- const handleCustomFramework = ({ devConfig, workingDir }) => {
208
- if (!hasCommandAndTargetPort(devConfig)) {
296
+ const handleCustomFramework = ({ devConfig }) => {
297
+ if (!hasCommandAndTargetPort({ devConfig })) {
209
298
  throw new Error(
210
299
  `${formatProperty('command')} and ${formatProperty('targetPort')} properties are required when ${formatProperty(
211
300
  'framework',
@@ -215,100 +304,101 @@ const handleCustomFramework = ({ devConfig, workingDir }) => {
215
304
  return {
216
305
  command: devConfig.command,
217
306
  frameworkPort: devConfig.targetPort,
218
- dist: devConfig.publish || getDefaultDist(workingDir),
307
+ dist: devConfig.publish || getDefaultDist(),
219
308
  framework: '#custom',
220
309
  pollingStrategies: devConfig.pollingStrategies || [],
221
310
  }
222
311
  }
223
312
 
224
- /**
225
- * Merges the framework settings with the devConfig
226
- * @param {object} config
227
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
228
- * @param {string} config.workingDir
229
- * @param {Partial<import('./types.js').BaseServerSettings>=} config.frameworkSettings
230
- */
231
- const mergeSettings = async ({ devConfig, frameworkSettings = {}, workingDir }) => {
232
- const command = devConfig.command || frameworkSettings.command
233
- const frameworkPort = devConfig.targetPort || frameworkSettings.frameworkPort
313
+ const mergeSettings = async ({ devConfig, frameworkSettings = {} }) => {
314
+ const {
315
+ command: frameworkCommand,
316
+ dist,
317
+ env,
318
+ framework,
319
+ frameworkPort: frameworkDetectedPort,
320
+ pollingStrategies = [],
321
+ } = frameworkSettings
322
+
323
+ const command = devConfig.command || frameworkCommand
324
+ const frameworkPort = devConfig.targetPort || frameworkDetectedPort
234
325
  // if the framework doesn't start a server, we use a static one
235
326
  const useStaticServer = !(command && frameworkPort)
236
327
  return {
237
- baseDirectory: devConfig.base || frameworkSettings.baseDirectory,
238
328
  command,
239
329
  frameworkPort: useStaticServer ? await getStaticServerPort({ devConfig }) : frameworkPort,
240
- dist: devConfig.publish || frameworkSettings.dist || getDefaultDist(workingDir),
241
- framework: frameworkSettings.framework,
242
- env: frameworkSettings.env,
243
- pollingStrategies: frameworkSettings.pollingStrategies || [],
330
+ dist: devConfig.publish || dist || getDefaultDist(),
331
+ framework,
332
+ env,
333
+ pollingStrategies,
244
334
  useStaticServer,
245
335
  }
246
336
  }
247
337
 
248
338
  /**
249
339
  * Handles a forced framework and retrieves the settings for it
250
- * @param {object} config
251
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
252
- * @param {import('@netlify/build-info').Project} config.project
253
- * @param {string} config.workingDir
254
- * @param {string=} config.workspacePackage
340
+ * @param {*} param0
255
341
  * @returns {Promise<import('./types.js').BaseServerSettings>}
256
342
  */
257
- const handleForcedFramework = async ({ devConfig, project, workingDir, workspacePackage }) => {
343
+ const handleForcedFramework = async ({ devConfig, projectDir }) => {
258
344
  // this throws if `devConfig.framework` is not a supported framework
259
- const framework = await getFramework(devConfig.framework, project)
260
- const settings = await getSettings(framework, project, workspacePackage || '')
261
- const frameworkSettings = getSettingsFromDetectedSettings(settings)
262
- return mergeSettings({ devConfig, workingDir, frameworkSettings })
345
+ const frameworkSettings = getSettingsFromFramework(await getFramework(devConfig.framework, { projectDir }))
346
+ return mergeSettings({ devConfig, frameworkSettings })
263
347
  }
264
348
 
265
349
  /**
266
350
  * Get the server settings based on the flags and the devConfig
267
351
  * @param {import('../commands/dev/types.js').DevConfig} devConfig
268
- * @param {import('commander').OptionValues} flags
269
- * @param {import('../commands/base-command.mjs').default} command
352
+ * @param {import('commander').OptionValues} options
353
+ * @param {string} projectDir
354
+ * @param {Record<string, Record<string, any>>} [metadata]
270
355
  * @returns {Promise<import('./types.js').ServerSettings>}
271
356
  */
272
-
273
- const detectServerSettings = async (devConfig, flags, command) => {
274
- validateProperty(devConfig, 'framework', 'string')
357
+ const detectServerSettings = async (devConfig, options, projectDir, metadata) => {
358
+ validateStringProperty({ devConfig, property: 'framework' })
275
359
 
276
360
  /** @type {Partial<import('./types.js').BaseServerSettings>} */
277
361
  let settings = {}
278
362
 
279
- if (flags.dir || devConfig.framework === '#static') {
363
+ if (options.dir || devConfig.framework === '#static') {
280
364
  // serving files statically without a framework server
281
- settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
365
+ settings = await handleStaticServer({ options, devConfig, projectDir })
282
366
  } else if (devConfig.framework === '#auto') {
283
367
  // this is the default CLI behavior
284
368
 
285
- const runDetection = !hasCommandAndTargetPort(devConfig)
286
- const frameworkSettings = runDetection
287
- ? getSettingsFromDetectedSettings(await detectFrameworkSettings(command, 'dev'))
288
- : undefined
369
+ const runDetection = !hasCommandAndTargetPort({ devConfig })
370
+ const frameworkSettings = runDetection ? await detectFrameworkSettings({ projectDir }) : undefined
371
+ const newSettings = runDetection ? await detectSettings(projectDir) : undefined
372
+
373
+ // just report differences in the settings
374
+ detectChangesInNewSettings(frameworkSettings, newSettings || [], {
375
+ ...metadata,
376
+ settings: {
377
+ projectDir,
378
+ devConfig,
379
+ options,
380
+ old: frameworkSettings,
381
+ settings: newSettings,
382
+ },
383
+ })
384
+
289
385
  if (frameworkSettings === undefined && runDetection) {
290
386
  log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
291
- settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
387
+ settings = await handleStaticServer({ options, devConfig, projectDir })
292
388
  } else {
293
389
  validateFrameworkConfig({ devConfig })
294
-
295
- settings = await mergeSettings({ devConfig, frameworkSettings, workingDir: command.workingDir })
390
+ settings = await mergeSettings({ devConfig, frameworkSettings })
296
391
  }
297
392
 
298
- settings.plugins = frameworkSettings?.plugins
393
+ settings.plugins = frameworkSettings && frameworkSettings.plugins
299
394
  } else if (devConfig.framework === '#custom') {
300
395
  validateFrameworkConfig({ devConfig })
301
396
  // when the users wants to configure `command` and `targetPort`
302
- settings = handleCustomFramework({ devConfig, workingDir: command.workingDir })
397
+ settings = handleCustomFramework({ devConfig })
303
398
  } else if (devConfig.framework) {
304
399
  validateFrameworkConfig({ devConfig })
305
400
  // this is when the user explicitly configures a framework, e.g. `framework = "gatsby"`
306
- settings = await handleForcedFramework({
307
- devConfig,
308
- project: command.project,
309
- workingDir: command.workingDir,
310
- workspacePackage: command.workspacePackage,
311
- })
401
+ settings = await handleForcedFramework({ devConfig, projectDir })
312
402
  }
313
403
 
314
404
  validateConfiguredPort({ devConfig, detectedPort: settings.frameworkPort })
@@ -319,7 +409,7 @@ const detectServerSettings = async (devConfig, flags, command) => {
319
409
  errorMessage: `Could not acquire required ${formatProperty('port')}`,
320
410
  })
321
411
  const functionsDir = devConfig.functions || settings.functions
322
- const internalFunctionsDir = await getInternalFunctionsDir({ base: command.workingDir })
412
+ const internalFunctionsDir = await getInternalFunctionsDir({ base: projectDir })
323
413
  const shouldStartFunctionsServer = Boolean(functionsDir || internalFunctionsDir)
324
414
 
325
415
  return {
@@ -333,6 +423,28 @@ const detectServerSettings = async (devConfig, flags, command) => {
333
423
  }
334
424
  }
335
425
 
426
+ const filterSettings = function (scriptInquirerOptions, input) {
427
+ const filterOptions = scriptInquirerOptions.map((scriptInquirerOption) => scriptInquirerOption.name)
428
+ // TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
429
+ // eslint-disable-next-line unicorn/no-array-method-this-argument
430
+ const filteredSettings = fuzzy.filter(input, filterOptions)
431
+ const filteredSettingNames = new Set(
432
+ filteredSettings.map((filteredSetting) => (input ? filteredSetting.string : filteredSetting)),
433
+ )
434
+ return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name))
435
+ }
436
+
437
+ const formatSettingsArrForInquirer = function (frameworks) {
438
+ const formattedArr = frameworks.map((framework) =>
439
+ framework.dev.commands.map((command) => ({
440
+ name: `[${chalk.yellow(framework.name)}] '${command}'`,
441
+ value: { ...framework, commands: [command] },
442
+ short: `${framework.name}-${command}`,
443
+ })),
444
+ )
445
+ return formattedArr.flat()
446
+ }
447
+
336
448
  /**
337
449
  * Returns a copy of the provided config with any plugins provided by the
338
450
  * server settings
@@ -18,14 +18,13 @@ const FRAMEWORK_PORT_TIMEOUT = 6e5
18
18
  /**
19
19
  * Start a static server if the `useStaticServer` is provided or a framework specific server
20
20
  * @param {object} config
21
- * @param {import('./types.js').ServerSettings} config.settings
22
- * @param {string} config.cwd
21
+ * @param {Partial<import('./types').ServerSettings>} config.settings
23
22
  * @returns {Promise<StartReturnObject>}
24
23
  */
25
- export const startFrameworkServer = async function ({ cwd, settings }) {
24
+ export const startFrameworkServer = async function ({ settings }) {
26
25
  if (settings.useStaticServer) {
27
26
  if (settings.command) {
28
- runCommand(settings.command, { env: settings.env, cwd })
27
+ runCommand(settings.command, settings.env)
29
28
  }
30
29
  await startStaticServer({ settings })
31
30
 
@@ -38,7 +37,7 @@ export const startFrameworkServer = async function ({ cwd, settings }) {
38
37
  text: `Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
39
38
  })
40
39
 
41
- runCommand(settings.command, { env: settings.env, spinner, cwd })
40
+ runCommand(settings.command, settings.env, spinner)
42
41
 
43
42
  let port
44
43
  try {
@@ -47,7 +46,7 @@ export const startFrameworkServer = async function ({ cwd, settings }) {
47
46
  host: 'localhost',
48
47
  output: 'silent',
49
48
  timeout: FRAMEWORK_PORT_TIMEOUT,
50
- ...(settings.pollingStrategies?.includes('HTTP') && { protocol: 'http' }),
49
+ ...(settings.pollingStrategies.includes('HTTP') && { protocol: 'http' }),
51
50
  })
52
51
 
53
52
  if (!port.open) {
@@ -17,7 +17,11 @@ export const SERVE_FUNCTIONS_FOLDER = 'functions-serve'
17
17
  * @returns {string}
18
18
  */
19
19
  export const getFunctionsDir = ({ config, options }, defaultValue) =>
20
- options.functions || config.dev?.functions || config.functionsDirectory || config.dev?.Functions || defaultValue
20
+ options.functions ||
21
+ (config.dev && config.dev.functions) ||
22
+ config.functionsDirectory ||
23
+ (config.dev && config.dev.Functions) ||
24
+ defaultValue
21
25
 
22
26
  export const getFunctionsManifestPath = async ({ base }) => {
23
27
  const path = resolve(base, getPathInProject(['functions', 'manifest.json']))
@@ -33,13 +37,6 @@ export const getFunctionsDistPath = async ({ base }) => {
33
37
  return isDirectory ? path : null
34
38
  }
35
39
 
36
- /**
37
- * Retrieves the internal functions directory and creates it if ensureExists is provided
38
- * @param {object} config
39
- * @param {string} config.base
40
- * @param {boolean=} config.ensureExists
41
- * @returns
42
- */
43
40
  export const getInternalFunctionsDir = async ({ base, ensureExists }) => {
44
41
  const path = resolve(base, getPathInProject([INTERNAL_FUNCTIONS_FOLDER]))
45
42
 
@@ -1,5 +1,6 @@
1
1
  // @ts-check
2
2
  import { dirname } from 'path'
3
+ import process from 'process'
3
4
  import util from 'util'
4
5
 
5
6
  import { findUp } from 'find-up'
@@ -13,14 +14,14 @@ import { log } from './command-helpers.mjs'
13
14
  *
14
15
  * @param {object} config
15
16
  * @param {string} [config.remoteName]
16
- * @param {string} config.workingDir
17
17
  * @returns
18
18
  */
19
- const getRepoData = async function ({ remoteName, workingDir }) {
19
+ const getRepoData = async function ({ remoteName } = {}) {
20
20
  try {
21
+ const cwd = process.cwd()
21
22
  const [gitConfig, gitDirectory] = await Promise.all([
22
- util.promisify(gitconfiglocal)(workingDir),
23
- findUp('.git', { cwd: workingDir, type: 'directory' }),
23
+ util.promisify(gitconfiglocal)(cwd),
24
+ findUp('.git', { cwd, type: 'directory' }),
24
25
  ])
25
26
 
26
27
  if (!gitDirectory || !gitConfig || !gitConfig.remote || Object.keys(gitConfig.remote).length === 0) {
@@ -29,7 +30,7 @@ const getRepoData = async function ({ remoteName, workingDir }) {
29
30
 
30
31
  const baseGitPath = dirname(gitDirectory)
31
32
 
32
- if (workingDir !== baseGitPath) {
33
+ if (cwd !== baseGitPath) {
33
34
  log(`Git directory located in ${baseGitPath}`)
34
35
  }
35
36
 
@@ -207,7 +207,7 @@ export const configGithub = async ({ command, repoName, repoOwner, siteId }) =>
207
207
  const { netlify } = command
208
208
  const {
209
209
  api,
210
- cachedConfig: { configPath },
210
+ cachedConfig: { configPath, env },
211
211
  config,
212
212
  globalConfig,
213
213
  repositoryRoot,
@@ -220,7 +220,7 @@ export const configGithub = async ({ command, repoName, repoOwner, siteId }) =>
220
220
  repositoryRoot,
221
221
  siteRoot,
222
222
  config,
223
- command,
223
+ env,
224
224
  })
225
225
  await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir })
226
226