netlify-cli 15.9.1-rc.0 → 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.
@@ -4,11 +4,9 @@ import { EOL } from 'os'
4
4
  import { dirname, relative, resolve } from 'path'
5
5
 
6
6
  import { getFramework, getSettings } from '@netlify/build-info'
7
- import fuzzy from 'fuzzy'
8
7
  import getPort from 'get-port'
9
- import inquirer from 'inquirer'
10
8
 
11
- import { detectBuildSettings } from './build-info.mjs'
9
+ import { detectFrameworkSettings } from './build-info.mjs'
12
10
  import { NETLIFYDEVWARN, chalk, log } from './command-helpers.mjs'
13
11
  import { acquirePort } from './dev.mjs'
14
12
  import { getInternalFunctionsDir } from './functions/functions.mjs'
@@ -175,53 +173,22 @@ const handleStaticServer = async ({ devConfig, flags, workingDir }) => {
175
173
 
176
174
  /**
177
175
  * Retrieves the settings from a framework
178
- * @param {import('@netlify/build-info').Settings} settings
179
- * @returns {import('./types.js').BaseServerSettings}
180
- */
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
- })
190
-
191
- /**
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
176
+ * @param {import('@netlify/build-info').Settings} [settings]
177
+ * @returns {import('./types.js').BaseServerSettings | undefined}
195
178
  */
