netlify-cli 15.9.1-rc.0 → 15.10.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 (36) hide show
  1. package/npm-shrinkwrap.json +236 -738
  2. package/package.json +6 -5
  3. package/src/commands/base-command.mjs +59 -195
  4. package/src/commands/deploy/deploy.mjs +9 -21
  5. package/src/commands/dev/dev.mjs +15 -21
  6. package/src/commands/functions/functions-create.mjs +3 -0
  7. package/src/commands/functions/functions-invoke.mjs +5 -8
  8. package/src/commands/init/init.mjs +1 -1
  9. package/src/commands/link/link.mjs +5 -5
  10. package/src/commands/serve/serve.mjs +5 -11
  11. package/src/commands/sites/sites-create-template.mjs +1 -1
  12. package/src/commands/sites/sites-create.mjs +1 -1
  13. package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
  14. package/src/lib/edge-functions/bootstrap.mjs +1 -1
  15. package/src/lib/edge-functions/headers.mjs +1 -0
  16. package/src/lib/edge-functions/internal.mjs +3 -5
  17. package/src/lib/edge-functions/proxy.mjs +4 -27
  18. package/src/lib/functions/runtimes/js/index.mjs +1 -1
  19. package/src/lib/functions/runtimes/js/worker.mjs +1 -1
  20. package/src/lib/spinner.mjs +1 -1
  21. package/src/utils/command-helpers.mjs +7 -16
  22. package/src/utils/detect-server-settings.mjs +198 -147
  23. package/src/utils/framework-server.mjs +2 -2
  24. package/src/utils/functions/functions.mjs +0 -7
  25. package/src/utils/get-repo-data.mjs +6 -5
  26. package/src/utils/init/config-github.mjs +2 -2
  27. package/src/utils/init/config-manual.mjs +7 -24
  28. package/src/utils/init/frameworks.mjs +23 -0
  29. package/src/utils/init/utils.mjs +63 -62
  30. package/src/utils/proxy-server.mjs +4 -7
  31. package/src/utils/proxy.mjs +0 -4
  32. package/src/utils/run-build.mjs +7 -58
  33. package/src/utils/shell.mjs +3 -14
  34. package/src/utils/state-config.mjs +1 -5
  35. package/src/utils/static-server.mjs +0 -4
  36. package/src/utils/build-info.mjs +0 -19
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "15.9.1-rc.0",
4
+ "version": "15.10.0",
5
5
  "author": "Netlify Inc.",
6
6
  "type": "module",
