netlify-cli 15.9.1 → 15.10.0-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 (43) hide show
  1. package/bin/run.mjs +6 -5
  2. package/npm-shrinkwrap.json +369 -266
  3. package/package.json +8 -9
  4. package/src/commands/base-command.mjs +295 -116
  5. package/src/commands/build/build.mjs +9 -1
  6. package/src/commands/deploy/deploy.mjs +22 -9
  7. package/src/commands/dev/dev.mjs +22 -17
  8. package/src/commands/functions/functions-create.mjs +118 -89
  9. package/src/commands/functions/functions-invoke.mjs +10 -7
  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 +10 -6
  14. package/src/commands/sites/sites-create-template.mjs +1 -1
  15. package/src/commands/sites/sites-create.mjs +1 -1
  16. package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
  17. package/src/lib/edge-functions/bootstrap.mjs +1 -1
  18. package/src/lib/edge-functions/headers.mjs +1 -0
  19. package/src/lib/edge-functions/internal.mjs +5 -3
  20. package/src/lib/edge-functions/proxy.mjs +29 -4
  21. package/src/lib/functions/runtimes/js/index.mjs +1 -1
  22. package/src/lib/functions/runtimes/js/worker.mjs +1 -1
  23. package/src/lib/functions/server.mjs +3 -2
  24. package/src/lib/spinner.mjs +1 -1
  25. package/src/recipes/vscode/index.mjs +24 -6
  26. package/src/utils/build-info.mjs +100 -0
  27. package/src/utils/command-helpers.mjs +16 -7
  28. package/src/utils/detect-server-settings.mjs +133 -245
  29. package/src/utils/framework-server.mjs +6 -5
  30. package/src/utils/functions/functions.mjs +8 -5
  31. package/src/utils/get-repo-data.mjs +5 -6
  32. package/src/utils/init/config-github.mjs +2 -2
  33. package/src/utils/init/config-manual.mjs +24 -7
  34. package/src/utils/init/utils.mjs +62 -63
  35. package/src/utils/proxy-server.mjs +7 -4
  36. package/src/utils/proxy.mjs +4 -3
  37. package/src/utils/read-repo-url.mjs +4 -0
  38. package/src/utils/run-build.mjs +58 -32
  39. package/src/utils/shell.mjs +24 -7
  40. package/src/utils/state-config.mjs +5 -1
  41. package/src/utils/static-server.mjs +4 -0
  42. package/src/utils/telemetry/report-error.mjs +8 -4
  43. package/src/utils/init/frameworks.mjs +0 -23
@@ -1,26 +1,28 @@
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'
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'
4
+ import { dirname, relative, resolve } from 'path'
5
+
6
+ import { getFramework, getSettings } from '@netlify/build-info'
12
7
  import getPort from 'get-port'
13
- import inquirer from 'inquirer'
14
- import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
15
8
 
9
+ import { detectFrameworkSettings } from './build-info.mjs'
16
10
  import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs'
17
11
  import { acquirePort } from './dev.mjs'
18
12
  import { getInternalFunctionsDir } from './functions/functions.mjs'
19
- import { reportError } from './telemetry/report-error.mjs'
13
+ import { getPluginsToAutoInstall } from './init/utils.mjs'
20
14
 
15
+ /** @param {string} str */
21
16
  const formatProperty = (str) => chalk.magenta(`'${str}'`)
17
+ /** @param {string} str */
22
18
  const formatValue = (str) => chalk.green(`'${str}'`)
