netlify-cli 15.8.1 → 15.9.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/npm-shrinkwrap.json +512 -414
  2. package/package.json +9 -10
  3. package/scripts/postinstall.mjs +8 -8
  4. package/src/commands/base-command.mjs +195 -59
  5. package/src/commands/deploy/deploy.mjs +21 -9
  6. package/src/commands/dev/dev.mjs +24 -16
  7. package/src/commands/functions/functions-build.mjs +2 -2
  8. package/src/commands/functions/functions-create.mjs +0 -3
  9. package/src/commands/functions/functions-invoke.mjs +8 -5
  10. package/src/commands/functions/functions-serve.mjs +2 -1
  11. package/src/commands/init/init.mjs +1 -1
  12. package/src/commands/link/link.mjs +5 -5
  13. package/src/commands/main.mjs +1 -1
  14. package/src/commands/serve/serve.mjs +14 -6
  15. package/src/commands/sites/sites-create-template.mjs +1 -1
  16. package/src/commands/sites/sites-create.mjs +1 -1
  17. package/src/functions-templates/javascript/auth-fetch/package-lock.json +6 -6
  18. package/src/functions-templates/javascript/google-analytics/package-lock.json +6 -6
  19. package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
  20. package/src/functions-templates/typescript/transform-response/{{name}}.ts +0 -1
  21. package/src/lib/completion/generate-autocompletion.mjs +4 -4
  22. package/src/lib/edge-functions/bootstrap.mjs +1 -1
  23. package/src/lib/edge-functions/headers.mjs +1 -0
  24. package/src/lib/edge-functions/internal.mjs +5 -3
  25. package/src/lib/edge-functions/proxy.mjs +39 -5
  26. package/src/lib/edge-functions/registry.mjs +11 -9
  27. package/src/lib/functions/registry.mjs +1 -3
  28. package/src/lib/functions/runtimes/js/builders/zisi.mjs +3 -8
  29. package/src/lib/functions/server.mjs +7 -6
  30. package/src/lib/spinner.mjs +1 -1
  31. package/src/recipes/vscode/index.mjs +1 -1
  32. package/src/utils/build-info.mjs +19 -0
  33. package/src/utils/command-helpers.mjs +16 -7
  34. package/src/utils/deploy/hash-fns.mjs +1 -2
  35. package/src/utils/detect-server-settings.mjs +148 -200
  36. package/src/utils/dev.mjs +1 -0
  37. package/src/utils/execa.mjs +4 -2
  38. package/src/utils/framework-server.mjs +2 -2
  39. package/src/utils/functions/functions.mjs +7 -0
  40. package/src/utils/functions/get-functions.mjs +2 -2
  41. package/src/utils/get-repo-data.mjs +5 -6
  42. package/src/utils/init/config-github.mjs +2 -2
  43. package/src/utils/init/config-manual.mjs +24 -7
  44. package/src/utils/init/utils.mjs +62 -63
  45. package/src/utils/proxy-server.mjs +9 -4
  46. package/src/utils/proxy.mjs +6 -0
  47. package/src/utils/run-build.mjs +58 -7
  48. package/src/utils/shell.mjs +14 -3
  49. package/src/utils/state-config.mjs +5 -1
  50. package/src/utils/static-server.mjs +4 -0
  51. package/src/utils/telemetry/report-error.mjs +8 -4
  52. package/src/utils/init/frameworks.mjs +0 -23
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.8.1",
4
+ "version": "15.9.1-rc.0",
5
5
  "author": "Netlify Inc.",
6
6
  "type": "module",
