netlify-cli 15.10.0-rc.1 → 15.11.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 (39) hide show
  1. package/bin/run.mjs +5 -6
  2. package/npm-shrinkwrap.json +3 -6
  3. package/package.json +2 -1
  4. package/src/commands/base-command.mjs +116 -295
  5. package/src/commands/build/build.mjs +1 -9
  6. package/src/commands/deploy/deploy.mjs +9 -23
  7. package/src/commands/dev/dev.mjs +17 -22
  8. package/src/commands/functions/functions-create.mjs +89 -118
  9. package/src/commands/functions/functions-invoke.mjs +7 -10
  10. package/src/commands/functions/functions-list.mjs +2 -2
  11. package/src/commands/init/init.mjs +1 -1
  12. package/src/commands/link/link.mjs +5 -5
  13. package/src/commands/serve/serve.mjs +6 -10
  14. package/src/commands/sites/sites-create-template.mjs +1 -1
  15. package/src/commands/sites/sites-create.mjs +1 -1
  16. package/src/lib/edge-functions/internal.mjs +3 -5
  17. package/src/lib/edge-functions/proxy.mjs +3 -27
  18. package/src/lib/functions/netlify-function.mjs +26 -1
  19. package/src/lib/functions/registry.mjs +14 -26
  20. package/src/lib/functions/runtimes/js/worker.mjs +1 -1
  21. package/src/lib/spinner.mjs +1 -1
  22. package/src/recipes/vscode/index.mjs +6 -24
  23. package/src/utils/command-helpers.mjs +7 -16
  24. package/src/utils/detect-server-settings.mjs +245 -133
  25. package/src/utils/framework-server.mjs +5 -6
  26. package/src/utils/functions/functions.mjs +5 -8
  27. package/src/utils/get-repo-data.mjs +6 -5
  28. package/src/utils/init/config-github.mjs +2 -2
  29. package/src/utils/init/config-manual.mjs +7 -24
  30. package/src/utils/init/frameworks.mjs +23 -0
  31. package/src/utils/init/utils.mjs +63 -62
  32. package/src/utils/proxy-server.mjs +4 -7
  33. package/src/utils/proxy.mjs +3 -4
  34. package/src/utils/read-repo-url.mjs +0 -4
  35. package/src/utils/run-build.mjs +32 -58
  36. package/src/utils/shell.mjs +7 -24
  37. package/src/utils/state-config.mjs +1 -5
  38. package/src/utils/static-server.mjs +0 -4
  39. package/src/utils/build-info.mjs +0 -100
package/bin/run.mjs CHANGED
@@ -4,7 +4,6 @@ import { argv } from 'process'
4
4
  import updateNotifier from 'update-notifier'
5
5
 
6
6
  import { createMainCommand } from '../src/commands/index.mjs'
7
- import { error } from '../src/utils/command-helpers.mjs'
8
7
  import getPackageJson from '../src/utils/get-package-json.mjs'
9
8
 
10
9
  // 12 hours
@@ -16,9 +15,9 @@ try {
16
15
  pkg,
17
16
  updateCheckInterval: UPDATE_CHECK_INTERVAL,
18
17
  }).notify()
19
- } catch (error_) {
20
- error('Error checking for updates:')
21
- error(error_)
18
+ } catch (error) {
19
+ console.log('Error checking for updates:')
20
+ console.log(error)
22
21
  }
23
22
 
24
23
  const program = createMainCommand()
@@ -26,6 +25,6 @@ const program = createMainCommand()
26
25
  try {
27
26
  await program.parseAsync(argv)
28
27
  program.onEnd()
29
- } catch (error_) {
30
- program.onEnd(error_)
28
+ } catch (error) {
29
+ program.onEnd(error)
31
30
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
- "version": "15.10.0-rc.1",
3
+ "version": "15.11.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "netlify-cli",
9
- "version": "15.10.0-rc.1",
9
+ "version": "15.11.0",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -16,6 +16,7 @@
16
16
  "@netlify/build-info": "7.7.3",
17
17
  "@netlify/config": "20.6.4",
18
18
  "@netlify/edge-bundler": "8.17.1",
19
+ "@netlify/framework-info": "9.8.10",
19
20
  "@netlify/local-functions-proxy": "1.1.1",
20
21
  "@netlify/serverless-functions-api": "1.5.2",
21
22
  "@netlify/zip-it-and-ship-it": "9.13.1",
@@ -14676,10 +14677,6 @@
14676
14677
  "engines": {
14677
14678
  "node": ">= 10"
14678
14679
  }
14679
- },
14680
- "tools/lint-rules": {
14681
- "name": "eslint-plugin-workspace",
14682
- "extraneous": true
14683
14680
  }