23
19
 
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
+ */
24
26
  const readHttpsSettings = async (options) => {
25
27
  if (typeof options !== 'object' || !options.keyFile || !options.certFile) {
26
28
  throw new TypeError(
@@ -38,43 +40,43 @@ const readHttpsSettings = async (options) => {
38
40
  throw new TypeError(`Certificate file configuration should be a string`)
39
41
  }
40
42
 
41
- const [{ reason: keyError, value: key }, { reason: certError, value: cert }] = await Promise.allSettled([
42
- readFile(keyFile, 'utf-8'),
43
- readFile(certFile, 'utf-8'),
44
- ])
43
+ const [key, cert] = await Promise.allSettled([readFile(keyFile, 'utf-8'), readFile(certFile, 'utf-8')])
45
44
 
46
- if (keyError) {
47
- throw new Error(`Error reading private key file: ${keyError.message}`)
45
+ if (key.status === 'rejected') {
46
+ throw new Error(`Error reading private key file: ${key.reason}`)
48
47
  }
49
- if (certError) {
50
- throw new Error(`Error reading certificate file: ${certError.message}`)
48
+ if (cert.status === 'rejected') {
49
+ throw new Error(`Error reading certificate file: ${cert.reason}`)
51
50
  }
52
51
 
53
- return { key, cert, keyFilePath: path.resolve(keyFile), certFilePath: path.resolve(certFile) }
54
- }
55
-
56
- const validateStringProperty = ({ devConfig, property }) => {
57
- if (devConfig[property] && typeof devConfig[property] !== 'string') {
58
- const formattedProperty = formatProperty(property)
59
- throw new TypeError(
60
- `Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be a string`,
61
- )
62
- }
52
+ return { key: key.value, cert: cert.value, keyFilePath: resolve(keyFile), certFilePath: resolve(certFile) }
63
53
  }
64
54
 
65
- const validateNumberProperty = ({ devConfig, property }) => {
66
- if (devConfig[property] && typeof devConfig[property] !== 'number') {
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) {
67
64
  const formattedProperty = formatProperty(property)
68
65
  throw new TypeError(
69
- `Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be an integer`,
66
+ `Invalid ${formattedProperty} option provided in config. The value of ${formattedProperty} option must be of type ${type}`,
70
67
  )
71
68
  }
72
69
  }
73
70
 
71
+ /**
72
+ *
73
+ * @param {object} config
74
+ * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
75
+ */
74
76
  const validateFrameworkConfig = ({ devConfig }) => {
75
- validateStringProperty({ devConfig, property: 'command' })
76
- validateNumberProperty({ devConfig, property: 'port' })
77
- validateNumberProperty({ devConfig, property: 'targetPort' })
77
+ validateProperty(devConfig, 'command', 'string')
78
+ validateProperty(devConfig, 'port', 'number')
79
+ validateProperty(devConfig, 'targetPort', 'number')
78
80
 
79
81
  if (devConfig.targetPort && devConfig.targetPort === devConfig.port) {
80
82
  throw new Error(
@@ -85,6 +87,11 @@ const validateFrameworkConfig = ({ devConfig }) => {
85
87
  }
86
88
  }
87
89
 
90
+ /**
91
+ * @param {object} config
92
+ * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
93
+ * @param {number=} config.detectedPort
94
+ */
88
95
  const validateConfiguredPort = ({ detectedPort, devConfig }) => {
89
96
  if (devConfig.port && devConfig.port === detectedPort) {
90
97
  const formattedPort = formatProperty('port')
@@ -97,13 +104,22 @@ const validateConfiguredPort = ({ detectedPort, devConfig }) => {
97
104
  const DEFAULT_PORT = 8888
98
105
  const DEFAULT_STATIC_PORT = 3999
99
106
 
100
- const getDefaultDist = () => {
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) => {
101
112
  log(`${NETLIFYDEVWARN} Unable to determine public folder to serve files from. Using current working directory`)
102
113
  log(`${NETLIFYDEVWARN} Setup a netlify.toml file with a [dev] section to specify your dev server settings.`)
103
114
  log(`${NETLIFYDEVWARN} See docs at: https://cli.netlify.com/netlify-dev#project-detection`)
104
- return process.cwd()
115
+ return workingDir
105
116
  }
106
117
 
118
+ /**
119
+ * @param {object} config
120
+ * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
121
+ * @returns {Promise<number>}
122
+ */
107
123
  const getStaticServerPort = async ({ devConfig }) => {
108
124
  const port = await acquirePort({
109
125
  configuredPort: devConfig.staticServerPort,
@@ -116,16 +132,16 @@ const getStaticServerPort = async ({ devConfig }) => {
116
132
 
117
133
  /**
118
134
  *
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>}
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}>}
124
140
  */
125
- const handleStaticServer = async ({ devConfig, options, projectDir }) => {
126
- validateNumberProperty({ devConfig, property: 'staticServerPort' })
141
+ const handleStaticServer = async ({ devConfig, flags, workingDir }) => {
142
+ validateProperty(devConfig, 'staticServerPort', 'number')
127
143
 
128
- if (options.dir) {
144
+ if (flags.dir) {
129
145
  log(`${NETLIFYDEVWARN} Using simple static server because ${formatProperty('--dir')} flag was specified`)
130
146
  } else if (devConfig.framework === '#static') {
131
147
  log(
@@ -143,8 +159,8 @@ const handleStaticServer = async ({ devConfig, options, projectDir }) => {
143
159
  )
144
160
  }
145
161
 
146
- const dist = options.dir || devConfig.publish || getDefaultDist()
147
- log(`${NETLIFYDEVWARN} Running static server from "${path.relative(path.dirname(projectDir), dist)}"`)
162
+ const dist = flags.dir || devConfig.publish || getDefaultDist(workingDir)
163
+ log(`${NETLIFYDEVWARN} Running static server from "${relative(dirname(workingDir), dist)}"`)
148
164
 
149
165
  const frameworkPort = await getStaticServerPort({ devConfig })
150
166
  return {
@@ -157,144 +173,39 @@ const handleStaticServer = async ({ devConfig, options, projectDir }) => {
157
173
 
158
174
  /**
159
175
  * Retrieves the settings from a framework
160
- * @param {import('./types.js').FrameworkInfo} framework
161
- * @returns {import('./types.js').BaseServerSettings}
176
+ * @param {import('@netlify/build-info').Settings} [settings]
177
+ * @returns {import('./types.js').BaseServerSettings | undefined}
162
178
  */
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
-
179
+ const getSettingsFromDetectedSettings = (settings) => {
180
+ if (!settings) {
181
+ return
182
+ }
177
183
  return {
178
- command,
179
- frameworkPort,
180
- dist: staticDir || dist,
181
- framework: frameworkName,
182
- env,
183
- pollingStrategies: pollingStrategies.map(({ name }) => name),
184
- plugins,
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),
185
192
  }
186
193
  }
187
194
 
188
- const hasDevCommand = (framework) => Array.isArray(framework.dev.commands) && framework.dev.commands.length !== 0
189
-
190
195
  /**
191
- * The new build setting detection with build systems and frameworks combined
192
- * @param {string} projectDir
196
+ * @param {import('../commands/dev/types.js').DevConfig} devConfig
193
197
  */
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
198
+ const hasCommandAndTargetPort = (devConfig) => devConfig.command && devConfig.targetPort
290
199
 
291
200
  /**
292
201
  * Creates settings for the custom framework
293
- * @param {*} param0
202
+ * @param {object} config
203
+ * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
204
+ * @param {string} config.workingDir
294
205
  * @returns {import('./types.js').BaseServerSettings}
295
206
  */
296
- const handleCustomFramework = ({ devConfig }) => {
297
- if (!hasCommandAndTargetPort({ devConfig })) {
207
+ const handleCustomFramework = ({ devConfig, workingDir }) => {
208
+ if (!hasCommandAndTargetPort(devConfig)) {
298
209
  throw new Error(
299
210
  `${formatProperty('command')} and ${formatProperty('targetPort')} properties are required when ${formatProperty(
300
211
  'framework',
@@ -304,101 +215,100 @@ const handleCustomFramework = ({ devConfig }) => {
304
215
  return {
305
216
  command: devConfig.command,
306
217
  frameworkPort: devConfig.targetPort,
307
- dist: devConfig.publish || getDefaultDist(),
218
+ dist: devConfig.publish || getDefaultDist(workingDir),
308
219
  framework: '#custom',
309
220
  pollingStrategies: devConfig.pollingStrategies || [],
310
221
  }
311
222
  }
312
223
 
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
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
325
234
  // if the framework doesn't start a server, we use a static one
326
235
  const useStaticServer = !(command && frameworkPort)
327
236
  return {
237
+ baseDirectory: devConfig.base || frameworkSettings.baseDirectory,
328
238
  command,
329
239
  frameworkPort: useStaticServer ? await getStaticServerPort({ devConfig }) : frameworkPort,
330
- dist: devConfig.publish || dist || getDefaultDist(),
331
- framework,
332
- env,
333
- pollingStrategies,
240
+ dist: devConfig.publish || frameworkSettings.dist || getDefaultDist(workingDir),
241
+ framework: frameworkSettings.framework,
242
+ env: frameworkSettings.env,
243
+ pollingStrategies: frameworkSettings.pollingStrategies || [],
334
244
  useStaticServer,
335
245
  }
336
246
  }
337
247
 
338
248
  /**
339
249
  * Handles a forced framework and retrieves the settings for it
340
- * @param {*} param0
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
341
255
  * @returns {Promise<import('./types.js').BaseServerSettings>}
342
256
  */
343
- const handleForcedFramework = async ({ devConfig, projectDir }) => {
257
+ const handleForcedFramework = async ({ devConfig, project, workingDir, workspacePackage }) => {
344
258
  // this throws if `devConfig.framework` is not a supported framework
345
- const frameworkSettings = getSettingsFromFramework(await getFramework(devConfig.framework, { projectDir }))
346
- return mergeSettings({ devConfig, frameworkSettings })
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 })
347
263
  }
348
264
 
349
265
  /**
350
266
  * Get the server settings based on the flags and the devConfig
351
267
  * @param {import('../commands/dev/types.js').DevConfig} devConfig
352
- * @param {import('commander').OptionValues} options
353
- * @param {string} projectDir
354
- * @param {Record<string, Record<string, any>>} [metadata]
268
+ * @param {import('commander').OptionValues} flags
269
+ * @param {import('../commands/base-command.mjs').default} command
355
270
  * @returns {Promise<import('./types.js').ServerSettings>}
356
271
  */
357
- const detectServerSettings = async (devConfig, options, projectDir, metadata) => {
358
- validateStringProperty({ devConfig, property: 'framework' })
272
+
273
+ const detectServerSettings = async (devConfig, flags, command) => {
274
+ validateProperty(devConfig, 'framework', 'string')
359
275
 
360
276
  /** @type {Partial<import('./types.js').BaseServerSettings>} */
361
277
  let settings = {}
362
278
 
363
- if (options.dir || devConfig.framework === '#static') {
279
+ if (flags.dir || devConfig.framework === '#static') {
364
280
  // serving files statically without a framework server
365
- settings = await handleStaticServer({ options, devConfig, projectDir })
281
+ settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
366
282
  } else if (devConfig.framework === '#auto') {
367
283
  // this is the default CLI behavior
368
284
 
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
-
285
+ const runDetection = !hasCommandAndTargetPort(devConfig)
286
+ const frameworkSettings = runDetection
287
+ ? getSettingsFromDetectedSettings(await detectFrameworkSettings(command, 'dev'))
288
+ : undefined
385
289
  if (frameworkSettings === undefined && runDetection) {
386
290
  log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
387
- settings = await handleStaticServer({ options, devConfig, projectDir })
291
+ settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
388
292
  } else {
389
293
  validateFrameworkConfig({ devConfig })
390
- settings = await mergeSettings({ devConfig, frameworkSettings })
294
+
295
+ settings = await mergeSettings({ devConfig, frameworkSettings, workingDir: command.workingDir })
391
296
  }
392
297
 
393
- settings.plugins = frameworkSettings && frameworkSettings.plugins
298
+ settings.plugins = frameworkSettings?.plugins
394
299
  } else if (devConfig.framework === '#custom') {
395
300
  validateFrameworkConfig({ devConfig })
396
301
  // when the users wants to configure `command` and `targetPort`
397
- settings = handleCustomFramework({ devConfig })
302
+ settings = handleCustomFramework({ devConfig, workingDir: command.workingDir })
398
303
  } else if (devConfig.framework) {
399
304
  validateFrameworkConfig({ devConfig })
400
305
  // this is when the user explicitly configures a framework, e.g. `framework = "gatsby"`
401
- settings = await handleForcedFramework({ devConfig, projectDir })
306
+ settings = await handleForcedFramework({
307
+ devConfig,
308
+ project: command.project,
309
+ workingDir: command.workingDir,
310
+ workspacePackage: command.workspacePackage,
311
+ })
402
312
  }
403
313
 
404
314
  validateConfiguredPort({ devConfig, detectedPort: settings.frameworkPort })
@@ -409,7 +319,7 @@ const detectServerSettings = async (devConfig, options, projectDir, metadata) =>
409
319
  errorMessage: `Could not acquire required ${formatProperty('port')}`,
410
320
  })
411
321
  const functionsDir = devConfig.functions || settings.functions
412
- const internalFunctionsDir = await getInternalFunctionsDir({ base: projectDir })
322
+ const internalFunctionsDir = await getInternalFunctionsDir({ base: command.workingDir })
413
323
  const shouldStartFunctionsServer = Boolean(functionsDir || internalFunctionsDir)
414
324
 
415
325
  return {
@@ -423,28 +333,6 @@ const detectServerSettings = async (devConfig, options, projectDir, metadata) =>
423
333
  }
424
334
  }
425
335
 
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
-
448
336
  /**
449
337
  * Returns a copy of the provided config with any plugins provided by the
450
338
  * server settings
@@ -18,13 +18,14 @@ 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
+ * @param {string} config.cwd
22
23
  * @returns {Promise<StartReturnObject>}
23
24
  */
24
- export const startFrameworkServer = async function ({ settings }) {
25
+ export const startFrameworkServer = async function ({ cwd, settings }) {
25
26
  if (settings.useStaticServer) {
26
27
  if (settings.command) {
27
- runCommand(settings.command, settings.env)
28
+ runCommand(settings.command, { env: settings.env, cwd })
28
29
  }
29
30
  await startStaticServer({ settings })
30
31
 
@@ -37,7 +38,7 @@ export const startFrameworkServer = async function ({ settings }) {
37
38
  text: `Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
38
39
  })
39
40
 
40
- runCommand(settings.command, settings.env, spinner)
41
+ runCommand(settings.command, { env: settings.env, spinner, cwd })
41
42
 
42
43
  let port
43
44
  try {
@@ -46,7 +47,7 @@ export const startFrameworkServer = async function ({ settings }) {
46
47
  host: 'localhost',
47
48
  output: 'silent',
48
49
  timeout: FRAMEWORK_PORT_TIMEOUT,
49
- ...(settings.pollingStrategies.includes('HTTP') && { protocol: 'http' }),
50
+ ...(settings.pollingStrategies?.includes('HTTP') && { protocol: 'http' }),
50
51
  })
51
52
 
52
53
  if (!port.open) {
@@ -17,11 +17,7 @@ export const SERVE_FUNCTIONS_FOLDER = 'functions-serve'
17
17
  * @returns {string}
18
18
  */
19
19
  export const getFunctionsDir = ({ config, options }, defaultValue) =>
20
- options.functions ||
21
- (config.dev && config.dev.functions) ||
22
- config.functionsDirectory ||
23
- (config.dev && config.dev.Functions) ||
24
- defaultValue
20
+ options.functions || config.dev?.functions || config.functionsDirectory || config.dev?.Functions || defaultValue
25
21
 
26
22
  export const getFunctionsManifestPath = async ({ base }) => {
27
23
  const path = resolve(base, getPathInProject(['functions', 'manifest.json']))
@@ -37,6 +33,13 @@ export const getFunctionsDistPath = async ({ base }) => {
37
33
  return isDirectory ? path : null
38
34
  }
39
35
 
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
+ */
40
43
  export const getInternalFunctionsDir = async ({ base, ensureExists }) => {
41
44
  const path = resolve(base, getPathInProject([INTERNAL_FUNCTIONS_FOLDER]))
42
45
 
@@ -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