netlify-cli 15.9.1 → 15.10.0-rc.1
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.
- package/bin/run.mjs +6 -5
- package/npm-shrinkwrap.json +369 -266
- package/package.json +8 -9
- package/src/commands/base-command.mjs +295 -116
- package/src/commands/build/build.mjs +9 -1
- package/src/commands/deploy/deploy.mjs +23 -9
- package/src/commands/dev/dev.mjs +22 -17
- package/src/commands/functions/functions-create.mjs +118 -89
- package/src/commands/functions/functions-invoke.mjs +10 -7
- package/src/commands/functions/functions-list.mjs +2 -2
- package/src/commands/init/init.mjs +1 -1
- package/src/commands/link/link.mjs +5 -5
- package/src/commands/serve/serve.mjs +10 -6
- package/src/commands/sites/sites-create-template.mjs +1 -1
- package/src/commands/sites/sites-create.mjs +1 -1
- package/src/functions-templates/typescript/hello-world/package-lock.json +6 -6
- package/src/lib/edge-functions/bootstrap.mjs +1 -1
- package/src/lib/edge-functions/headers.mjs +1 -0
- package/src/lib/edge-functions/internal.mjs +5 -3
- package/src/lib/edge-functions/proxy.mjs +29 -4
- package/src/lib/functions/runtimes/js/index.mjs +1 -1
- package/src/lib/functions/runtimes/js/worker.mjs +1 -1
- package/src/lib/functions/server.mjs +3 -2
- package/src/lib/spinner.mjs +1 -1
- package/src/recipes/vscode/index.mjs +24 -6
- package/src/utils/build-info.mjs +100 -0
- package/src/utils/command-helpers.mjs +16 -7
- package/src/utils/detect-server-settings.mjs +133 -245
- package/src/utils/framework-server.mjs +6 -5
- package/src/utils/functions/functions.mjs +8 -5
- package/src/utils/get-repo-data.mjs +5 -6
- package/src/utils/init/config-github.mjs +2 -2
- package/src/utils/init/config-manual.mjs +24 -7
- package/src/utils/init/utils.mjs +62 -63
- package/src/utils/proxy-server.mjs +7 -4
- package/src/utils/proxy.mjs +4 -3
- package/src/utils/read-repo-url.mjs +4 -0
- package/src/utils/run-build.mjs +58 -32
- package/src/utils/shell.mjs +24 -7
- package/src/utils/state-config.mjs +5 -1
- package/src/utils/static-server.mjs +4 -0
- package/src/utils/telemetry/report-error.mjs +8 -4
- 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.
|
|
4
|
+
"version": "15.10.0-rc.1",
|
|
5
5
|
"author": "Netlify Inc.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -44,16 +44,14 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@bugsnag/js": "7.20.2",
|
|
46
46
|
"@fastify/static": "6.10.2",
|
|
47
|
-
"@netlify/build": "29.
|
|
48
|
-
"@netlify/build-info": "7.
|
|
49
|
-
"@netlify/config": "20.6.
|
|
50
|
-
"@netlify/edge-bundler": "8.
|
|
51
|
-
"@netlify/framework-info": "9.8.10",
|
|
47
|
+
"@netlify/build": "29.17.3",
|
|
48
|
+
"@netlify/build-info": "7.7.3",
|
|
49
|
+
"@netlify/config": "20.6.4",
|
|
50
|
+
"@netlify/edge-bundler": "8.17.1",
|
|
52
51
|
"@netlify/local-functions-proxy": "1.1.1",
|
|
53
|
-
"@netlify/serverless-functions-api": "1.5.
|
|
54
|
-
"@netlify/zip-it-and-ship-it": "9.13.
|
|
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
|
-
"@skn0tt/lambda-local": "2.0.3",
|
|
57
55
|
"ansi-escapes": "6.2.0",
|
|
58
56
|
"ansi-styles": "6.2.1",
|
|
59
57
|
"ansi-to-html": "0.7.2",
|
|
@@ -108,6 +106,7 @@
|
|
|
108
106
|
"isexe": "2.0.0",
|
|
109
107
|
"jsonwebtoken": "9.0.1",
|
|
110
108
|
"jwt-decode": "3.1.2",
|
|
109
|
+
"lambda-local": "2.1.1",
|
|
111
110
|
"listr": "0.14.3",
|
|
112
111
|
"locate-path": "7.2.0",
|
|
113
112
|
"lodash": "4.17.21",
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import { existsSync } from 'fs'
|
|
3
|
+
import { join, relative, resolve } from 'path'
|
|
2
4
|
import process from 'process'
|
|
3
5
|
import { format } from 'util'
|
|
4
6
|
|
|
5
|
-
import { Project } from '@netlify/build-info'
|
|
7
|
+
import { DefaultLogger, Project } from '@netlify/build-info'
|
|
6
8
|
// eslint-disable-next-line import/extensions, n/no-missing-import
|
|
7
|
-
import { NodeFS } from '@netlify/build-info/node'
|
|
9
|
+
import { NodeFS, NoopLogger } from '@netlify/build-info/node'
|
|
8
10
|
import { resolveConfig } from '@netlify/config'
|
|
9
11
|
import { Command, Option } from 'commander'
|
|
10
12
|
import debug from 'debug'
|
|
13
|
+
import { findUp } from 'find-up'
|
|
14
|
+
import inquirer from 'inquirer'
|
|
15
|
+
import inquirerAutocompletePrompt from 'inquirer-autocomplete-prompt'
|
|
11
16
|
import merge from 'lodash/merge.js'
|
|
12
17
|
import { NetlifyAPI } from 'netlify'
|
|
13
18
|
|
|
@@ -30,22 +35,31 @@ import getGlobalConfig from '../utils/get-global-config.mjs'
|
|
|
30
35
|
import { getSiteByName } from '../utils/get-site.mjs'
|
|
31
36
|
import openBrowser from '../utils/open-browser.mjs'
|
|
32
37
|
import StateConfig from '../utils/state-config.mjs'
|
|
33
|
-
import { identify, track } from '../utils/telemetry/index.mjs'
|
|
38
|
+
import { identify, reportError, track } from '../utils/telemetry/index.mjs'
|
|
34
39
|
|
|
35
|
-
//
|
|
40
|
+
// load the autocomplete plugin
|
|
41
|
+
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
|
|
42
|
+
/** Netlify CLI client id. Lives in bot@netlify.com */
|
|
36
43
|
// TODO: setup client for multiple environments
|
|
37
44
|
const CLIENT_ID = 'd6f37de6614df7ae58664cfca524744d73807a377f5ee71f1a254f78412e3750'
|
|
38
45
|
|
|
39
46
|
const NANO_SECS_TO_MSECS = 1e6
|
|
40
|
-
|
|
47
|
+
/** The fallback width for the help terminal */
|
|
41
48
|
const FALLBACK_HELP_CMD_WIDTH = 80
|
|
42
49
|
|
|
43
50
|
const HELP_$ = NETLIFY_CYAN('$')
|
|
44
|
-
|
|
51
|
+
/** indent on commands or description on the help page */
|
|
45
52
|
const HELP_INDENT_WIDTH = 2
|
|
46
|
-
|
|
53
|
+
/** separator width between term and description */
|
|
47
54
|
const HELP_SEPARATOR_WIDTH = 5
|
|
48
55
|
|
|
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
|
+
|
|
49
63
|
/**
|
|
50
64
|
* Formats a help list correctly with the correct indent
|
|
51
65
|
* @param {string[]} textArray
|
|
@@ -64,30 +78,83 @@ const getDuration = function (startTime) {
|
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
/**
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @
|
|
70
|
-
* @
|
|
71
|
-
* @
|
|
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,
|
|
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>}
|
|
81
86
|
*/
|
|
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
|
+
}
|
|
82
122
|
|
|
83
123
|
/** Base command class that provides tracking and config initialization */
|
|
84
124
|
export default class BaseCommand extends Command {
|
|
85
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* The netlify object inside each command with the state
|
|
127
|
+
* @type {import('./types.js').NetlifyOptions}
|
|
128
|
+
*/
|
|
86
129
|
netlify
|
|
87
130
|
|
|
88
131
|
/** @type {{ startTime: bigint, payload?: any}} */
|
|
89
132
|
analytics = { startTime: process.hrtime.bigint() }
|
|
90
133
|
|
|
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
|
+
|
|
91
158
|
/**
|
|
92
159
|
* IMPORTANT this function will be called for each command!
|
|
93
160
|
* Don't do anything expensive in there.
|
|
@@ -95,49 +162,61 @@ export default class BaseCommand extends Command {
|
|
|
95
162
|
* @returns
|
|
96
163
|
*/
|
|
97
164
|
createCommand(name) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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),
|
|
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.',
|
|
121
181
|
)
|
|
122
|
-
|
|
182
|
+
.default(process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME)
|
|
183
|
+
.hideHelp(true),
|
|
184
|
+
)
|
|
185
|
+
.addOption(
|
|
186
|
+
new Option(
|
|
123
187
|
'--http-proxy-certificate-filename [file]',
|
|
124
188
|
'Certificate file to use when connecting using a proxy server',
|
|
125
|
-
process.env.NETLIFY_PROXY_CERTIFICATE_FILENAME,
|
|
126
189
|
)
|
|
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')
|
|
127
204
|
.option(
|
|
128
|
-
'--
|
|
129
|
-
'
|
|
130
|
-
process.env.HTTP_PROXY || process.env.HTTPS_PROXY,
|
|
205
|
+
'--filter <app>',
|
|
206
|
+
'Optional name of an application to run the command in.\nThis option is needed for working in Monorepos',
|
|
131
207
|
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
})
|
|
141
220
|
}
|
|
142
221
|
|
|
143
222
|
/** @private */
|
|
@@ -149,7 +228,7 @@ export default class BaseCommand extends Command {
|
|
|
149
228
|
return this
|
|
150
229
|
}
|
|
151
230
|
|
|
152
|
-
/** The examples list for the command (used inside doc generation and help page) */
|
|
231
|
+
/** @type {string[]} The examples list for the command (used inside doc generation and help page) */
|
|
153
232
|
examples = []
|
|
154
233
|
|
|
155
234
|
/**
|
|
@@ -172,23 +251,27 @@ export default class BaseCommand extends Command {
|
|
|
172
251
|
const term =
|
|
173
252
|
this.name() === 'netlify'
|
|
174
253
|
? `${HELP_$} ${command.name()} [COMMAND]`
|
|
175
|
-
: `${HELP_$} ${command.parent
|
|
254
|
+
: `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`
|
|
176
255
|
|
|
177
256
|
return padLeft(term, HELP_INDENT_WIDTH)
|
|
178
257
|
}
|
|
179
258
|
|
|
259
|
+
/**
|
|
260
|
+
* @param {BaseCommand} command
|
|
261
|
+
*/
|
|
180
262
|
const getCommands = (command) => {
|
|
181
263
|
const parentCommand = this.name() === 'netlify' ? command : command.parent
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
+
)
|
|
192
275
|
}
|
|
193
276
|
|
|
194
277
|
/**
|
|
@@ -281,9 +364,8 @@ export default class BaseCommand extends Command {
|
|
|
281
364
|
}
|
|
282
365
|
|
|
283
366
|
// Aliases
|
|
284
|
-
|
|
367
|
+
|
|
285
368
|
if (command._aliases.length !== 0) {
|
|
286
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
287
369
|
const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true))
|
|
288
370
|
output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), '']
|
|
289
371
|
}
|
|
@@ -337,6 +419,11 @@ export default class BaseCommand extends Command {
|
|
|
337
419
|
}
|
|
338
420
|
}
|
|
339
421
|
|
|
422
|
+
/**
|
|
423
|
+
*
|
|
424
|
+
* @param {string|undefined} tokenFromFlag
|
|
425
|
+
* @returns
|
|
426
|
+
*/
|
|
340
427
|
async authenticate(tokenFromFlag) {
|
|
341
428
|
const [token] = await getToken(tokenFromFlag)
|
|
342
429
|
if (token) {
|
|
@@ -406,6 +493,10 @@ export default class BaseCommand extends Command {
|
|
|
406
493
|
return accessToken
|
|
407
494
|
}
|
|
408
495
|
|
|
496
|
+
/**
|
|
497
|
+
* Adds some data to the analytics payload
|
|
498
|
+
* @param {Record<string, unknown>} payload
|
|
499
|
+
*/
|
|
409
500
|
setAnalyticsPayload(payload) {
|
|
410
501
|
const newPayload = { ...this.analytics.payload, ...payload }
|
|
411
502
|
this.analytics = { ...this.analytics, payload: newPayload }
|
|
@@ -418,12 +509,58 @@ export default class BaseCommand extends Command {
|
|
|
418
509
|
*/
|
|
419
510
|
async init(actionCommand) {
|
|
420
511
|
debug(`${actionCommand.name()}:init`)('start')
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
//
|
|
424
|
-
|
|
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
|
+
}
|
|
425
551
|
|
|
426
|
-
|
|
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)
|
|
427
564
|
|
|
428
565
|
const apiUrlOpts = {
|
|
429
566
|
userAgent: USER_AGENT,
|
|
@@ -437,12 +574,24 @@ export default class BaseCommand extends Command {
|
|
|
437
574
|
process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
|
|
438
575
|
}
|
|
439
576
|
|
|
440
|
-
|
|
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
|
+
})
|
|
441
590
|
const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
|
|
442
591
|
const normalizedConfig = normalizeConfig(config)
|
|
443
592
|
const agent = await getAgent({
|
|
444
|
-
httpProxy:
|
|
445
|
-
certificateFile:
|
|
593
|
+
httpProxy: flags.httpProxy,
|
|
594
|
+
certificateFile: flags.httpProxyCertificateFilename,
|
|
446
595
|
})
|
|
447
596
|
const apiOpts = { ...apiUrlOpts, agent }
|
|
448
597
|
const api = new NetlifyAPI(token || '', apiOpts)
|
|
@@ -454,33 +603,44 @@ export default class BaseCommand extends Command {
|
|
|
454
603
|
// options.site as a site name (and not just site id) was introduced for the deploy command, so users could
|
|
455
604
|
// deploy by name along with by id
|
|
456
605
|
let siteData = siteInfo
|
|
457
|
-
if (!siteData.url &&
|
|
458
|
-
siteData = await getSiteByName(api,
|
|
606
|
+
if (!siteData.url && flags.site) {
|
|
607
|
+
siteData = await getSiteByName(api, flags.site)
|
|
459
608
|
}
|
|
460
609
|
|
|
461
610
|
const globalConfig = await getGlobalConfig()
|
|
462
611
|
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const frameworks = await project.detectFrameworks()
|
|
467
|
-
|
|
612
|
+
// ==================================================
|
|
613
|
+
// Perform analytics reporting
|
|
614
|
+
// ==================================================
|
|
468
615
|
const frameworkIDs = frameworks?.map((framework) => framework.id)
|
|
469
|
-
|
|
470
616
|
if (frameworkIDs?.length !== 0) {
|
|
471
617
|
this.setAnalyticsPayload({ frameworks: frameworkIDs })
|
|
472
618
|
}
|
|
473
|
-
|
|
474
619
|
this.setAnalyticsPayload({
|
|
475
|
-
|
|
476
|
-
|
|
620
|
+
monorepo: Boolean(this.project.workspace),
|
|
621
|
+
packageManager: this.project.packageManager?.name,
|
|
622
|
+
buildSystem: this.project.buildSystems.map(({ id }) => id),
|
|
477
623
|
})
|
|
478
624
|
|
|
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
|
+
|
|
479
636
|
actionCommand.netlify = {
|
|
480
637
|
// api methods
|
|
481
638
|
api,
|
|
482
639
|
apiOpts,
|
|
640
|
+
// The Absolute Repository root (detected through @netlify/config)
|
|
483
641
|
repositoryRoot,
|
|
642
|
+
configFilePath,
|
|
643
|
+
relConfigFilePath: relative(repositoryRoot, configFilePath),
|
|
484
644
|
// current site context
|
|
485
645
|
site: {
|
|
486
646
|
root: buildDir,
|
|
@@ -508,26 +668,36 @@ export default class BaseCommand extends Command {
|
|
|
508
668
|
|
|
509
669
|
/**
|
|
510
670
|
* Find and resolve the Netlify configuration
|
|
511
|
-
* @param {
|
|
512
|
-
* @
|
|
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>}
|
|
513
682
|
*/
|
|
514
683
|
async getConfig(config) {
|
|
515
|
-
|
|
516
|
-
const
|
|
684
|
+
// the flags that are passed to the command like `--debug` or `--offline`
|
|
685
|
+
const flags = this.opts()
|
|
517
686
|
|
|
518
687
|
try {
|
|
519
688
|
return await resolveConfig({
|
|
520
|
-
config:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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,
|
|
526
696
|
mode: 'cli',
|
|
527
|
-
host,
|
|
528
|
-
pathPrefix,
|
|
529
|
-
scheme,
|
|
530
|
-
offline,
|
|
697
|
+
host: config.host,
|
|
698
|
+
pathPrefix: config.pathPrefix,
|
|
699
|
+
scheme: config.scheme,
|
|
700
|
+
offline: config.offline ?? flags.offline,
|
|
531
701
|
siteFeatureFlagPrefix: 'cli',
|
|
532
702
|
})
|
|
533
703
|
} catch (error_) {
|
|
@@ -539,17 +709,17 @@ export default class BaseCommand extends Command {
|
|
|
539
709
|
//
|
|
540
710
|
// @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
|
|
541
711
|
// the option to say that we don't need API data.)
|
|
542
|
-
if (isUserError && !offline && token) {
|
|
543
|
-
if (
|
|
712
|
+
if (isUserError && !config.offline && config.token) {
|
|
713
|
+
if (flags.debug) {
|
|
544
714
|
error(error_, { exit: false })
|
|
545
715
|
warn('Failed to resolve config, falling back to offline resolution')
|
|
546
716
|
}
|
|
547
|
-
|
|
717
|
+
// recursive call with trying to resolve offline
|
|
718
|
+
return this.getConfig({ ...config, offline: true })
|
|
548
719
|
}
|
|
549
720
|
|
|
550
721
|
const message = isUserError ? error_.message : error_.stack
|
|
551
|
-
|
|
552
|
-
exit(1)
|
|
722
|
+
error(message, { exit: true })
|
|
553
723
|
}
|
|
554
724
|
}
|
|
555
725
|
|
|
@@ -558,13 +728,22 @@ export default class BaseCommand extends Command {
|
|
|
558
728
|
* set. The default context is `dev` most of the time, but some commands may
|
|
559
729
|
* wish to override that.
|
|
560
730
|
*
|
|
561
|
-
* @returns {
|
|
731
|
+
* @returns {'production' | 'dev'}
|
|
562
732
|
*/
|
|
563
733
|
getDefaultContext() {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
734
|
+
return this.name() === 'serve' ? 'production' : 'dev'
|
|
735
|
+
}
|
|
736
|
+
}
|
|
567
737
|
|
|
568
|
-
|
|
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, '..')
|
|
569
748
|
}
|
|
570
749
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import process from 'process'
|
|
3
3
|
|
|
4
4
|
import { getBuildOptions, runBuild } from '../../lib/build.mjs'
|
|
5
|
+
import { detectFrameworkSettings } from '../../utils/build-info.mjs'
|
|
5
6
|
import { error, exit, getToken } from '../../utils/command-helpers.mjs'
|
|
6
7
|
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.mjs'
|
|
7
8
|
|
|
@@ -33,11 +34,18 @@ const injectEnv = async function (command, { api, buildOptions, context, siteInf
|
|
|
33
34
|
* @param {import('../base-command.mjs').default} command
|
|
34
35
|
*/
|
|
35
36
|
const build = async (options, command) => {
|
|
37
|
+
const { cachedConfig, siteInfo } = command.netlify
|
|
36
38
|
command.setAnalyticsPayload({ dry: options.dry })
|
|
37
39
|
// Retrieve Netlify Build options
|
|
38
40
|
const [token] = await getToken()
|
|
41
|
+
const settings = await detectFrameworkSettings(command, 'build')
|
|
42
|
+
|
|
43
|
+
// override the build command with the detection result if no command is specified through the config
|
|
44
|
+
if (!cachedConfig.config.build.command) {
|
|
45
|
+
cachedConfig.config.build.command = settings?.buildCommand
|
|
46
|
+
cachedConfig.config.build.commandOrigin = 'heuristics'
|
|
47
|
+
}
|
|
39
48
|
|
|
40
|
-
const { cachedConfig, siteInfo } = command.netlify
|
|
41
49
|
const buildOptions = await getBuildOptions({
|
|
42
50
|
cachedConfig,
|
|
43
51
|
token,
|