196
- const detectFrameworkSettings = async (command) => {
197
- const settings = await detectBuildSettings(command)
198
- if (settings.length === 1) {
199
- return getSettingsFromDetectedSettings(settings[0])
179
+ const getSettingsFromDetectedSettings = (settings) => {
180
+ if (!settings) {
181
+ return
200
182
  }
201
-
202
- if (settings.length > 1) {
203
- /** multiple matching detectors, make the user choose */
204
- const scriptInquirerOptions = formatSettingsArrForInquirer(settings)
205
- const { chosenSettings } = await inquirer.prompt({
206
- name: 'chosenSettings',
207
- message: `Multiple possible dev commands found`,
208
- type: 'autocomplete',
209
- source(/** @type {string} */ _, input = '') {
210
- if (!input || input === '') {
211
- return scriptInquirerOptions
212
- }
213
- // only show filtered results
214
- return filterSettings(scriptInquirerOptions, input)
215
- },
216
- })
217
- // TODO: do better logging here with the framework command or port
218
- log(
219
- `Add ${formatProperty(
220
- `framework = "${chosenSettings.framework.id}"`,
221
- )} to the [dev] section of your netlify.toml to avoid this selection prompt next time`,
222
- )
223
-
224
- return getSettingsFromDetectedSettings(chosenSettings)
183
+ 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),
225
192
  }
226
193
  }
227
194
 
@@ -262,26 +229,18 @@ const handleCustomFramework = ({ devConfig, workingDir }) => {
262
229
  * @param {Partial<import('./types.js').BaseServerSettings>=} config.frameworkSettings
263
230
  */
264
231
  const mergeSettings = async ({ devConfig, frameworkSettings = {}, workingDir }) => {
265
- const {
266
- command: frameworkCommand,
267
- dist,
268
- env,
269
- framework,
270
- frameworkPort: frameworkDetectedPort,
271
- pollingStrategies = [],
272
- } = frameworkSettings
273
-
274
- const command = devConfig.command || frameworkCommand
275
- const frameworkPort = devConfig.targetPort || frameworkDetectedPort
232
+ const command = devConfig.command || frameworkSettings.command
233
+ const frameworkPort = devConfig.targetPort || frameworkSettings.frameworkPort
276
234
  // if the framework doesn't start a server, we use a static one
277
235
  const useStaticServer = !(command && frameworkPort)
278
236
  return {
237
+ baseDirectory: devConfig.base || frameworkSettings.baseDirectory,
279
238
  command,
280
239
  frameworkPort: useStaticServer ? await getStaticServerPort({ devConfig }) : frameworkPort,
281
- dist: devConfig.publish || dist || getDefaultDist(workingDir),
282
- framework,
283
- env,
284
- pollingStrategies,
240
+ dist: devConfig.publish || frameworkSettings.dist || getDefaultDist(workingDir),
241
+ framework: frameworkSettings.framework,
242
+ env: frameworkSettings.env,
243
+ pollingStrategies: frameworkSettings.pollingStrategies || [],
285
244
  useStaticServer,
286
245
  }
287
246
  }
@@ -324,12 +283,15 @@ const detectServerSettings = async (devConfig, flags, command) => {
324
283
  // this is the default CLI behavior
325
284
 
326
285
  const runDetection = !hasCommandAndTargetPort(devConfig)
327
- const frameworkSettings = runDetection ? await detectFrameworkSettings(command) : undefined
286
+ const frameworkSettings = runDetection
287
+ ? getSettingsFromDetectedSettings(await detectFrameworkSettings(command, 'dev'))
288
+ : undefined
328
289
  if (frameworkSettings === undefined && runDetection) {
329
290
  log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
330
291
  settings = await handleStaticServer({ flags, devConfig, workingDir: command.workingDir })
331
292
  } else {
332
293
  validateFrameworkConfig({ devConfig })
294
+
333
295
  settings = await mergeSettings({ devConfig, frameworkSettings, workingDir: command.workingDir })
334
296
  }
335
297
 
@@ -371,29 +333,6 @@ const detectServerSettings = async (devConfig, flags, command) => {
371
333
  }
372
334
  }
373
335
 
374
- const filterSettings = function (scriptInquirerOptions, input) {
375
- const filterOptions = scriptInquirerOptions.map((scriptInquirerOption) => scriptInquirerOption.name)
376
- // TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
377
- // eslint-disable-next-line unicorn/no-array-method-this-argument
378
- const filteredSettings = fuzzy.filter(input, filterOptions)
379
- const filteredSettingNames = new Set(
380
- filteredSettings.map((filteredSetting) => (input ? filteredSetting.string : filteredSetting)),
381
- )
382
- return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name))
383
- }
384
-
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
- }))
395
- }
396
-
397
336
  /**
398
337
  * Returns a copy of the provided config with any plugins provided by the
399
338
  * server settings
@@ -19,12 +19,13 @@ const FRAMEWORK_PORT_TIMEOUT = 6e5
19
19
  * Start a static server if the `useStaticServer` is provided or a framework specific server
20
20
  * @param {object} config
21
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 {
@@ -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']))
@@ -375,7 +375,6 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
375
375
  proxy.before('web', 'stream', (req) => {
376
376
  // See https://github.com/http-party/node-http-proxy/issues/1219#issuecomment-511110375
377
377
  if (req.headers.expect) {
378
- // eslint-disable-next-line no-underscore-dangle
379
378
  req.__expectHeader = req.headers.expect
380
379
  delete req.headers.expect
381
380
  }
@@ -402,9 +401,7 @@ const initializeProxy = async function ({ configPath, distDir, env, host, port,
402
401
  handleProxyRequest(req, proxyReq)
403
402
  }
404
403
 
405
- // eslint-disable-next-line no-underscore-dangle
406
404
  if (req.__expectHeader) {
407
- // eslint-disable-next-line no-underscore-dangle
408
405
  proxyReq.setHeader('Expect', req.__expectHeader)
409
406
  }
410
407
  if (req.originalBody) {
@@ -7,6 +7,7 @@ import fetch from 'node-fetch'
7
7
  const GITHUB = 'GitHub'
8
8
 
9
9
  /**
10
+ * @param {string} _url
10
11
  * Takes a url like https://github.com/netlify-labs/all-the-functions/tree/master/functions/9-using-middleware
11
12
  * and returns https://api.github.com/repos/netlify-labs/all-the-functions/contents/functions/9-using-middleware
12
13
  */
@@ -36,6 +37,9 @@ const getRepoURLContents = async function (repoHost, ownerAndRepo, contentsPath)
36
37
  throw new Error('unsupported host ', repoHost)
37
38
  }
38
39
 
