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