7
7
  "engines": {
@@ -44,15 +44,15 @@
44
44
  "dependencies": {
45
45
  "@bugsnag/js": "7.20.2",
46
46
  "@fastify/static": "6.10.2",
47
- "@netlify/build": "29.17.1",
48
- "@netlify/build-info": "7.7.1",
47
+ "@netlify/build": "29.17.3",
48
+ "@netlify/build-info": "7.7.3",
49
49
  "@netlify/config": "20.6.4",
50
- "@netlify/edge-bundler": "8.16.4",
50
+ "@netlify/edge-bundler": "8.17.1",
51
+ "@netlify/framework-info": "9.8.10",
51
52
  "@netlify/local-functions-proxy": "1.1.1",
52
53
  "@netlify/serverless-functions-api": "1.5.2",
53
54
  "@netlify/zip-it-and-ship-it": "9.13.1",
54
55
  "@octokit/rest": "19.0.13",
55
- "@skn0tt/lambda-local": "2.0.3",
56
56
  "ansi-escapes": "6.2.0",
57
57
  "ansi-styles": "6.2.1",
58
58
  "ansi-to-html": "0.7.2",
@@ -107,6 +107,7 @@
107
107
  "isexe": "2.0.0",
108
108
  "jsonwebtoken": "9.0.1",
109
109
  "jwt-decode": "3.1.2",
110
+ "lambda-local": "2.1.1",
110
111
  "listr": "0.14.3",
111
112
  "locate-path": "7.2.0",
112
113
  "lodash": "4.17.21",
@@ -1,17 +1,13 @@
1
1
  // @ts-check
2
- import { join, resolve } from 'path'
3
2
  import process from 'process'
4
3
  import { format } from 'util'
5
4
 
6
- import { DefaultLogger, Project } from '@netlify/build-info'
5
+ import { Project } from '@netlify/build-info'
7
6
  // eslint-disable-next-line import/extensions, n/no-missing-import
8
- import { NodeFS, NoopLogger } from '@netlify/build-info/node'
7
+ import { NodeFS } from '@netlify/build-info/node'
9
8
  import { resolveConfig } from '@netlify/config'
10
9
  import { Command, Option } from 'commander'
11
10
  import debug from 'debug'
12
- import execa from 'execa'
13
- import inquirer from 'inquirer'
14
- import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
15
11
  import merge from 'lodash/merge.js'
16
12
  import { NetlifyAPI } from 'netlify'
17
13
 
@@ -34,10 +30,8 @@ import getGlobalConfig from '../utils/get-global-config.mjs'
34
30
  import { getSiteByName } from '../utils/get-site.mjs'
35
31
  import openBrowser from '../utils/open-browser.mjs'
36
32
  import StateConfig from '../utils/state-config.mjs'
37
- import { identify, reportError, track } from '../utils/telemetry/index.mjs'
33
+ import { identify, track } from '../utils/telemetry/index.mjs'
38
34
 
39
- // load the autocomplete plugin
40
- inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
41
35
  // Netlify CLI client id. Lives in bot@netlify.com
42
36
  // TODO: setup client for multiple environments
43
37
  const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
@@ -70,73 +64,30 @@ const getDuration = function (startTime) {
70
64
  }
71
65
 
72
66
  /**
73
- * Retrieves a workspace package based of the filter flag that is provided.
74
- * If the filter flag does not match a workspace package or is not defined then it will prompt with an autocomplete to select a package
75
- * @param {Project} project
76
- * @param {string=} filter
77
- * @returns {Promise<string>}
67
+ * The netlify object inside each command with the state
68
+ * @typedef NetlifyOptions
69
+ * @type {object}
70
+ * @property {import('netlify').NetlifyAPI} api
71
+ * @property {*} repositoryRoot
72
+ * @property {object} site
73
+ * @property {*} site.root
74
+ * @property {*} site.configPath
75
+ * @property {*} site.id
76
+ * @property {*} siteInfo
77
+ * @property {*} config
78
+ * @property {*} cachedConfig
79
+ * @property {*} globalConfig
80
+ * @property {import('../../utils/state-config.mjs').default} state,
78
81
  */
79
- async function selectWorkspace(project, filter) {
80
- const selected = project.workspace?.packages.find((pkg) => {
81
- if (project.relativeBaseDirectory && pkg.path.startsWith(project.relativeBaseDirectory)) {
82
- return true
83
- }
84
- return pkg.name === filter
85
- })
86
-
87
- if (!selected) {
88
- log()
89
- log(chalk.cyan(`We've detected multiple sites inside your repository!`))
90
-
91
- const { result } = await inquirer.prompt({
92
- name: 'result',
93
- type: 'autocomplete',
94
- message: 'Select a site you want to work with',
95
- source: (/** @type {string} */ _, input = '') =>
96
- (project.workspace?.packages || [])
97
- .filter((pkg) => pkg.path.includes(input))
98
- .map((pkg) => ({
99
- name: `${pkg.name && `${chalk.bold(pkg.name)}`} ${pkg.path} ${
100
- pkg.name && chalk.dim(`--filter ${pkg.name}`)
101
- }`,
102
- value: pkg.path,
103
- })),
104
- })
105
-
106
- return result
107
- }
108
- return selected.path
109
- }
110
82
 
111
83
  /** Base command class that provides tracking and config initialization */
112
84
  export default class BaseCommand extends Command {
113
- /**
114
- * The netlify object inside each command with the state
115
- * @type {import('./types.js').NetlifyOptions}
116
- */
85
+ /** @type {NetlifyOptions} */
117
86
  netlify
118
87
 
119
88
  /** @type {{ startTime: bigint, payload?: any}} */
120
89
  analytics = { startTime: process.hrtime.bigint() }
121
90
 
122
- /** @type {Project} */
123
- project
124
-
125
- /**
126
- * The working directory that is used for reading the `netlify.toml` file and storing the state.
127
- * In a monorepo context this must not be the process working directory and can be an absolute path to the
128
- * Package/Site that should be worked in.
129
- */
130
- // here we actually want to disable the lint rule as it's value is set
131
- // eslint-disable-next-line workspace/no-process-cwd
132
- workingDir = process.cwd()
133
-
134
- /**
135
- * The current workspace package we should execute the commands in
136
- * @type {string|undefined}
137
- */
138
- workspacePackage
139
-
140
91
  /**
141
92
  * IMPORTANT this function will be called for each command!
142
93
  * Don't do anything expensive in there.
@@ -179,15 +130,7 @@ export default class BaseCommand extends Command {
179
130
  process.env.HTTP_PROXY || process.env.HTTPS_PROXY,
180
131
  )
181
132
  .option('--debug', 'Print debugging information')
182
- .option('--config <configFilePath>', 'Custom path to a netlify configuration file')
183
- .option(
184
- '--filter <app>',
185
- 'Optional name of an application to run the command in.\nThis option is needed for working in Monorepos',
186
- )
187
133
  .hook('preAction', async (_parentCommand, actionCommand) => {
188
- if (actionCommand.opts()?.debug) {
189
- process.env.DEBUG = '*'
190
- }
191
134
  debug(`${name}:preAction`)('start')
192
135
  this.analytics = { startTime: process.hrtime.bigint() }
193
136
  // @ts-ignore cannot type actionCommand as BaseCommand
@@ -206,7 +149,7 @@ export default class BaseCommand extends Command {
206
149
  return this
207
150
  }
208
151
 
209
- /** @type {string[]} The examples list for the command (used inside doc generation and help page) */
152
+ /** The examples list for the command (used inside doc generation and help page) */
210
153
  examples = []
211
154
 
212
155
  /**
@@ -229,7 +172,7 @@ export default class BaseCommand extends Command {
229
172
  const term =
230
173
  this.name() === 'netlify'
231
174
  ? `${HELP_$} ${command.name()} [COMMAND]`
232
- : `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`
175
+ : `${HELP_$} ${command.parent.name()} ${command.name()} ${command.usage()}`
233
176
 
234
177
  return padLeft(term, HELP_INDENT_WIDTH)
235
178
  }
@@ -394,11 +337,6 @@ export default class BaseCommand extends Command {
394
337
  }
395
338
  }
396
339
 
397
- /**
398
- *
399
- * @param {string|undefined} tokenFromFlag
400
- * @returns
401
- */
402
340
  async authenticate(tokenFromFlag) {
403
341
  const [token] = await getToken(tokenFromFlag)
404
342
  if (token) {
@@ -468,10 +406,6 @@ export default class BaseCommand extends Command {
468
406
  return accessToken
469
407
  }
470
408
 
471
- /**
472
- * Adds some data to the analytics payload
473
- * @param {Record<string, unknown>} payload
474
- */
475
409
  setAnalyticsPayload(payload) {
476
410
  const newPayload = { ...this.analytics.payload, ...payload }
477
411
  this.analytics = { ...this.analytics, payload: newPayload }
@@ -484,45 +418,12 @@ export default class BaseCommand extends Command {
484
418
  */
485
419
  async init(actionCommand) {
486
420
  debug(`${actionCommand.name()}:init`)('start')
487
- const flags = actionCommand.opts()
488
- // here we actually want to use the process.cwd as we are setting the workingDir
489
- // eslint-disable-next-line workspace/no-process-cwd
490
- this.workingDir = flags.cwd || process.cwd()
491
-
492
- // ==================================================
493
- // Create a Project and run the Heuristics to detect
494
- // if we are run inside a monorepo or not.
495
- // ==================================================
496
-
497
- // retrieve the repository root
498
- const rootDir = await getRepositoryRoot()
499
- // Get framework, add to analytics payload for every command, if a framework is set
500
- const fs = new NodeFS()
501
- // disable logging inside the project and FS if not in debug mode
502
- fs.logger = actionCommand.opts()?.debug ? new DefaultLogger('debug') : new NoopLogger()
503
- this.project = new Project(fs, this.workingDir, rootDir)
504
- .setEnvironment(process.env)
505
- .setNodeVersion(process.version)
506
- // eslint-disable-next-line promise/prefer-await-to-callbacks
507
- .setReportFn((err, reportConfig) => {
508
- reportError(err, {
509
- severity: reportConfig?.severity || 'error',
510
- metadata: reportConfig?.metadata,
511
- })
512
- })
513
- const frameworks = await this.project.detectFrameworks()
514
- // check if we have detected multiple projects inside which one we have to perform our operations.
515
- // only ask to select one if on the workspace root
516
- if (this.project.workspace?.packages.length && this.project.workspace.isRoot) {
517
- this.workspacePackage = await selectWorkspace(this.project, actionCommand.opts().filter)
518
- this.workingDir = join(this.project.jsWorkspaceRoot, this.workspacePackage)
519
- }
421
+ const options = actionCommand.opts()
422
+ const cwd = options.cwd || process.cwd()
423
+ // Get site id & build state
424
+ const state = new StateConfig(cwd)
520
425
 
521
- // ==================================================
522
- // Retrieve Site id and build state from the state.json
523
- // ==================================================
524
- const state = new StateConfig(this.workingDir)
525
- const [token] = await getToken(flags.auth)
426
+ const [token] = await getToken(options.auth)
526
427
 
527
428
  const apiUrlOpts = {
528
429
  userAgent: USER_AGENT,
@@ -536,23 +437,12 @@ export default class BaseCommand extends Command {
536
437
  process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
537
438
  }
538
439
 
539
- // ==================================================
540
- // Start retrieving the configuration through the
541
- // configuration file and the API
542
- // ==================================================
543
- const cachedConfig = await actionCommand.getConfig({
544
- cwd: this.workingDir,
545
- // The config flag needs to be resolved from the actual process working directory
546
- configFilePath: flags.config ? resolve(flags.config) : undefined,
547
- state,
548
- token,
549
- ...apiUrlOpts,
550
- })
440
+ const cachedConfig = await actionCommand.getConfig({ cwd, state, token, ...apiUrlOpts })
551
441
  const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
552
442
  const normalizedConfig = normalizeConfig(config)
553
443
  const agent = await getAgent({
554
- httpProxy: flags.httpProxy,
555
- certificateFile: flags.httpProxyCertificateFilename,
444
+ httpProxy: options.httpProxy,
445
+ certificateFile: options.httpProxyCertificateFilename,
556
446
  })
557
447
  const apiOpts = { ...apiUrlOpts, agent }
558
448
  const api = new NetlifyAPI(token || '', apiOpts)
@@ -564,30 +454,28 @@ export default class BaseCommand extends Command {
564
454
  // options.site as a site name (and not just site id) was introduced for the deploy command, so users could
565
455
  // deploy by name along with by id
566
456
  let siteData = siteInfo
567
- if (!siteData.url && flags.site) {
568
- siteData = await getSiteByName(api, flags.site)
457
+ if (!siteData.url && options.site) {
458
+ siteData = await getSiteByName(api, options.site)
569
459
  }
570
460
 
571
461
  const globalConfig = await getGlobalConfig()
572
462
 
573
- // ==================================================
574
- // Perform analytics reporting
575
- // ==================================================
463
+ // Get framework, add to analytics payload for every command, if a framework is set
464
+ const fs = new NodeFS()
465
+ const project = new Project(fs, buildDir)
466
+ const frameworks = await project.detectFrameworks()
467
+
576
468
  const frameworkIDs = frameworks?.map((framework) => framework.id)
469
+
577
470
  if (frameworkIDs?.length !== 0) {
578
471
  this.setAnalyticsPayload({ frameworks: frameworkIDs })
579
472
  }
473
+
580
474
  this.setAnalyticsPayload({
581
- monorepo: Boolean(this.project.workspace),
582
- packageManager: this.project.packageManager?.name,
583
- buildSystem: this.project.buildSystems.map(({ id }) => id),
475
+ packageManager: project.packageManager?.name,
476
+ buildSystem: project.buildSystems.map(({ id }) => id),
584
477
  })
585
478
 
586
- // set the project and the netlify api object on the command,
587
- // to be accessible inside each command.
588
- actionCommand.project = this.project
589
- actionCommand.workingDir = this.workingDir
590
- actionCommand.workspacePackage = this.workspacePackage
591
479
  actionCommand.netlify = {
592
480
  // api methods
593
481
  api,
@@ -620,36 +508,26 @@ export default class BaseCommand extends Command {
620
508
 
621
509
  /**
622
510
  * Find and resolve the Netlify configuration
623
- * @param {object} config
624
- * @param {string} config.cwd
625
- * @param {string|null=} config.token
626
- * @param {*} config.state
627
- * @param {boolean=} config.offline
628
- * @param {string=} config.configFilePath An optional path to the netlify configuration file e.g. netlify.toml
629
- * @param {string=} config.repositoryRoot
630
- * @param {string=} config.host
631
- * @param {string=} config.pathPrefix
632
- * @param {string=} config.scheme
633
- * @returns {ReturnType<typeof resolveConfig>}
511
+ * @param {*} config
512
+ * @returns {ReturnType<import('@netlify/config/src/main')>}
634
513
  */
635
514
  async getConfig(config) {
636
- // the flags that are passed to the command like `--debug` or `--offline`
637
- const flags = this.opts()
515
+ const options = this.opts()
516
+ const { cwd, host, offline = options.offline, pathPrefix, scheme, state, token } = config
638
517
 
639
518
  try {
640
519
  return await resolveConfig({
641
- config: config.configFilePath,
642
- repositoryRoot: config.repositoryRoot,
643
- cwd: config.cwd,
644
- context: flags.context || process.env.CONTEXT || this.getDefaultContext(),
645
- debug: flags.debug,
646
- siteId: flags.siteId || (typeof flags.site === 'string' && flags.site) || config.state.get('siteId'),
647
- token: config.token,
520
+ config: options.config,
521
+ cwd,
522
+ context: options.context || process.env.CONTEXT || this.getDefaultContext(),
523
+ debug: this.opts().debug,
524
+ siteId: options.siteId || (typeof options.site === 'string' && options.site) || state.get('siteId'),
525
+ token,
648
526
  mode: 'cli',
649
- host: config.host,
650
- pathPrefix: config.pathPrefix,
651
- scheme: config.scheme,
652
- offline: config.offline ?? flags.offline,
527
+ host,
528
+ pathPrefix,
529
+ scheme,
530
+ offline,
653
531
  siteFeatureFlagPrefix: 'cli',
654
532
  })
655
533
  } catch (error_) {
@@ -661,17 +539,17 @@ export default class BaseCommand extends Command {
661
539
  //
662
540
  // @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
663
541
  // the option to say that we don't need API data.)
664
- if (isUserError && !config.offline && config.token) {
665
- if (flags.debug) {
542
+ if (isUserError && !offline && token) {
543
+ if (this.opts().debug) {
666
544
  error(error_, { exit: false })
667
545
  warn('Failed to resolve config, falling back to offline resolution')
668
546
  }
669
- // recursive call with trying to resolve offline
670
- return this.getConfig({ ...config, offline: true })
547
+ return this.getConfig({ cwd, offline: true, state, token })
671
548
  }
672
549
 
673
550
  const message = isUserError ? error_.message : error_.stack
674
- error(message, { exit: true })
551
+ console.error(message)
552
+ exit(1)
675
553
  }
676
554
  }
677
555
 
@@ -680,7 +558,7 @@ export default class BaseCommand extends Command {
680
558
  * set. The default context is `dev` most of the time, but some commands may
681
559
  * wish to override that.
682
560
  *
683
- * @returns {'production' | 'dev'}
561
+ * @returns {string}
684
562
  */
685
563
  getDefaultContext() {
686
564
  if (this.name() === 'serve') {
@@ -690,17 +568,3 @@ export default class BaseCommand extends Command {
690
568
  return 'dev'
691
569
  }
692
570
  }
693
-
694
- /**
695
- * Retrieves the repository root through a git command.
696
- * Returns undefined if not a git project.
697
- * @returns {Promise<string|undefined>}
698
- */
699
- async function getRepositoryRoot() {
700
- try {
701
- const res = await execa('git', ['rev-parse', '--show-toplevel'], { preferLocal: true })
702
- return res.stdout
703
- } catch {
704
- // noop
705
- }
706
- }
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  import { stat } from 'fs/promises'
3
3
  import { basename, resolve } from 'path'
4
- import { env } from 'process'
4
+ import { cwd, env } from 'process'
5
5
 
6
6
  import { runCoreSteps } from '@netlify/build'
7
7
  import { restoreConfig, updateConfig } from '@netlify/config'
@@ -64,17 +64,16 @@ const triggerDeploy = async ({ api, options, siteData, siteId }) => {
64
64
  /**
65
65
  * g
66
66
  * @param {object} config
67
- * @param {string} config.workingDir The process working directory
68
67
  * @param {object} config.config
69
68
  * @param {import('commander').OptionValues} config.options
70
69
  * @param {object} config.site
71
70
  * @param {object} config.siteData
72
71
  * @returns {Promise<string>}
73
72
  */
74
- const getDeployFolder = async ({ config, options, site, siteData, workingDir }) => {
73
+ const getDeployFolder = async ({ config, options, site, siteData }) => {
75
74
  let deployFolder
76
75
  if (options.dir) {
77
- deployFolder = resolve(workingDir, options.dir)
76
+ deployFolder = resolve(cwd(), options.dir)
78
77
  } else if (config?.build?.publish) {
79
78
  deployFolder = resolve(site.root, config.build.publish)
80
79
  } else if (siteData?.build_settings?.dir) {
@@ -83,14 +82,14 @@ const getDeployFolder = async ({ config, options, site, siteData, workingDir })
83
82
 
84
83
  if (!deployFolder) {
85
84
  log('Please provide a publish directory (e.g. "public" or "dist" or "."):')
86
- log(workingDir)
85
+ log(cwd())
87
86
  const { promptPath } = await inquirer.prompt([
88
87
  {
89
88
  type: 'input',
90
89
  name: 'promptPath',
91
90
  message: 'Publish directory',
92
91
  default: '.',
93
- filter: (input) => resolve(workingDir, input),
92
+ filter: (input) => resolve(cwd(), input),
94
93
  },
95
94
  ])
96
95
  deployFolder = promptPath
@@ -129,15 +128,14 @@ const validateDeployFolder = async ({ deployFolder }) => {
129
128
  * @param {import('commander').OptionValues} config.options
130
129
  * @param {object} config.site
131
130
  * @param {object} config.siteData
132
- * @param {string} config.workingDir // The process working directory
133
131
  * @returns {string}
134
132
  */
135
- const getFunctionsFolder = ({ config, options, site, siteData, workingDir }) => {
133
+ const getFunctionsFolder = ({ config, options, site, siteData }) => {
136
134
  let functionsFolder
137
135
  // Support "functions" and "Functions"
138
136
  const funcConfig = config.functionsDirectory
139
137
  if (options.functions) {
140
- functionsFolder = resolve(workingDir, options.functions)
138
+ functionsFolder = resolve(cwd(), options.functions)
141
139
  } else if (funcConfig) {
142
140
  functionsFolder = resolve(site.root, funcConfig)
143
141
  } else if (siteData?.build_settings?.functions_dir) {
@@ -180,21 +178,12 @@ const validateFolders = async ({ deployFolder, functionsFolder }) => {
180
178
  return { deployFolderStat, functionsFolderStat }
181
179
  }
182
180
 
183
- /**
184
- * @param {object} config
185
- * @param {string} config.deployFolder
186
- * @param {*} config.site
187
- * @returns
188
- */
189
181
  const getDeployFilesFilter = ({ deployFolder, site }) => {
190
182
  // site.root === deployFolder can happen when users run `netlify deploy --dir .`
191
183
  // in that specific case we don't want to publish the repo node_modules
192
184
  // when site.root !== deployFolder the behaviour matches our buildbot
193
185
  const skipNodeModules = site.root === deployFolder
194
186
 
195
- /**
196
- * @param {string} filename
197
- */
198
187
  return (filename) => {
199
188
  if (filename == null) {
200
189
  return false
@@ -507,7 +496,6 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
507
496
  * @param {import('../base-command.mjs').default} command
508
497
  */
509
498
  const deploy = async (options, command) => {
510
- const { workingDir } = command
511
499
  const { api, site, siteInfo } = command.netlify
512
500
  const alias = options.alias || options.branch
513
501
 
@@ -578,8 +566,8 @@ const deploy = async (options, command) => {
578
566
  })
579
567
  const config = newConfig || command.netlify.config
580
568
 
581
- const deployFolder = await getDeployFolder({ workingDir, options, config, site, siteData })
582
- const functionsFolder = getFunctionsFolder({ workingDir, options, config, site, siteData })
569
+ const deployFolder = await getDeployFolder({ options, config, site, siteData })
570
+ const functionsFolder = getFunctionsFolder({ options, config, site, siteData })
583
571
  const { configPath } = site
584
572
  const edgeFunctionsConfig = command.netlify.config.edge_functions
585
573
 
@@ -9,6 +9,7 @@ import { printBanner } from '../../utils/banner.mjs'
9
9
  import {
10
10
  BANG,
11
11
  chalk,
12
+ exit,
12
13
  log,
13
14
  NETLIFYDEV,
14
15
  NETLIFYDEVERR,
@@ -34,7 +35,7 @@ import { createDevExecCommand } from './dev-exec.mjs'
34
35
  * @param {object} config
35
36
  * @param {*} config.api
36
37
  * @param {import('commander').OptionValues} config.options
37
- * @param {import('../../utils/types.js').ServerSettings} config.settings
38
+ * @param {*} config.settings
38
39
  * @param {*} config.site
39
40
  * @param {*} config.state
40
41
  * @returns
@@ -67,9 +68,6 @@ const handleLiveTunnel = async ({ api, options, settings, site, state }) => {
67
68
  }
68
69
  }
69
70
 
70
- /**
71
- * @param {string} args
72
- */
73
71
  const validateShortFlagArgs = (args) => {
74
72
  if (args.startsWith('=')) {
75
73
  throw new Error(
@@ -96,10 +94,9 @@ const dev = async (options, command) => {
96
94
  const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
97
95
  config.dev = { ...config.dev }
98
96
  config.build = { ...config.build }
99
- /** @type {import('./types.js').DevConfig} */
97
+ /** @type {import('./types').DevConfig} */
100
98
  const devConfig = {
101
99
  framework: '#auto',
102
- autoLaunch: Boolean(options.open),
103
100
  ...(config.functionsDirectory && { functions: config.functionsDirectory }),
104
101
  ...(config.build.publish && { publish: config.build.publish }),
105
102
  ...config.dev,
@@ -127,17 +124,20 @@ const dev = async (options, command) => {
127
124
  siteInfo,
128
125
  })
129
126
 
130
- /** @type {import('../../utils/types.js').ServerSettings} */
131
- let settings
127
+ /** @type {Partial<import('../../utils/types').ServerSettings>} */
128
+ let settings = {}
132
129
  try {
133
- settings = await detectServerSettings(devConfig, options, command)
130
+ settings = await detectServerSettings(devConfig, options, site.root, {
131
+ site: {
132
+ id: site.id,
133
+ url: siteUrl,
134
+ },
135
+ })
134
136
 
135
137
  cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
136
138
  } catch (error_) {
137
- if (error_ && typeof error_ === 'object' && 'message' in error_) {
138
- log(NETLIFYDEVERR, error_.message)
139
- }
140
- process.exit(1)
139
+ log(NETLIFYDEVERR, error_.message)
140
+ exit(1)
141
141
  }
142
142
 
143
143
  command.setAnalyticsPayload({ live: options.live })
@@ -154,7 +154,6 @@ const dev = async (options, command) => {
154
154
  cachedConfig,
155
155
  options,
156
156
  settings,
157
- projectDir: command.workingDir,
158
157
  site,
159
158
  env: {
160
159
  URL: url,
@@ -189,11 +188,8 @@ const dev = async (options, command) => {
189
188
 
190
189
  // TODO: We should consolidate this with the existing config watcher.
191
190
  const getUpdatedConfig = async () => {
192
- const { config: newConfig } = await command.getConfig({
193
- cwd: command.workingDir,
194
- offline: true,
195
- state,
196
- })
191
+ const cwd = options.cwd || process.cwd()
192
+ const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
197
193
  const normalizedNewConfig = normalizeConfig(newConfig)
198
194
 
199
195
  return normalizedNewConfig
@@ -206,7 +202,6 @@ const dev = async (options, command) => {
206
202
  config,
207
203
  configPath: configPathOverride,
208
204
  debug: options.debug,
209
- projectDir: command.workingDir,
210
205
  env,
211
206
  getUpdatedConfig,
212
207
  inspectSettings,
@@ -253,7 +248,6 @@ export const createDevCommand = (program) => {
253
248
  .argParser((value) => Number.parseInt(value))
254
249
  .hideHelp(true),
255
250
  )
256
- .addOption(new Option('--no-open', 'disables the automatic opening of a browser window'))
257
251
  .option('--target-port <port>', 'port of target app server', (value) => Number.parseInt(value))
258
252
  .option('--framework <name>', 'framework to use. Defaults to #auto which automatically detects a framework')
259
253
  .option('-d ,--dir <path>', 'dir with static files')
@@ -12,6 +12,7 @@ import copyTemplateDirOriginal from 'copy-template-dir'
12
12
  import { findUp } from 'find-up'
13
13
  import fuzzy from 'fuzzy'
14
14
  import inquirer from 'inquirer'
15
+ import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
15
16
  import fetch from 'node-fetch'
16
17
  import ora from 'ora'
17
18
 
@@ -171,6 +172,8 @@ const pickTemplate = async function ({ language: languageFromFlag }, funcType) {
171
172
  language = languageFromPrompt
172
173
  }
173
174
 
175
+ inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
176
+
174
177
  let templatesForLanguage
175
178
 
176
179
  try {