7
7
  "engines": {
@@ -44,14 +44,13 @@
44
44
  "dependencies": {
45
45
  "@bugsnag/js": "7.20.2",
46
46
  "@fastify/static": "6.10.2",
47
- "@netlify/build": "29.15.2",
48
- "@netlify/build-info": "7.3.4",
49
- "@netlify/config": "20.5.2",
50
- "@netlify/edge-bundler": "8.16.2",
51
- "@netlify/framework-info": "9.8.10",
47
+ "@netlify/build": "29.17.1",
48
+ "@netlify/build-info": "7.7.1",
49
+ "@netlify/config": "20.6.4",
50
+ "@netlify/edge-bundler": "8.16.4",
52
51
  "@netlify/local-functions-proxy": "1.1.1",
53
- "@netlify/serverless-functions-api": "1.5.1",
54
- "@netlify/zip-it-and-ship-it": "9.12.1",
52
+ "@netlify/serverless-functions-api": "1.5.2",
53
+ "@netlify/zip-it-and-ship-it": "9.13.1",
55
54
  "@octokit/rest": "19.0.13",
56
55
  "@skn0tt/lambda-local": "2.0.3",
57
56
  "ansi-escapes": "6.2.0",
@@ -115,7 +114,7 @@
115
114
  "log-update": "5.0.1",
116
115
  "minimist": "1.2.8",
117
116
  "multiparty": "4.2.3",
118
- "netlify": "13.1.9",
117
+ "netlify": "13.1.10",
119
118
  "netlify-headers-parser": "7.1.2",
120
119
  "netlify-redirect-parser": "14.1.3",
121
120
  "netlify-redirector": "0.4.0",
@@ -133,7 +132,7 @@
133
132
  "pump": "3.0.0",
134
133
  "raw-body": "2.5.2",
135
134
  "read-pkg-up": "9.1.0",
136
- "semver": "7.5.3",
135
+ "semver": "7.5.4",
137
136
  "source-map-support": "0.5.21",
138
137
  "strip-ansi-control-characters": "2.0.0",
139
138
  "tabtab": "3.0.2",
@@ -1,5 +1,11 @@
1
1
  import process from 'process'
2
2
 
3
+ import chalk from 'chalk'
4
+
5
+ import { createMainCommand } from '../src/commands/index.mjs'
6
+ // TODO: use destructuring again once the imported file is esm
7
+ import { generateAutocompletion } from '../src/lib/completion/index.mjs'
8
+
3
9
  const id = (message) => message
4
10
 
5
11
  /**
@@ -8,12 +14,10 @@ const id = (message) => message
8
14
  * @param {Array<chalk['Color'] | chalk['Modifiers']>} styles
9
15
  * @returns
10
16
  */
11
- const format = async (message, styles) => {
17
+ const format = (message, styles) => {
12
18
  let func = id
13
19
  try {
14
- // this fails sometimes on outdated npm versions
15
- const chalk = await import('chalk')
16
- func = chalk.default
20
+ func = chalk
17
21
  styles.forEach((style) => {
18
22
  func = func[style]
19
23
  })
@@ -26,10 +30,6 @@ const postInstall = async () => {
26
30
  // as yarn pnp analyzes everything inside the postinstall
27
31
  // yarn pnp executes it out of a .yarn folder .yarn/unplugged/netlify-cli-file-fb026a3a6d/node_modules/netlify-cli/scripts/postinstall.mjs
28
32
  if (!process.argv[1].includes('.yarn')) {
29
- const { createMainCommand } = await import('../src/commands/index.mjs')
30
- // TODO: use destructuring again once the imported file is esm
31
- const { generateAutocompletion } = await import('../src/lib/completion/index.mjs')
32
-
33
33
  // create or update the autocompletion definition
34
34
  const program = createMainCommand()
35
35
  generateAutocompletion(program)
@@ -1,13 +1,17 @@
1
1
  // @ts-check
2
+ import { join, resolve } from 'path'
2
3
  import process from 'process'
3
4
  import { format } from 'util'
4
5
 
5
- import { Project } from '@netlify/build-info'
6
+ import { DefaultLogger, Project } from '@netlify/build-info'
6
7
  // eslint-disable-next-line import/extensions, n/no-missing-import
7
- import { NodeFS } from '@netlify/build-info/node'
8
+ import { NodeFS, NoopLogger } from '@netlify/build-info/node'
8
9
  import { resolveConfig } from '@netlify/config'
9
10
  import { Command, Option } from 'commander'
10
11
  import debug from 'debug'
12
+ import execa from 'execa'
13
+ import inquirer from 'inquirer'
14
+ import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
11
15
  import merge from 'lodash/merge.js'
12
16
  import { NetlifyAPI } from 'netlify'
13
17
 
@@ -30,8 +34,10 @@ import getGlobalConfig from '../utils/get-global-config.mjs'
30
34
  import { getSiteByName } from '../utils/get-site.mjs'
31
35
  import openBrowser from '../utils/open-browser.mjs'
32
36
  import StateConfig from '../utils/state-config.mjs'
33
- import { identify, track } from '../utils/telemetry/index.mjs'
37
+ import { identify, reportError, track } from '../utils/telemetry/index.mjs'
34
38
 
39
+ // load the autocomplete plugin
40
+ inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
35
41
  // Netlify CLI client id. Lives in bot@netlify.com
36
42
  // TODO: setup client for multiple environments
37
43
  const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
@@ -64,30 +70,73 @@ const getDuration = function (startTime) {
64
70
  }
65
71
 
66
72
  /**
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,
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>}
81
78
  */
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
+ }
82
110
 
83
111
  /** Base command class that provides tracking and config initialization */
84
112
  export default class BaseCommand extends Command {
85
- /** @type {NetlifyOptions} */
113
+ /**
114
+ * The netlify object inside each command with the state
115
+ * @type {import('./types.js').NetlifyOptions}
116
+ */
86
117
  netlify
87
118
 
88
119
  /** @type {{ startTime: bigint, payload?: any}} */
89
120
  analytics = { startTime: process.hrtime.bigint() }
90
121
 
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
+
91
140
  /**
92
141
  * IMPORTANT this function will be called for each command!
93
142
  * Don't do anything expensive in there.
@@ -130,7 +179,15 @@ export default class BaseCommand extends Command {
130
179
  process.env.HTTP_PROXY || process.env.HTTPS_PROXY,
131
180
  )
132
181
  .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
+ )
133
187
  .hook('preAction', async (_parentCommand, actionCommand) => {
188
+ if (actionCommand.opts()?.debug) {
189
+ process.env.DEBUG = '*'
190
+ }
134
191
  debug(`${name}:preAction`)('start')
135
192
  this.analytics = { startTime: process.hrtime.bigint() }
136
193
  // @ts-ignore cannot type actionCommand as BaseCommand
@@ -149,7 +206,7 @@ export default class BaseCommand extends Command {
149
206
  return this
150
207
  }
151
208
 
152
- /** The examples list for the command (used inside doc generation and help page) */
209
+ /** @type {string[]} The examples list for the command (used inside doc generation and help page) */
153
210
  examples = []
154
211
 
155
212
  /**
@@ -172,7 +229,7 @@ export default class BaseCommand extends Command {
172
229
  const term =
173
230
  this.name() === 'netlify'
174
231
  ? `${HELP_$} ${command.name()} [COMMAND]`
175
- : `${HELP_$} ${command.parent.name()} ${command.name()} ${command.usage()}`
232
+ : `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`
176
233
 
177
234
  return padLeft(term, HELP_INDENT_WIDTH)
178
235
  }
@@ -337,6 +394,11 @@ export default class BaseCommand extends Command {
337
394
  }
338
395
  }
339
396
 
397
+ /**
398
+ *
399
+ * @param {string|undefined} tokenFromFlag
400
+ * @returns
401
+ */
340
402
  async authenticate(tokenFromFlag) {
341
403
  const [token] = await getToken(tokenFromFlag)
342
404
  if (token) {
@@ -406,6 +468,10 @@ export default class BaseCommand extends Command {
406
468
  return accessToken
407
469
  }
408
470
 
471
+ /**
472
+ * Adds some data to the analytics payload
473
+ * @param {Record<string, unknown>} payload
474
+ */
409
475
  setAnalyticsPayload(payload) {
410
476
  const newPayload = { ...this.analytics.payload, ...payload }
411
477
  this.analytics = { ...this.analytics, payload: newPayload }
@@ -418,12 +484,45 @@ export default class BaseCommand extends Command {
418
484
  */
419
485
  async init(actionCommand) {
420
486
  debug(`${actionCommand.name()}:init`)('start')
421
- const options = actionCommand.opts()
422
- const cwd = options.cwd || process.cwd()
423
- // Get site id & build state
424
- const state = new StateConfig(cwd)
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
+ }
425
520
 
426
- const [token] = await getToken(options.auth)
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)
427
526
 
428
527
  const apiUrlOpts = {
429
528
  userAgent: USER_AGENT,
@@ -437,12 +536,23 @@ export default class BaseCommand extends Command {
437
536
  process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
438
537
  }
439
538
 
440
- const cachedConfig = await actionCommand.getConfig({ cwd, state, token, ...apiUrlOpts })
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
+ })
441
551
  const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
442
552
  const normalizedConfig = normalizeConfig(config)
443
553
  const agent = await getAgent({
444
- httpProxy: options.httpProxy,
445
- certificateFile: options.httpProxyCertificateFilename,
554
+ httpProxy: flags.httpProxy,
555
+ certificateFile: flags.httpProxyCertificateFilename,
446
556
  })
447
557
  const apiOpts = { ...apiUrlOpts, agent }
448
558
  const api = new NetlifyAPI(token || '', apiOpts)
@@ -454,28 +564,30 @@ export default class BaseCommand extends Command {
454
564
  // options.site as a site name (and not just site id) was introduced for the deploy command, so users could
455
565
  // deploy by name along with by id
456
566
  let siteData = siteInfo
457
- if (!siteData.url && options.site) {
458
- siteData = await getSiteByName(api, options.site)
567
+ if (!siteData.url && flags.site) {
568
+ siteData = await getSiteByName(api, flags.site)
459
569
  }
460
570
 
461
571
  const globalConfig = await getGlobalConfig()
462
572
 
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
-
573
+ // ==================================================
574
+ // Perform analytics reporting
575
+ // ==================================================
468
576
  const frameworkIDs = frameworks?.map((framework) => framework.id)
469
-
470
577
  if (frameworkIDs?.length !== 0) {
471
578
  this.setAnalyticsPayload({ frameworks: frameworkIDs })
472
579
  }
473
-
474
580
  this.setAnalyticsPayload({
475
- packageManager: project.packageManager?.name,
476
- buildSystem: project.buildSystems.map(({ id }) => id),
581
+ monorepo: Boolean(this.project.workspace),
582
+ packageManager: this.project.packageManager?.name,
583
+ buildSystem: this.project.buildSystems.map(({ id }) => id),
477
584
  })
478
585
 
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
479
591
  actionCommand.netlify = {
480
592
  // api methods
481
593
  api,
@@ -508,26 +620,36 @@ export default class BaseCommand extends Command {
508
620
 
509
621
  /**
510
622
  * Find and resolve the Netlify configuration
511
- * @param {*} config
512
- * @returns {ReturnType<import('@netlify/config/src/main')>}
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>}
513
634
  */
514
635
  async getConfig(config) {
515
- const options = this.opts()
516
- const { cwd, host, offline = options.offline, pathPrefix, scheme, state, token } = config
636
+ // the flags that are passed to the command like `--debug` or `--offline`
637
+ const flags = this.opts()
517
638
 
518
639
  try {
519
640
  return await resolveConfig({
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,
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,
526
648
  mode: 'cli',
527
- host,
528
- pathPrefix,
529
- scheme,
530
- offline,
649
+ host: config.host,
650
+ pathPrefix: config.pathPrefix,
651
+ scheme: config.scheme,
652
+ offline: config.offline ?? flags.offline,
531
653
  siteFeatureFlagPrefix: 'cli',
532
654
  })
533
655
  } catch (error_) {
@@ -539,17 +661,17 @@ export default class BaseCommand extends Command {
539
661
  //
540
662
  // @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
541
663
  // the option to say that we don't need API data.)
542
- if (isUserError && !offline && token) {
543
- if (this.opts().debug) {
664
+ if (isUserError && !config.offline && config.token) {
665
+ if (flags.debug) {
544
666
  error(error_, { exit: false })
545
667
  warn('Failed to resolve config, falling back to offline resolution')
546
668
  }
547
- return this.getConfig({ cwd, offline: true, state, token })
669
+ // recursive call with trying to resolve offline
670
+ return this.getConfig({ ...config, offline: true })
548
671
  }
549
672
 
550
673
  const message = isUserError ? error_.message : error_.stack
551
- console.error(message)
552
- exit(1)
674
+ error(message, { exit: true })
553
675
  }
554
676
  }
555
677
 
@@ -558,7 +680,7 @@ export default class BaseCommand extends Command {
558
680
  * set. The default context is `dev` most of the time, but some commands may
559
681
  * wish to override that.
560
682
  *
561
- * @returns {string}
683
+ * @returns {'production' | 'dev'}
562
684
  */
563
685
  getDefaultContext() {
564
686
  if (this.name() === 'serve') {
@@ -568,3 +690,17 @@ export default class BaseCommand extends Command {
568
690
  return 'dev'
569
691
  }
570
692
  }
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 { cwd, env } from 'process'
4
+ import { env } from 'process'
5
5
 
6
6
  import { runCoreSteps } from '@netlify/build'
7
7
  import { restoreConfig, updateConfig } from '@netlify/config'
@@ -64,16 +64,17 @@ 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
67
68
  * @param {object} config.config
68
69
  * @param {import('commander').OptionValues} config.options
69
70
  * @param {object} config.site
70
71
  * @param {object} config.siteData
71
72
  * @returns {Promise<string>}
72
73
  */
73
- const getDeployFolder = async ({ config, options, site, siteData }) => {
74
+ const getDeployFolder = async ({ config, options, site, siteData, workingDir }) => {
74
75
  let deployFolder
75
76
  if (options.dir) {
76
- deployFolder = resolve(cwd(), options.dir)
77
+ deployFolder = resolve(workingDir, options.dir)
77
78
  } else if (config?.build?.publish) {
78
79
  deployFolder = resolve(site.root, config.build.publish)
79
80
  } else if (siteData?.build_settings?.dir) {
@@ -82,14 +83,14 @@ const getDeployFolder = async ({ config, options, site, siteData }) => {
82
83
 
83
84
  if (!deployFolder) {
84
85
  log('Please provide a publish directory (e.g. "public" or "dist" or "."):')
85
- log(cwd())
86
+ log(workingDir)
86
87
  const { promptPath } = await inquirer.prompt([
87
88
  {
88
89
  type: 'input',
89
90
  name: 'promptPath',
90
91
  message: 'Publish directory',
91
92
  default: '.',
92
- filter: (input) => resolve(cwd(), input),
93
+ filter: (input) => resolve(workingDir, input),
93
94
  },
94
95
  ])
95
96
  deployFolder = promptPath
@@ -128,14 +129,15 @@ const validateDeployFolder = async ({ deployFolder }) => {
128
129
  * @param {import('commander').OptionValues} config.options
129
130
  * @param {object} config.site
130
131
  * @param {object} config.siteData
132
+ * @param {string} config.workingDir // The process working directory
131
133
  * @returns {string}
132
134
  */
133
- const getFunctionsFolder = ({ config, options, site, siteData }) => {
135
+ const getFunctionsFolder = ({ config, options, site, siteData, workingDir }) => {
134
136
  let functionsFolder
135
137
  // Support "functions" and "Functions"
136
138
  const funcConfig = config.functionsDirectory
137
139
  if (options.functions) {
138
- functionsFolder = resolve(cwd(), options.functions)
140
+ functionsFolder = resolve(workingDir, options.functions)
139
141
  } else if (funcConfig) {
140
142
  functionsFolder = resolve(site.root, funcConfig)
141
143
  } else if (siteData?.build_settings?.functions_dir) {
@@ -178,12 +180,21 @@ const validateFolders = async ({ deployFolder, functionsFolder }) => {
178
180
  return { deployFolderStat, functionsFolderStat }
179
181
  }
180
182
 
183
+ /**
184
+ * @param {object} config
185
+ * @param {string} config.deployFolder
186
+ * @param {*} config.site
187
+ * @returns
188
+ */
181
189
  const getDeployFilesFilter = ({ deployFolder, site }) => {
182
190
  // site.root === deployFolder can happen when users run `netlify deploy --dir .`
183
191
  // in that specific case we don't want to publish the repo node_modules
184
192
  // when site.root !== deployFolder the behaviour matches our buildbot
185
193
  const skipNodeModules = site.root === deployFolder
186
194
 
195
+ /**
196
+ * @param {string} filename
197
+ */
187
198
  return (filename) => {
188
199
  if (filename == null) {
189
200
  return false
@@ -496,6 +507,7 @@ const printResults = ({ deployToProduction, json, results, runBuildCommand }) =>
496
507
  * @param {import('../base-command.mjs').default} command
497
508
  */
498
509
  const deploy = async (options, command) => {
510
+ const { workingDir } = command
499
511
  const { api, site, siteInfo } = command.netlify
500
512
  const alias = options.alias || options.branch
501
513
 
@@ -566,8 +578,8 @@ const deploy = async (options, command) => {
566
578
  })
567
579
  const config = newConfig || command.netlify.config
568
580
 
569
- const deployFolder = await getDeployFolder({ options, config, site, siteData })
570
- const functionsFolder = getFunctionsFolder({ options, config, site, siteData })
581
+ const deployFolder = await getDeployFolder({ workingDir, options, config, site, siteData })
582
+ const functionsFolder = getFunctionsFolder({ workingDir, options, config, site, siteData })
571
583
  const { configPath } = site
572
584
  const edgeFunctionsConfig = command.netlify.config.edge_functions
573
585