netlify-cli 15.8.1-rc.1 → 15.9.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.
@@ -1,28 +1,24 @@
1
1
  // @ts-check
2
2
  import { readFile } from 'fs/promises'
3
3
  import { EOL } from 'os'
4
- import path, { join } from 'path'
4
+ import path from 'path'
5
5
  import process from 'process'
6
6
 
7
- import { getFramework, getSettings } from '@netlify/build-info'
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'
8
11
  import fuzzy from 'fuzzy'
9
12
  import getPort from 'get-port'
10
13
 
11
14
  import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs'
12
15
  import { acquirePort } from './dev.mjs'
13
16
  import { getInternalFunctionsDir } from './functions/functions.mjs'
17
+ import { reportError } from './telemetry/report-error.mjs'
14
18
 
15
- /** @param {string} str */
16
19
  const formatProperty = (str) => chalk.magenta(`'${str}'`)
17
- /** @param {string} str */
18
20
  const formatValue = (str) => chalk.green(`'${str}'`)
19
21
 
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
22
  const readHttpsSettings = async (options) => {
27
23
  if (typeof options !== 'object' || !options.keyFile || !options.certFile) {
28
24
  throw new TypeError(
@@ -55,11 +51,6 @@ const readHttpsSettings = async (options) => {
55
51
  return { key, cert, keyFilePath: path.resolve(keyFile), certFilePath: path.resolve(certFile) }
56
52
  }
57
53
 
58
- /**
59
- * @param {object} config
60
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
61
- * @param {keyof import('../commands/dev/types.js').DevConfig} config.property
62
- */
63
54
  const validateStringProperty = ({ devConfig, property }) => {
64
55
  if (devConfig[property] && typeof devConfig[property] !== 'string') {
65
56
  const formattedProperty = formatProperty(property)
@@ -69,11 +60,6 @@ const validateStringProperty = ({ devConfig, property }) => {
69
60
  }
70
61
  }
71
62
 
72
- /**
73
- * @param {object} config
74
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
75
- * @param {keyof import('../commands/dev/types.js').DevConfig} config.property
76
- */
77
63
  const validateNumberProperty = ({ devConfig, property }) => {
78
64
  if (devConfig[property] && typeof devConfig[property] !== 'number') {
79
65
  const formattedProperty = formatProperty(property)
@@ -83,11 +69,6 @@ const validateNumberProperty = ({ devConfig, property }) => {
83
69
  }
84
70
  }
85
71
 
86
- /**
87
- *
88
- * @param {object} config
89
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
90
- */
91
72
  const validateFrameworkConfig = ({ devConfig }) => {
92
73
  validateStringProperty({ devConfig, property: 'command' })
93
74
  validateNumberProperty({ devConfig, property: 'port' })
@@ -102,11 +83,6 @@ const validateFrameworkConfig = ({ devConfig }) => {
102
83
  }
103
84
  }
104
85
 
105
- /**
106
- * @param {object} config
107
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
108
- * @param {number=} config.detectedPort
109
- */
110
86
  const validateConfiguredPort = ({ detectedPort, devConfig }) => {
111
87
  if (devConfig.port && devConfig.port === detectedPort) {
112
88
  const formattedPort = formatProperty('port')
@@ -126,11 +102,6 @@ const getDefaultDist = () => {
126
102
  return process.cwd()
127
103
  }
128
104
 
129
- /**
130
- * @param {object} config
131
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
132
- * @returns {Promise<number>}
133
- */
134
105
  const getStaticServerPort = async ({ devConfig }) => {
135
106
  const port = await acquirePort({
136
107
  configuredPort: devConfig.staticServerPort,
@@ -143,10 +114,10 @@ const getStaticServerPort = async ({ devConfig }) => {
143
114
 
144
115
  /**
145
116
  *
146
- * @param {object} config
147
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
148
- * @param {import('commander').OptionValues} config.options
149
- * @param {string} config.projectDir
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
150
121
  * @returns {Promise<import('./types.js').BaseServerSettings>}
151
122
  */
152
123
  const handleStaticServer = async ({ devConfig, options, projectDir }) => {
@@ -182,84 +153,120 @@ const handleStaticServer = async ({ devConfig, options, projectDir }) => {
182
153
  }
183
154
  }
184
155
 
185
- // these plugins represent runtimes that are
186
- // expected to be "automatically" installed. Even though
187
- // they can be installed on package/toml, we always
188
- // want them installed in the site settings. When installed
189
- // there our build will automatically install the latest without
190
- // user management of the versioning.
191
- const pluginsToAlwaysInstall = new Set(['@netlify/plugin-nextjs'])
192
-
193
- /**
194
- * Retrieve a list of plugins to auto install
195
- * @param {string[]=} pluginsInstalled
196
- * @param {string[]=} pluginsRecommended
197
- * @returns
198
- */
199
- const getPluginsToAutoInstall = (pluginsInstalled = [], pluginsRecommended = []) =>
200
- pluginsRecommended.reduce(
201
- (acc, plugin) =>
202
- pluginsInstalled.includes(plugin) && !pluginsToAlwaysInstall.has(plugin) ? acc : [...acc, plugin],
203
- // eslint-disable-next-line no-inline-comments
204
- /** @type {string[]} */ ([]),
205
- )
206
-
207
156
  /**
208
157
  * Retrieves the settings from a framework
209
- * @param {import('@netlify/build-info').Settings} settings
158
+ * @param {import('./types.js').FrameworkInfo} framework
210
159
  * @returns {import('./types.js').BaseServerSettings}
211
160
  */
212
- const getSettingsFromDetectedSettings = (settings) => {
161
+ const getSettingsFromFramework = (framework) => {
213
162
  const {
214
- devCommand: command,
215
- dist,
216
- env,
217
- framework: { name: frameworkName },
218
- frameworkPort,
219
- // eslint-disable-next-line camelcase
220
- plugins_from_config_file,
221
- // eslint-disable-next-line camelcase
222
- plugins_recommended,
223
- pollingStrategies,
224
- } = settings
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
225
174
 
226
175
  return {
227
176
  command,
228
177
  frameworkPort,
229
- dist,
178
+ dist: staticDir || dist,
230
179
  framework: frameworkName,
231
180
  env,
232
- pollingStrategies,
233
- plugins: getPluginsToAutoInstall(plugins_from_config_file, plugins_recommended),
181
+ pollingStrategies: pollingStrategies.map(({ name }) => name),
182
+ plugins,
234
183
  }
235
184
  }
236
185
 
186
+ const hasDevCommand = (framework) => Array.isArray(framework.dev.commands) && framework.dev.commands.length !== 0
187
+
237
188
  /**
238
- * @param {import('@netlify/build-info').Project} project
189
+ * The new build setting detection with build systems and frameworks combined
190
+ * @param {string} projectDir
239
191
  */
240
- const detectFrameworkSettings = async (project) => {
241
- const projectSettings = await project.getBuildSettings()
242
- const settings = projectSettings
243
- .filter((setting) =>
244
- project.workspace && !project.workspace.isRoot
245
- ? process.cwd().startsWith(join(project.jsWorkspaceRoot, setting.packagePath ?? ''))
246
- : true,
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 },
247
249
  )
248
- .filter((setting) => setting.devCommand)
250
+ }
251
+ }
252
+
253
+ const detectFrameworkSettings = async ({ projectDir }) => {
254
+ const projectFrameworks = await listFrameworks({ projectDir })
255
+ const frameworks = projectFrameworks.filter((framework) => hasDevCommand(framework))
249
256
 
250
- if (settings.length === 1) {
251
- return getSettingsFromDetectedSettings(settings[0])
257
+ if (frameworks.length === 1) {
258
+ return getSettingsFromFramework(frameworks[0])
252
259
  }
253
260
 
254
- if (settings.length > 1) {
261
+ if (frameworks.length > 1) {
255
262
  // performance optimization, load inquirer on demand
256
263
  const { default: inquirer } = await import('inquirer')
257
264
  const { default: inquirerAutocompletePrompt } = await import('inquirer-autocomplete-prompt')
258
265
  /** multiple matching detectors, make the user choose */
259
266
  inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
260
- const scriptInquirerOptions = formatSettingsArrForInquirer(settings)
261
- const { chosenSettings } = await inquirer.prompt({
262
- name: 'chosenSettings',
267
+ const scriptInquirerOptions = formatSettingsArrForInquirer(frameworks)
268
+ const { chosenFramework } = await inquirer.prompt({
269
+ name: 'chosenFramework',
263
270
  message: `Multiple possible start commands found`,
264
271
  type: 'autocomplete',
265
272
  source(_, input) {
@@ -272,24 +279,19 @@ const detectFrameworkSettings = async (project) => {
272
279
  })
273
280
  log(
274
281
  `Add ${formatProperty(
275
- `framework = "${chosenSettings.framework.id}"`,
282
+ `framework = "${chosenFramework.id}"`,
276
283
  )} to the [dev] section of your netlify.toml to avoid this selection prompt next time`,
277
284
  )
278
285
 
279
- return getSettingsFromDetectedSettings(chosenSettings)
286
+ return getSettingsFromFramework(chosenFramework)
280
287
  }
281
288
  }
282
289
 
283
- /**
284
- * @param {object} config
285
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
286
- */
287
290
  const hasCommandAndTargetPort = ({ devConfig }) => devConfig.command && devConfig.targetPort
288
291
 
289
292
  /**
290
293
  * Creates settings for the custom framework
291
- * @param {object} config
292
- * @param {import('../commands/dev/types.js').DevConfig} config.devConfig
294
+ * @param {*} param0
293
295
  * @returns {import('./types.js').BaseServerSettings}
294
296
  */
295
297
  const handleCustomFramework = ({ devConfig }) => {
@@ -309,9 +311,6 @@ const handleCustomFramework = ({ devConfig }) => {
309
311
  }
310
312
  }
311
313
 
312
- /**
313
- * @param {{ devConfig: any, frameworkSettings: import('./types.js').BaseServerSettings }} param0
314
- */
315
314
  const mergeSettings = async ({ devConfig, frameworkSettings = {} }) => {
316
315
  const {
317
316
  command: frameworkCommand,
@@ -339,14 +338,12 @@ const mergeSettings = async ({ devConfig, frameworkSettings = {} }) => {
339
338
 
340
339
  /**
341
340
  * Handles a forced framework and retrieves the settings for it
342
- * @param {{ devConfig: any, project: import('@netlify/build-info').Project }} config
341
+ * @param {*} param0
343
342
  * @returns {Promise<import('./types.js').BaseServerSettings>}
344
343
  */
345
- const handleForcedFramework = async ({ devConfig, project }) => {
344
+ const handleForcedFramework = async ({ devConfig, projectDir }) => {
346
345
  // this throws if `devConfig.framework` is not a supported framework
347
- const framework = await getFramework(devConfig.framework, project)
348
- const settings = await getSettings(framework, project, '')
349
- const frameworkSettings = getSettingsFromDetectedSettings(settings)
346
+ const frameworkSettings = getSettingsFromFramework(await getFramework(devConfig.framework, { projectDir }))
350
347
  return mergeSettings({ devConfig, frameworkSettings })
351
348
  }
352
349
 
@@ -354,12 +351,11 @@ const handleForcedFramework = async ({ devConfig, project }) => {
354
351
  * Get the server settings based on the flags and the devConfig
355
352
  * @param {import('../commands/dev/types.js').DevConfig} devConfig
356
353
  * @param {import('commander').OptionValues} options
357
- * @param {import('@netlify/build-info').Project} project
358
354
  * @param {string} projectDir
355
+ * @param {Record<string, Record<string, any>>} [metadata]
359
356
  * @returns {Promise<import('./types.js').ServerSettings>}
360
357
  */
361
-
362
- const detectServerSettings = async (devConfig, options, project, projectDir) => {
358
+ const detectServerSettings = async (devConfig, options, projectDir, metadata) => {
363
359
  validateStringProperty({ devConfig, property: 'framework' })
364
360
 
365
361
  /** @type {Partial<import('./types.js').BaseServerSettings>} */
@@ -372,9 +368,22 @@ const detectServerSettings = async (devConfig, options, project, projectDir) =>
372
368
  // this is the default CLI behavior
373
369
 
374
370
  const runDetection = !hasCommandAndTargetPort({ devConfig })
375
- const frameworkSettings = runDetection ? await detectFrameworkSettings(project) : undefined
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
+ })
376
385
 
377
- if (frameworkSettings === undefined) {
386
+ if (frameworkSettings === undefined && runDetection) {
378
387
  log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
379
388
  settings = await handleStaticServer({ options, devConfig, projectDir })
380
389
  } else {
@@ -390,7 +399,7 @@ const detectServerSettings = async (devConfig, options, project, projectDir) =>
390
399
  } else if (devConfig.framework) {
391
400
  validateFrameworkConfig({ devConfig })
392
401
  // this is when the user explicitly configures a framework, e.g. `framework = "gatsby"`
393
- settings = await handleForcedFramework({ devConfig, project })
402
+ settings = await handleForcedFramework({ devConfig, projectDir })
394
403
  }
395
404
 
396
405
  validateConfiguredPort({ devConfig, detectedPort: settings.frameworkPort })
@@ -426,16 +435,15 @@ const filterSettings = function (scriptInquirerOptions, input) {
426
435
  return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name))
427
436
  }
428
437
 
429
- /**
430
- * @param {import('@netlify/build-info').Settings[]} settings
431
- * @returns
432
- */
433
- const formatSettingsArrForInquirer = function (settings) {
434
- return settings.map((setting) => ({
435
- name: `[${chalk.yellow(setting.framework.name)}] '${setting.devCommand}'`,
436
- value: { ...setting, commands: [setting.devCommand] },
437
- short: `${setting.name}-${setting.devCommand}`,
438
- }))
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()
439
447
  }
440
448
 
441
449
  /**
@@ -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 {import('./types.js').ServerSettings} config.settings
21
+ * @param {Partial<import('./types').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) {
@@ -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
- project: command.project,
223
+ env,
224
224
  })
225
225
  await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir })
226
226
 
@@ -5,12 +5,7 @@ import { exit, log } from '../command-helpers.mjs'
5
5
 
6
6
  import { createDeployKey, getBuildSettings, saveNetlifyToml, setupSite } from './utils.mjs'
7
7
 
8
- /**
9
- * Prompts for granting the netlify ssh public key access to your repo
10
- * @param {object} deployKey
11
- * @param {string} deployKey.public_key
12
- */
13
- const addDeployKey = async (deployKey) => {
8
+ const addDeployKey = async ({ deployKey }) => {
14
9
  log('\nGive this Netlify SSH public key access to your repository:\n')
15
10
  log(`\n${deployKey.public_key}\n\n`)
16
11
 
@@ -28,11 +23,6 @@ const addDeployKey = async (deployKey) => {
28
23
  }
29
24
  }
30
25
 
31
- /**
32
- * @param {object} config
33
- * @param {Awaited<ReturnType<import('../../utils/get-repo-data.mjs').default>>} config.repoData
34
- * @returns {Promise<string>}
35
- */
36
26
  const getRepoPath = async ({ repoData }) => {
37
27
  const { repoPath } = await inquirer.prompt([
38
28
  {
@@ -40,9 +30,6 @@ const getRepoPath = async ({ repoData }) => {
40
30
  name: 'repoPath',
41
31
  message: 'The SSH URL of the remote git repo:',
42
32
  default: repoData.url,
43
- /**
44
- * @param {string} url
45
- */
46
33
  validate: (url) => SSH_URL_REGEXP.test(url) || 'The URL provided does not use the SSH protocol',
47
34
  },
48
35
  ])
@@ -50,11 +37,7 @@ const getRepoPath = async ({ repoData }) => {
50
37
  return repoPath
51
38
  }
52
39
 
53
- /**
54
- * @param {string} deployHook
55
- * @returns
56
- */
57
- const addDeployHook = async (deployHook) => {
40
+ const addDeployHook = async ({ deployHook }) => {
58
41
  log('\nConfigure the following webhook for your repository:\n')
59
42
  log(`\n${deployHook}\n\n`)
60
43
  const { deployHookAdded } = await inquirer.prompt([
@@ -72,14 +55,14 @@ const addDeployHook = async (deployHook) => {
72
55
  /**
73
56
  * @param {object} config
74
57
  * @param {import('../../commands/base-command.mjs').default} config.command
75
- * @param {Awaited<ReturnType<import('../../utils/get-repo-data.mjs').default>>} config.repoData
58
+ * @param {*} config.repoData
76
59
  * @param {string} config.siteId
77
60
  */
78
61
  export default async function configManual({ command, repoData, siteId }) {
79
62
  const { netlify } = command
80
63
  const {
81
64
  api,
82
- cachedConfig: { configPath },
65
+ cachedConfig: { configPath, env },
83
66
  config,
84
67
  repositoryRoot,
85
68
  site: { root: siteRoot },
@@ -89,12 +72,12 @@ export default async function configManual({ command, repoData, siteId }) {
89
72
  repositoryRoot,
90
73
  siteRoot,
91
74
  config,
92
- project: command.project,
75
+ env,
93
76
  })
94
77
  await saveNetlifyToml({ repositoryRoot, config, configPath, baseDir, buildCmd, buildDir, functionsDir })
95
78
 
96
79
  const deployKey = await createDeployKey({ api })
97
- await addDeployKey(deployKey)
80
+ await addDeployKey({ deployKey })
98
81
 
99
82
  const repoPath = await getRepoPath({ repoData })
100
83
  const repo = {
@@ -116,7 +99,7 @@ export default async function configManual({ command, repoData, siteId }) {
116
99
  configPlugins: config.plugins,
117
100
  pluginsToInstall,
118
101
  })
119
- const deployHookAdded = await addDeployHook(updatedSite.deploy_hook)
102
+ const deployHookAdded = await addDeployHook({ deployHook: updatedSite.deploy_hook })
120
103
  if (!deployHookAdded) {
121
104
  exit()
122
105
  }
@@ -1,18 +1,22 @@
1
1
  // @ts-check
2
+ import { listFrameworks } from '@netlify/framework-info'
2
3
 
3
- /**
4
- * @param {{ project: import("@netlify/build-info").Project }} param0
5
- * @returns
6
- */
7
- export const getFrameworkInfo = async ({ project }) => {
8
- const settings = await project.getBuildSettings()
4
+ export const getFrameworkInfo = async ({ baseDirectory, nodeVersion }) => {
5
+ const frameworks = await listFrameworks({ projectDir: baseDirectory, nodeVersion })
9
6
  // several frameworks can be detected - first one has highest priority
10
- if (settings?.length) {
7
+ if (frameworks.length !== 0) {
8
+ const [
9
+ {
10
+ build: { commands, directory },
11
+ name,
12
+ plugins,
13
+ },
14
+ ] = frameworks
11
15
  return {
12
- frameworkName: settings[0].framework?.name,
13
- frameworkBuildCommand: settings[0].buildCommand,
14
- frameworkBuildDir: settings[0].dist,
15
- frameworkPlugins: settings[0].plugins_recommended,
16
+ frameworkName: name,
17
+ frameworkBuildCommand: commands[0],
18
+ frameworkBuildDir: directory,
19
+ frameworkPlugins: plugins,
16
20
  }
17
21
  }
18
22
  return {}
@@ -11,6 +11,7 @@ import { normalizeBackslash } from '../../lib/path.mjs'
11
11
  import { chalk, error as failAndExit, log, warn } from '../command-helpers.mjs'
12
12
 
13
13
  import { getFrameworkInfo } from './frameworks.mjs'
14
+ import { detectNodeVersion } from './node-version.mjs'
14
15
  import { getRecommendPlugins, getUIPlugins } from './plugins.mjs'
15
16
 
16
17
  const normalizeDir = ({ baseDirectory, defaultValue, dir }) => {
@@ -82,19 +83,17 @@ const getPromptInputs = ({ defaultBaseDir, defaultBuildCmd, defaultBuildDir }) =
82
83
  const getBaseDirectory = ({ repositoryRoot, siteRoot }) =>
83
84
  path.normalize(repositoryRoot) === path.normalize(siteRoot) ? process.cwd() : siteRoot
84
85
 
85
- /**
86
- * @param {{ config: any, repositoryRoot: any, siteRoot: any, project: import("@netlify/build-info").Project }} param0
87
- * @returns
88
- */
89
- export const getBuildSettings = async ({ config, project, repositoryRoot, siteRoot }) => {
86
+ export const getBuildSettings = async ({ config, env, repositoryRoot, siteRoot }) => {
90
87
  const baseDirectory = getBaseDirectory({ repositoryRoot, siteRoot })
88
+ const nodeVersion = await detectNodeVersion({ baseDirectory, env })
91
89
  const {
92
90
  frameworkBuildCommand,
93
91
  frameworkBuildDir,
94
92
  frameworkName,
95
93
  frameworkPlugins = [],
96
94
  } = await getFrameworkInfo({
97
- project,
95
+ baseDirectory,
96
+ nodeVersion,
98
97
  })
99
98
  const { defaultBaseDir, defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins } =
100
99
  await getDefaultSettings({
@@ -599,10 +599,6 @@ const onRequest = async (
599
599
  proxy.web(req, res, options)
600
600
  }
601
601
 
602
- /**
603
- * @param {import('./types.js').ServerSettings} settings
604
- * @returns
605
- */
606
602
  export const getProxyUrl = function (settings) {
607
603
  const scheme = settings.https ? 'https' : 'http'
608
604
  return `${scheme}://localhost:${settings.port}`