14684
14681
  },
14685
14682
  "dependencies": {
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.10.0-rc.1",
4
+ "version": "15.11.0",
5
5
  "author": "Netlify Inc.",
6
6
  "type": "module",
7
7
  "engines": {
@@ -48,6 +48,7 @@
48
48
  "@netlify/build-info": "7.7.3",
49
49
  "@netlify/config": "20.6.4",
50
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",
@@ -1,18 +1,13 @@
1
1
  // @ts-check
2
- import { existsSync } from 'fs'
3
- import { join, relative, resolve } from 'path'
4
2
  import process from 'process'
5
3
  import { format } from 'util'
6
4
 
7
- import { DefaultLogger, Project } from '@netlify/build-info'
5
+ import { Project } from '@netlify/build-info'
8
6
  // eslint-disable-next-line import/extensions, n/no-missing-import
9
- import { NodeFS, NoopLogger } from '@netlify/build-info/node'
7
+ import { NodeFS } from '@netlify/build-info/node'
10
8
  import { resolveConfig } from '@netlify/config'
11
9
  import { Command, Option } from 'commander'
12
10
  import debug from 'debug'
13
- import { findUp } from 'find-up'
14
- import inquirer from 'inquirer'
15
- import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
16
11
  import merge from 'lodash/merge.js'
17
12
  import { NetlifyAPI } from 'netlify'
18
13
 
@@ -35,31 +30,22 @@ import getGlobalConfig from '../utils/get-global-config.mjs'
35
30
  import { getSiteByName } from '../utils/get-site.mjs'
36
31
  import openBrowser from '../utils/open-browser.mjs'
37
32
  import StateConfig from '../utils/state-config.mjs'
38
- import { identify, reportError, track } from '../utils/telemetry/index.mjs'
33
+ import { identify, track } from '../utils/telemetry/index.mjs'
39
34
 
40
- // load the autocomplete plugin
41
- inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
42
- /** Netlify CLI client id. Lives in bot@netlify.com */
35
+ // Netlify CLI client id. Lives in bot@netlify.com
43
36
  // TODO: setup client for multiple environments
44
37
  const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
45
38
 
46
39
  const NANO_SECS_TO_MSECS = 1e6
47
- /** The fallback width for the help terminal */
40
+ // The fallback width for the help terminal
48
41
  const FALLBACK_HELP_CMD_WIDTH = 80
49
42
 
50
43
  const HELP_$ = NETLIFY_CYAN('$')
51
- /** indent on commands or description on the help page */
44
+ // indent on commands or description on the help page
52
45
  const HELP_INDENT_WIDTH = 2
53
- /** separator width between term and description */
46
+ // separator width between term and description
54
47
  const HELP_SEPARATOR_WIDTH = 5
55
48
 
56
- /**
57
- * A list of commands where we don't have to perform the workspace selection at.
58
- * Those commands work with the system or are not writing any config files that need to be
59
- * workspace aware.
60
- */
61
- const COMMANDS_WITHOUT_WORKSPACE_OPTIONS = new Set(['recipes', 'completion', 'status', 'switch', 'login', 'lm'])
62
-
63
49
  /**
64
50
  * Formats a help list correctly with the correct indent
65
51
  * @param {string[]} textArray
@@ -78,83 +64,30 @@ const getDuration = function (startTime) {
78
64
  }
79
65
 
80
66
  /**
81
- * Retrieves a workspace package based of the filter flag that is provided.
82
- * 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
83
- * @param {Project} project
84
- * @param {string=} filter
85
- * @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,
86
81
  */
87
- async function selectWorkspace(project, filter) {
88
- const selected = project.workspace?.packages.find((pkg) => {
89
- if (
90
- project.relativeBaseDirectory &&
91
- project.relativeBaseDirectory.length !== 0 &&
92
- pkg.path.startsWith(project.relativeBaseDirectory)
93
- ) {
94
- return true
95
- }
96
- return (pkg.name && pkg.name === filter) || pkg.path === filter
97
- })
98
-
99
- if (!selected) {
100
- log()
101
- log(chalk.cyan(`We've detected multiple sites inside your repository!`))
102
-
103
- const { result } = await inquirer.prompt({
104
- name: 'result',
105
- type: 'autocomplete',
106
- message: 'Select a site you want to work with',
107
- source: (/** @type {string} */ _, input = '') =>
108
- (project.workspace?.packages || [])
109
- .filter((pkg) => pkg.path.includes(input))
110
- .map((pkg) => ({
111
- name: `${pkg.name ? `${chalk.bold(pkg.name)} ` : ''}${pkg.path} ${chalk.dim(
112
- `--filter ${pkg.name || pkg.path}`,
113
- )}`,
114
- value: pkg.path,
115
- })),
116
- })
117
-
118
- return result
119
- }
120
- return selected.path
121
- }
122
82
 
123
83
  /** Base command class that provides tracking and config initialization */
124
84
  export default class BaseCommand extends Command {
125
- /**
126
- * The netlify object inside each command with the state
127
- * @type {import('./types.js').NetlifyOptions}
128
- */
85
+ /** @type {NetlifyOptions} */
129
86
  netlify
130
87
 
131
88
  /** @type {{ startTime: bigint, payload?: any}} */
132
89
  analytics = { startTime: process.hrtime.bigint() }
133
90
 
134
- /** @type {Project} */
135
- project
136
-
137
- /**
138
- * The working directory that is used for reading the `netlify.toml` file and storing the state.
139
- * In a monorepo context this must not be the process working directory and can be an absolute path to the
140
- * Package/Site that should be worked in.
141
- */
142
- // here we actually want to disable the lint rule as it's value is set
143
- // eslint-disable-next-line workspace/no-process-cwd
144
- workingDir = process.cwd()
145
-
146
- /**
147
- * The workspace root if inside a mono repository.
148
- * Must not be the repository root!
149
- * @type {string|undefined}
150
- */
151
- jsWorkspaceRoot
152
- /**
153
- * The current workspace package we should execute the commands in
154
- * @type {string|undefined}
155
- */
156
- workspacePackage
157
-
158
91
  /**
159
92
  * IMPORTANT this function will be called for each command!
160
93
  * Don't do anything expensive in there.
@@ -162,61 +95,49 @@ export default class BaseCommand extends Command {
162
95
  * @returns
163
96
  */
164
97
  createCommand(name) {
165
- const base = new BaseCommand(name)
166
- // If --silent or --json flag passed disable logger
167
- .addOption(new Option('--json', 'Output return values as JSON').hideHelp(true))
168
- .addOption(new Option('--silent', 'Silence CLI output').hideHelp(true))
169
- .addOption(new Option('--cwd <cwd>').hideHelp(true))
170
- .addOption(new Option('-o, --offline').hideHelp(true))
171
- .addOption(new Option('--auth <token>', 'Netlify auth token').hideHelp(true))
172
- .addOption(
173
- new Option('--httpProxy [address]', 'Old, prefer --http-proxy. Proxy server address to route requests through.')
174
- .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
175
- .hideHelp(true),
176
- )
177
- .addOption(
178
- new Option(
179
- '--httpProxyCertificateFilename [file]',
180
- 'Old, prefer --http-proxy-certificate-filename. Certificate file to use when connecting using a proxy server.',
98
+ return (
99
+ new BaseCommand(name)
100
+ // If --silent or --json flag passed disable logger
101
+ .addOption(new Option('--json', 'Output return values as JSON').hideHelp(true))
102
+ .addOption(new Option('--silent', 'Silence CLI output').hideHelp(true))
103
+ .addOption(new Option('--cwd <cwd>').hideHelp(true))
104
+ .addOption(new Option('-o, --offline').hideHelp(true))
105
+ .addOption(new Option('--auth <token>', 'Netlify auth token').hideHelp(true))
106
+ .addOption(
107
+ new Option(
108
+ '--httpProxy [address]',
109
+ 'Old, prefer --http-proxy. Proxy server address to route requests through.',
110
+ )
111
+ .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
112
+ .hideHelp(true),
181
113
  )
182
- .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
183
- .hideHelp(true),
184
- )
185
- .addOption(
186
- new Option(
114
+ .addOption(
115
+ new Option(
116
+ '--httpProxyCertificateFilename [file]',
117
+ 'Old, prefer --http-proxy-certificate-filename. Certificate file to use when connecting using a proxy server.',
118
+ )
119
+ .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
120
+ .hideHelp(true),
121
+ )
122
+ .option(
187
123
  '--http-proxy-certificate-filename [file]',
188
124
  'Certificate file to use when connecting using a proxy server',
125
+ process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME,
189
126
  )
190
- .default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
191
- .hideHelp(true),
192
- )
193
- .addOption(
194
- new Option('--httpProxy [address]', 'Proxy server address to route requests through.')
195
- .default(process.env.HTTP_PROXY || process.env.HTTPS_PROXY)
196
- .hideHelp(true),
197
- )
198
- .option('--debug', 'Print debugging information')
199
-
200
- // only add the `--config` or `--filter` option to commands that are workspace aware
201
- if (!COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(name)) {
202
- base
203
- .option('--config <configFilePath>', 'Custom path to a netlify configuration file')
204
127
  .option(
205
- '--filter <app>',
206
- 'Optional name of an application to run the command in.\nThis option is needed for working in Monorepos',
128
+ '--http-proxy [address]',
129
+ 'Proxy server address to route requests through.',
130
+ process.env.HTTP_PROXY || process.env.HTTPS_PROXY,
207
131
  )
208
- }
209
-
210
- return base.hook('preAction', async (_parentCommand, actionCommand) => {
211
- if (actionCommand.opts()?.debug) {
212
- process.env.DEBUG = '*'
213
- }
214
- debug(`${name}:preAction`)('start')
215
- this.analytics = { startTime: process.hrtime.bigint() }
216
- // @ts-ignore cannot type actionCommand as BaseCommand
217
- await this.init(actionCommand)
218
- debug(`${name}:preAction`)('end')
219
- })
132
+ .option('--debug', 'Print debugging information')
133
+ .hook('preAction', async (_parentCommand, actionCommand) => {
134
+ debug(`${name}:preAction`)('start')
135
+ this.analytics = { startTime: process.hrtime.bigint() }
136
+ // @ts-ignore cannot type actionCommand as BaseCommand
137
+ await this.init(actionCommand)
138
+ debug(`${name}:preAction`)('end')
139
+ })
140
+ )
220
141
  }
221
142
 
222
143
  /** @private */
@@ -228,7 +149,7 @@ export default class BaseCommand extends Command {
228
149
  return this
229
150
  }
230
151
 
231
- /** @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) */
232
153
  examples = []
233
154
 
234
155
  /**
@@ -251,27 +172,23 @@ export default class BaseCommand extends Command {
251
172
  const term =
252
173
  this.name() === 'netlify'
253
174
  ? `${HELP_$} ${command.name()} [COMMAND]`
254
- : `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`
175
+ : `${HELP_$} ${command.parent.name()} ${command.name()} ${command.usage()}`
255
176
 
256
177
  return padLeft(term, HELP_INDENT_WIDTH)
257
178
  }
258
179
 
259
- /**
260
- * @param {BaseCommand} command
261
- */
262
180
  const getCommands = (command) => {
263
181
  const parentCommand = this.name() === 'netlify' ? command : command.parent
264
- return (
265
- parentCommand?.commands.filter((cmd) => {
266
- if (cmd._hidden) return false
267
- // the root command
268
- if (this.name() === 'netlify') {
269
- // don't include subcommands on the main page
270
- return !cmd.name().includes(':')
271
- }
272
- return cmd.name().startsWith(`${command.name()}:`)
273
- }) || []
274
- )
182
+ return parentCommand.commands.filter((cmd) => {
183
+ // eslint-disable-next-line no-underscore-dangle
184
+ if (cmd._hidden) return false
185
+ // the root command
186
+ if (this.name() === 'netlify') {
187
+ // don't include subcommands on the main page
188
+ return !cmd.name().includes(':')
189
+ }
190
+ return cmd.name().startsWith(`${command.name()}:`)
191
+ })
275
192
  }
276
193
 
277
194
  /**
@@ -364,8 +281,9 @@ export default class BaseCommand extends Command {
364
281
  }
365
282
 
366
283
  // Aliases
367
-
284
+ // eslint-disable-next-line no-underscore-dangle
368
285
  if (command._aliases.length !== 0) {
286
+ // eslint-disable-next-line no-underscore-dangle
369
287
  const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true))
370
288
  output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), '']
371
289
  }
@@ -419,11 +337,6 @@ export default class BaseCommand extends Command {
419
337
  }
420
338
  }
421
339
 
422
- /**
423
- *
424
- * @param {string|undefined} tokenFromFlag
425
- * @returns
426
- */
427
340
  async authenticate(tokenFromFlag) {
428
341
  const [token] = await getToken(tokenFromFlag)
429
342
  if (token) {
@@ -493,10 +406,6 @@ export default class BaseCommand extends Command {
493
406
  return accessToken
494
407
  }
495
408
 
496
- /**
497
- * Adds some data to the analytics payload
498
- * @param {Record<string, unknown>} payload
499
- */
500
409
  setAnalyticsPayload(payload) {
501
410
  const newPayload = { ...this.analytics.payload, ...payload }
502
411
  this.analytics = { ...this.analytics, payload: newPayload }
@@ -509,58 +418,12 @@ export default class BaseCommand extends Command {
509
418
  */
510
419
  async init(actionCommand) {
511
420
  debug(`${actionCommand.name()}:init`)('start')
512
- const flags = actionCommand.opts()
513
- // here we actually want to use the process.cwd as we are setting the workingDir
514
- // eslint-disable-next-line workspace/no-process-cwd
515
- this.workingDir = flags.cwd || process.cwd()
516
-
517
- // ==================================================
518
- // Create a Project and run the Heuristics to detect
519
- // if we are run inside a monorepo or not.
520
- // ==================================================
521
-
522
- // retrieve the repository root
523
- const rootDir = await getRepositoryRoot()
524
- // Get framework, add to analytics payload for every command, if a framework is set
525
- const fs = new NodeFS()
526
- // disable logging inside the project and FS if not in debug mode
527
- fs.logger = actionCommand.opts()?.debug ? new DefaultLogger('debug') : new NoopLogger()
528
- this.project = new Project(fs, this.workingDir, rootDir)
529
- .setEnvironment(process.env)
530
- .setNodeVersion(process.version)
531
- // eslint-disable-next-line promise/prefer-await-to-callbacks
532
- .setReportFn((err, reportConfig) => {
533
- reportError(err, {
534
- severity: reportConfig?.severity || 'error',
535
- metadata: reportConfig?.metadata,
536
- })
537
- })
538
- const frameworks = await this.project.detectFrameworks()
539
- /** @type { string|undefined} */
540
- let packageConfig = flags.config ? resolve(flags.config) : undefined
541
- // check if we have detected multiple projects inside which one we have to perform our operations.
542
- // only ask to select one if on the workspace root
543
- if (
544
- !COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(actionCommand.name()) &&
545
- this.project.workspace?.packages.length &&
546
- this.project.workspace.isRoot
547
- ) {
548
- this.workspacePackage = await selectWorkspace(this.project, actionCommand.opts().filter)
549
- this.workingDir = join(this.project.jsWorkspaceRoot, this.workspacePackage)
550
- }
421
+ const options = actionCommand.opts()
422
+ const cwd = options.cwd || process.cwd()
423
+ // Get site id & build state
424
+ const state = new StateConfig(cwd)
551
425
 
552
- this.jsWorkspaceRoot = this.project.jsWorkspaceRoot
553
- // detect if a toml exists in this package.
554
- const tomlFile = join(this.workingDir, 'netlify.toml')
555
- if (!packageConfig && existsSync(tomlFile)) {
556
- packageConfig = tomlFile
557
- }
558
-
559
- // ==================================================
560
- // Retrieve Site id and build state from the state.json
561
- // ==================================================
562
- const state = new StateConfig(this.workingDir)
563
- const [token] = await getToken(flags.auth)
426
+ const [token] = await getToken(options.auth)
564
427
 
565
428
  const apiUrlOpts = {
566
429
  userAgent: USER_AGENT,
@@ -574,24 +437,12 @@ export default class BaseCommand extends Command {
574
437
  process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
575
438
  }
576
439
 
577
- // ==================================================
578
- // Start retrieving the configuration through the
579
- // configuration file and the API
580
- // ==================================================
581
- const cachedConfig = await actionCommand.getConfig({
582
- cwd: this.jsWorkspaceRoot || this.workingDir,
583
- repositoryRoot: rootDir,
584
- // The config flag needs to be resolved from the actual process working directory
585
- configFilePath: packageConfig,
586
- state,
587
- token,
588
- ...apiUrlOpts,
589
- })
440
+ const cachedConfig = await actionCommand.getConfig({ cwd, state, token, ...apiUrlOpts })
590
441
  const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
591
442
  const normalizedConfig = normalizeConfig(config)
592
443
  const agent = await getAgent({
593
- httpProxy: flags.httpProxy,
594
- certificateFile: flags.httpProxyCertificateFilename,
444
+ httpProxy: options.httpProxy,
445
+ certificateFile: options.httpProxyCertificateFilename,
595
446
  })
596
447
  const apiOpts = { ...apiUrlOpts, agent }
597
448
  const api = new NetlifyAPI(token || '', apiOpts)
@@ -603,44 +454,33 @@ export default class BaseCommand extends Command {
603
454
  // options.site as a site name (and not just site id) was introduced for the deploy command, so users could
604
455
  // deploy by name along with by id
605
456
  let siteData = siteInfo
606
- if (!siteData.url && flags.site) {
607
- siteData = await getSiteByName(api, flags.site)
457
+ if (!siteData.url && options.site) {
458
+ siteData = await getSiteByName(api, options.site)
608
459
  }
609
460
 
610
461
  const globalConfig = await getGlobalConfig()
611
462
 
612
- // ==================================================
613
- // Perform analytics reporting
614
- // ==================================================
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
+
615
468
  const frameworkIDs = frameworks?.map((framework) => framework.id)
469
+
616
470
  if (frameworkIDs?.length !== 0) {
617
471
  this.setAnalyticsPayload({ frameworks: frameworkIDs })
618
472
  }
473
+
619
474
  this.setAnalyticsPayload({
620
- monorepo: Boolean(this.project.workspace),
621
- packageManager: this.project.packageManager?.name,
622
- buildSystem: this.project.buildSystems.map(({ id }) => id),
475
+ packageManager: project.packageManager?.name,
476
+ buildSystem: project.buildSystems.map(({ id }) => id),
623
477
  })
624
478
 
625
- // set the project and the netlify api object on the command,
626
- // to be accessible inside each command.
627
- actionCommand.project = this.project
628
- actionCommand.workingDir = this.workingDir
629
- actionCommand.workspacePackage = this.workspacePackage
630
- actionCommand.jsWorkspaceRoot = this.jsWorkspaceRoot
631
-
632
- // Either an existing configuration file from `@netlify/config` or a file path
633
- // that should be used for creating it.
634
- const configFilePath = configPath || join(this.workingDir, 'netlify.toml')
635
-
636
479
  actionCommand.netlify = {
637
480
  // api methods
638
481
  api,
639
482
  apiOpts,
640
- // The Absolute Repository root (detected through @netlify/config)
641
483
  repositoryRoot,
642
- configFilePath,
643
- relConfigFilePath: relative(repositoryRoot, configFilePath),
644
484
  // current site context
645
485
  site: {
646
486
  root: buildDir,
@@ -668,36 +508,26 @@ export default class BaseCommand extends Command {
668
508
 
669
509
  /**
670
510
  * Find and resolve the Netlify configuration
671
- * @param {object} config
672
- * @param {string} config.cwd
673
- * @param {string|null=} config.token
674
- * @param {*} config.state
675
- * @param {boolean=} config.offline
676
- * @param {string=} config.configFilePath An optional path to the netlify configuration file e.g. netlify.toml
677
- * @param {string=} config.repositoryRoot
678
- * @param {string=} config.host
679
- * @param {string=} config.pathPrefix
680
- * @param {string=} config.scheme
681
- * @returns {ReturnType<typeof resolveConfig>}
511
+ * @param {*} config
512
+ * @returns {ReturnType<import('@netlify/config/src/main')>}
682
513
  */
683
514
  async getConfig(config) {
684
- // the flags that are passed to the command like `--debug` or `--offline`
685
- const flags = this.opts()
515
+ const options = this.opts()
516
+ const { cwd, host, offline = options.offline, pathPrefix, scheme, state, token } = config
686
517
 
687
518
  try {
688
519
  return await resolveConfig({
689
- config: config.configFilePath,
690
- repositoryRoot: config.repositoryRoot,
691
- cwd: config.cwd,
692
- context: flags.context || process.env.CONTEXT || this.getDefaultContext(),
693
- debug: flags.debug,
694
- siteId: flags.siteId || (typeof flags.site === 'string' && flags.site) || config.state.get('siteId'),
695
- 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,
696
526
  mode: 'cli',
697
- host: config.host,
698
- pathPrefix: config.pathPrefix,
699
- scheme: config.scheme,
700
- offline: config.offline ?? flags.offline,
527
+ host,
528
+ pathPrefix,
529
+ scheme,
530
+ offline,
701
531
  siteFeatureFlagPrefix: 'cli',
702
532
  })
703
533
  } catch (error_) {
@@ -709,17 +539,17 @@ export default class BaseCommand extends Command {
709
539
  //
710
540
  // @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
711
541
  // the option to say that we don't need API data.)
712
- if (isUserError && !config.offline && config.token) {
713
- if (flags.debug) {
542
+ if (isUserError && !offline && token) {
543
+ if (this.opts().debug) {
714
544
  error(error_, { exit: false })
715
545
  warn('Failed to resolve config, falling back to offline resolution')
716
546
  }
717
- // recursive call with trying to resolve offline
718
- return this.getConfig({ ...config, offline: true })
547
+ return this.getConfig({ cwd, offline: true, state, token })
719
548
  }
720
549
 
721
550
  const message = isUserError ? error_.message : error_.stack
722
- error(message, { exit: true })
551
+ console.error(message)
552
+ exit(1)
723
553
  }
724
554
  }
725
555
 
@@ -728,22 +558,13 @@ export default class BaseCommand extends Command {
728
558
  * set. The default context is `dev` most of the time, but some commands may
729
559
  * wish to override that.
730
560
  *
731
- * @returns {'production' | 'dev'}
561
+ * @returns {string}
732
562
  */
733
563
  getDefaultContext() {
734
- return this.name() === 'serve' ? 'production' : 'dev'
735
- }
736
- }
564
+ if (this.name() === 'serve') {
565
+ return 'production'
566
+ }
737
567
 
738
- /**
739
- * Retrieves the repository root through a git command.
740
- * Returns undefined if not a git project.
741
- * @param {string} [cwd] The optional current working directory
742
- * @returns {Promise<string|undefined>}
743
- */
744
- async function getRepositoryRoot(cwd) {
745
- const res = await findUp('.git', { cwd, type: 'directory' })
746
- if (res) {
747
- return join(res, '..')
568
+ return 'dev'
748
569
  }
749
570
  }