40
+ /**
41
+ * @param {string} _url
42
+ */
39
43
  export const validateRepoURL = function (_url) {
40
44
  // TODO: use `url.URL()` instead
41
45
  // eslint-disable-next-line n/no-deprecated-api
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
  import { promises as fs } from 'fs'
3
- import path from 'path'
3
+ import path, { join } from 'path'
4
4
 
5
5
  import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from '../lib/edge-functions/consts.mjs'
6
6
  import { getPathInProject } from '../lib/settings.mjs'
@@ -14,12 +14,11 @@ const netlifyBuildPromise = import('@netlify/build')
14
14
  /**
15
15
  * Copies `netlify.toml`, if one is defined, into the `.netlify` internal
16
16
  * directory and returns the path to its new location.
17
- * @param {object} config
18
- * @param {string} config.configPath
19
- * @param {string} config.siteRoot
17
+ * @param {string} configPath
18
+ * @param {string} destinationFolder The folder where it should be copied to either the root of the repo or a package inside a monorepo
20
19
  */
21
- const copyConfig = async ({ configPath, siteRoot }) => {
22
- const newConfigPath = path.resolve(siteRoot, getPathInProject(['netlify.toml']))
20
+ const copyConfig = async (configPath, destinationFolder) => {
21
+ const newConfigPath = path.resolve(destinationFolder, getPathInProject(['netlify.toml']))
23
22
 
24
23
  try {
25
24
  await fs.copyFile(configPath, newConfigPath)
@@ -44,75 +43,51 @@ const cleanInternalDirectory = async (basePath) => {
44
43
  }
45
44
 
46
45
  /**
47
- *
48
- * @param {object} config
49
- * @param {string} config.projectDir
50
- * @param {*} config.cachedConfig
51
- * @param {object} config.options
52
- * @param {string} config.options.configPath
53
- * @param {*} config.options.context
54
- * @param {string=} config.options.cwd
55
- * @param {boolean} config.options.debug
56
- * @param {boolean} config.options.dry
57
- * @param {boolean} config.options.offline
58
- * @param {boolean} config.options.quiet
59
- * @param {boolean} config.options.saveConfig
46
+ * @param {object} params
47
+ * @param {import('../commands/base-command.mjs').default} params.command
48
+ * @param {import('../commands/base-command.mjs').default} params.command
49
+ * @param {*} params.options The flags of the command
50
+ * @param {import('./types.js').ServerSettings} params.settings
51
+ * @param {NodeJS.ProcessEnv} [params.env]
52
+ * @param {'build' | 'dev'} [params.timeline]
60
53
  * @returns
61
54
  */
62
- const getBuildOptions = ({
63
- cachedConfig,
64
- options: { configPath, context, debug, dry, offline, quiet, saveConfig },
65
- projectDir,
66
- }) => ({
67
- cachedConfig,
68
- configPath,
69
- siteId: cachedConfig.siteInfo.id,
70
- token: cachedConfig.token,
71
- dry,
72
- debug,
73
- context,
74
- mode: 'cli',
75
- telemetry: false,
76
- buffer: false,
77
- offline,
78
- cwd: projectDir,
79
- quiet,
80
- saveConfig,
81
- })
55
+ export const runNetlifyBuild = async ({ command, env = {}, options, settings, timeline = 'build' }) => {
56
+ const { cachedConfig, site } = command.netlify
82
57
 
83
- /**
84
- *
85
- * @param {object} config
86
- * @param {*} config.cachedConfig
87
- * @param {NodeJS.ProcessEnv} config.env
88
- * @param {*} config.options The flags of the command
89
- * @param {string} config.projectDir
90
- * @param {import('./types.js').ServerSettings} config.settings
91
- * @param {*} config.site
92
- * @param {'build' | 'dev'} config.timeline
93
- * @returns
94
- */
95
- export const runNetlifyBuild = async ({
96
- cachedConfig,
97
- env,
98
- options,
99
- projectDir,
100
- settings,
101
- site,
102
- timeline = 'build',
103
- }) => {
104
58
  const { default: buildSite, startDev } = await netlifyBuildPromise
105
- const sharedOptions = getBuildOptions({
106
- projectDir,
59
+
60
+ const sharedOptions = {
107
61
  cachedConfig,
108
- options,
109
- })
62
+ configPath: cachedConfig.configPath,
63
+ siteId: cachedConfig.siteInfo.id,
64
+ token: cachedConfig.token,
65
+ dry: options.dry,
66
+ debug: options.debug,
67
+ context: options.context,
68
+ mode: 'cli',
69
+ telemetry: false,
70
+ buffer: false,
71
+ offline: options.offline,
72
+ cwd: cachedConfig.buildDir,
73
+ quiet: options.quiet,
74
+ saveConfig: options.saveConfig,
75
+ }
76
+
110
77
  const devCommand = async (settingsOverrides = {}) => {
78
+ let cwd = command.workingDir
79
+
80
+ if (command.project.workspace?.packages.length) {
81
+ console.log('packages', settings.baseDirectory)
82
+ cwd = join(command.project.jsWorkspaceRoot, settings.baseDirectory || '')
83
+ }
84
+
111
85
  const { ipVersion } = await startFrameworkServer({
112
86
  settings: {
113
87
  ...settings,
114
88
  ...settingsOverrides,
115
89
  },
90
+ cwd,
116
91
  })
117
92
 
118
93
  settings.frameworkHost = ipVersion === 6 ? '::1' : '127.0.0.1'
@@ -125,7 +100,7 @@ export const runNetlifyBuild = async ({
125
100
 
126
101
  // Copy `netlify.toml` into the internal directory. This will be the new
127
102
  // location of the config file for the duration of the command.
128
- const tempConfigPath = await copyConfig({ configPath: cachedConfig.configPath, siteRoot: site.root })
103
+ const tempConfigPath = await copyConfig(cachedConfig.configPath, command.workingDir)
129
104
  const buildSiteOptions = {
130
105
  ...sharedOptions,
131
106
  outputConfigPath: tempConfigPath,
@@ -40,10 +40,14 @@ const cleanupBeforeExit = async ({ exitCode }) => {
40
40
  /**
41
41
  * Run a command and pipe stdout, stderr and stdin
42
42
  * @param {string} command
43
- * @param {NodeJS.ProcessEnv} env
43
+ * @param {object} options
44
+ * @param {import('ora').Ora|null} [options.spinner]
45
+ * @param {NodeJS.ProcessEnv} [options.env]
46
+ * @param {string} [options.cwd]
44
47
  * @returns {execa.ExecaChildProcess<string>}
45
48
  */
46
- export const runCommand = (command, env = {}, spinner = null) => {
49
+ export const runCommand = (command, options = {}) => {
50
+ const { cwd, env = {}, spinner = null } = options
47
51
  const commandProcess = execa.command(command, {
48
52
  preferLocal: true,
49
53
  // we use reject=false to avoid rejecting synchronously when the command doesn't exist
@@ -55,6 +59,7 @@ export const runCommand = (command, env = {}, spinner = null) => {
55
59
  },
56
60
  // windowsHide needs to be false for child process to terminate properly on Windows
57
61
  windowsHide: false,
62
+ cwd,
58
63
  })
59
64
 
60
65
  // This ensures that an active spinner stays at the bottom of the commandline
@@ -86,8 +91,9 @@ export const runCommand = (command, env = {}, spinner = null) => {
86
91
  const [commandWithoutArgs] = command.split(' ')
87
92
  if (result.failed && isNonExistingCommandError({ command: commandWithoutArgs, error: result })) {
88
93
  log(
89
- NETLIFYDEVERR,
90
- `Failed running command: ${command}. Please verify ${chalk.magenta(`'${commandWithoutArgs}'`)} exists`,
94
+ `\n\n${NETLIFYDEVERR} Failed running command: ${command}. Please verify ${chalk.magenta(
95
+ `'${commandWithoutArgs}'`,
96
+ )} exists`,
91
97
  )
92
98
  } else {
93
99
  const errorMessage = result.failed