netlify-cli 15.11.0 → 16.0.0-alpha.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.
- package/bin/run.mjs +6 -5
- package/npm-shrinkwrap.json +628 -42
- package/package.json +4 -5
- package/src/commands/base-command.mjs +295 -118
- package/src/commands/build/build.mjs +9 -1
- package/src/commands/deploy/deploy.mjs +42 -18
- 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 +3 -3
- package/src/commands/functions/functions-serve.mjs +1 -0
- 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/javascript/google-analytics/package.json +1 -1
- package/src/functions-templates/typescript/scheduled-function/package.json +1 -1
- package/src/lib/edge-functions/deploy.mjs +11 -4
- package/src/lib/edge-functions/internal.mjs +5 -3
- package/src/lib/edge-functions/proxy.mjs +29 -5
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +20 -3
- 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/deploy/deploy-site.mjs +4 -4
- package/src/utils/deploy/hash-fns.mjs +2 -2
- 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 +68 -68
- 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/init/frameworks.mjs +0 -23
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
3
|
"description": "Netlify command line tool",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "16.0.0-alpha.0",
|
|
5
5
|
"author": "Netlify Inc.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": "
|
|
8
|
+
"node": ">=16.16.0"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"/bin",
|
|
@@ -44,11 +44,10 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@bugsnag/js": "7.20.2",
|
|
46
46
|
"@fastify/static": "6.10.2",
|
|
47
|
-
"@netlify/build": "29.
|
|
47
|
+
"@netlify/build": "29.19.0",
|
|
48
48
|
"@netlify/build-info": "7.7.3",
|
|
49
|
-
"@netlify/config": "20.
|
|
49
|
+
"@netlify/config": "20.8.0",
|
|
50
50
|
"@netlify/edge-bundler": "8.17.1",
|
|
51
|
-
"@netlify/framework-info": "9.8.10",
|
|
52
51
|
"@netlify/local-functions-proxy": "1.1.1",
|
|
53
52
|
"@netlify/serverless-functions-api": "1.5.2",
|
|
54
53
|
"@netlify/zip-it-and-ship-it": "9.13.1",
|
|
@@ -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(['api', '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 the 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 its 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,56 @@ 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
|
)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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 `--filter` option to commands that are workspace aware
|
|
201
|
+
if (!COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(name)) {
|
|
202
|
+
base.option('--filter <app>', 'For monorepos, specify the name of the application to run the command in')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return base.hook('preAction', async (_parentCommand, actionCommand) => {
|
|
206
|
+
if (actionCommand.opts()?.debug) {
|
|
207
|
+
process.env.DEBUG = '*'
|
|
208
|
+
}
|
|
209
|
+
debug(`${name}:preAction`)('start')
|
|
210
|
+
this.analytics = { startTime: process.hrtime.bigint() }
|
|
211
|
+
// @ts-ignore cannot type actionCommand as BaseCommand
|
|
212
|
+
await this.init(actionCommand)
|
|
213
|
+
debug(`${name}:preAction`)('end')
|
|
214
|
+
})
|
|
141
215
|
}
|
|
142
216
|
|
|
143
217
|
/** @private */
|
|
@@ -149,7 +223,7 @@ export default class BaseCommand extends Command {
|
|
|
149
223
|
return this
|
|
150
224
|
}
|
|
151
225
|
|
|
152
|
-
/** The examples list for the command (used inside doc generation and help page) */
|
|
226
|
+
/** @type {string[]} The examples list for the command (used inside doc generation and help page) */
|
|
153
227
|
examples = []
|
|
154
228
|
|
|
155
229
|
/**
|
|
@@ -172,23 +246,27 @@ export default class BaseCommand extends Command {
|
|
|
172
246
|
const term =
|
|
173
247
|
this.name() === 'netlify'
|
|
174
248
|
? `${HELP_$} ${command.name()} [COMMAND]`
|
|
175
|
-
: `${HELP_$} ${command.parent
|
|
249
|
+
: `${HELP_$} ${command.parent?.name()} ${command.name()} ${command.usage()}`
|
|
176
250
|
|
|
177
251
|
return padLeft(term, HELP_INDENT_WIDTH)
|
|
178
252
|
}
|
|
179
253
|
|
|
254
|
+
/**
|
|
255
|
+
* @param {BaseCommand} command
|
|
256
|
+
*/
|
|
180
257
|
const getCommands = (command) => {
|
|
181
258
|
const parentCommand = this.name() === 'netlify' ? command : command.parent
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
259
|
+
return (
|
|
260
|
+
parentCommand?.commands.filter((cmd) => {
|
|
261
|
+
if (cmd._hidden) return false
|
|
262
|
+
// the root command
|
|
263
|
+
if (this.name() === 'netlify') {
|
|
264
|
+
// don't include subcommands on the main page
|
|
265
|
+
return !cmd.name().includes(':')
|
|
266
|
+
}
|
|
267
|
+
return cmd.name().startsWith(`${command.name()}:`)
|
|
268
|
+
}) || []
|
|
269
|
+
)
|
|
192
270
|
}
|
|
193
271
|
|
|
194
272
|
/**
|
|
@@ -281,9 +359,8 @@ export default class BaseCommand extends Command {
|
|
|
281
359
|
}
|
|
282
360
|
|
|
283
361
|
// Aliases
|
|
284
|
-
|
|
362
|
+
|
|
285
363
|
if (command._aliases.length !== 0) {
|
|
286
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
287
364
|
const aliases = command._aliases.map((alias) => formatItem(`${parentCommand.name()} ${alias}`, null, true))
|
|
288
365
|
output = [...output, chalk.bold('ALIASES'), formatHelpList(aliases), '']
|
|
289
366
|
}
|
|
@@ -337,6 +414,11 @@ export default class BaseCommand extends Command {
|
|
|
337
414
|
}
|
|
338
415
|
}
|
|
339
416
|
|
|
417
|
+
/**
|
|
418
|
+
*
|
|
419
|
+
* @param {string|undefined} tokenFromFlag
|
|
420
|
+
* @returns
|
|
421
|
+
*/
|
|
340
422
|
async authenticate(tokenFromFlag) {
|
|
341
423
|
const [token] = await getToken(tokenFromFlag)
|
|
342
424
|
if (token) {
|
|
@@ -406,6 +488,10 @@ export default class BaseCommand extends Command {
|
|
|
406
488
|
return accessToken
|
|
407
489
|
}
|
|
408
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Adds some data to the analytics payload
|
|
493
|
+
* @param {Record<string, unknown>} payload
|
|
494
|
+
*/
|
|
409
495
|
setAnalyticsPayload(payload) {
|
|
410
496
|
const newPayload = { ...this.analytics.payload, ...payload }
|
|
411
497
|
this.analytics = { ...this.analytics, payload: newPayload }
|
|
@@ -418,12 +504,58 @@ export default class BaseCommand extends Command {
|
|
|
418
504
|
*/
|
|
419
505
|
async init(actionCommand) {
|
|
420
506
|
debug(`${actionCommand.name()}:init`)('start')
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
//
|
|
424
|
-
|
|
507
|
+
const flags = actionCommand.opts()
|
|
508
|
+
// here we actually want to use the process.cwd as we are setting the workingDir
|
|
509
|
+
// eslint-disable-next-line workspace/no-process-cwd
|
|
510
|
+
this.workingDir = flags.cwd || process.cwd()
|
|
511
|
+
|
|
512
|
+
// ==================================================
|
|
513
|
+
// Create a Project and run the Heuristics to detect
|
|
514
|
+
// if we are running inside a monorepo or not.
|
|
515
|
+
// ==================================================
|
|
516
|
+
|
|
517
|
+
// retrieve the repository root
|
|
518
|
+
const rootDir = await getRepositoryRoot()
|
|
519
|
+
// Get framework, add to analytics payload for every command, if a framework is set
|
|
520
|
+
const fs = new NodeFS()
|
|
521
|
+
// disable logging inside the project and FS if not in debug mode
|
|
522
|
+
fs.logger = actionCommand.opts()?.debug ? new DefaultLogger('debug') : new NoopLogger()
|
|
523
|
+
this.project = new Project(fs, this.workingDir, rootDir)
|
|
524
|
+
.setEnvironment(process.env)
|
|
525
|
+
.setNodeVersion(process.version)
|
|
526
|
+
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
|
527
|
+
.setReportFn((err, reportConfig) => {
|
|
528
|
+
reportError(err, {
|
|
529
|
+
severity: reportConfig?.severity || 'error',
|
|
530
|
+
metadata: reportConfig?.metadata,
|
|
531
|
+
})
|
|
532
|
+
})
|
|
533
|
+
const frameworks = await this.project.detectFrameworks()
|
|
534
|
+
/** @type { string|undefined} */
|
|
535
|
+
let packageConfig = flags.config ? resolve(flags.config) : undefined
|
|
536
|
+
// check if we have detected multiple projects inside which one we have to perform our operations.
|
|
537
|
+
// only ask to select one if on the workspace root
|
|
538
|
+
if (
|
|
539
|
+
!COMMANDS_WITHOUT_WORKSPACE_OPTIONS.has(actionCommand.name()) &&
|
|
540
|
+
this.project.workspace?.packages.length &&
|
|
541
|
+
this.project.workspace.isRoot
|
|
542
|
+
) {
|
|
543
|
+
this.workspacePackage = await selectWorkspace(this.project, actionCommand.opts().filter)
|
|
544
|
+
this.workingDir = join(this.project.jsWorkspaceRoot, this.workspacePackage)
|
|
545
|
+
}
|
|
425
546
|
|
|
426
|
-
|
|
547
|
+
this.jsWorkspaceRoot = this.project.jsWorkspaceRoot
|
|
548
|
+
// detect if a toml exists in this package.
|
|
549
|
+
const tomlFile = join(this.workingDir, 'netlify.toml')
|
|
550
|
+
if (!packageConfig && existsSync(tomlFile)) {
|
|
551
|
+
packageConfig = tomlFile
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ==================================================
|
|
555
|
+
// Retrieve Site id and build state from the state.json
|
|
556
|
+
// ==================================================
|
|
557
|
+
const state = new StateConfig(this.workingDir)
|
|
558
|
+
const [token] = await getToken(flags.auth)
|
|
427
559
|
|
|
428
560
|
const apiUrlOpts = {
|
|
429
561
|
userAgent: USER_AGENT,
|
|
@@ -437,12 +569,25 @@ export default class BaseCommand extends Command {
|
|
|
437
569
|
process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname
|
|
438
570
|
}
|
|
439
571
|
|
|
440
|
-
|
|
572
|
+
// ==================================================
|
|
573
|
+
// Start retrieving the configuration through the
|
|
574
|
+
// configuration file and the API
|
|
575
|
+
// ==================================================
|
|
576
|
+
const cachedConfig = await actionCommand.getConfig({
|
|
577
|
+
cwd: this.jsWorkspaceRoot || this.workingDir,
|
|
578
|
+
repositoryRoot: rootDir,
|
|
579
|
+
packagePath: this.workspacePackage,
|
|
580
|
+
// The config flag needs to be resolved from the actual process working directory
|
|
581
|
+
configFilePath: packageConfig,
|
|
582
|
+
state,
|
|
583
|
+
token,
|
|
584
|
+
...apiUrlOpts,
|
|
585
|
+
})
|
|
441
586
|
const { buildDir, config, configPath, repositoryRoot, siteInfo } = cachedConfig
|
|
442
587
|
const normalizedConfig = normalizeConfig(config)
|
|
443
588
|
const agent = await getAgent({
|
|
444
|
-
httpProxy:
|
|
445
|
-
certificateFile:
|
|
589
|
+
httpProxy: flags.httpProxy,
|
|
590
|
+
certificateFile: flags.httpProxyCertificateFilename,
|
|
446
591
|
})
|
|
447
592
|
const apiOpts = { ...apiUrlOpts, agent }
|
|
448
593
|
const api = new NetlifyAPI(token || '', apiOpts)
|
|
@@ -454,33 +599,44 @@ export default class BaseCommand extends Command {
|
|
|
454
599
|
// options.site as a site name (and not just site id) was introduced for the deploy command, so users could
|
|
455
600
|
// deploy by name along with by id
|
|
456
601
|
let siteData = siteInfo
|
|
457
|
-
if (!siteData.url &&
|
|
458
|
-
siteData = await getSiteByName(api,
|
|
602
|
+
if (!siteData.url && flags.site) {
|
|
603
|
+
siteData = await getSiteByName(api, flags.site)
|
|
459
604
|
}
|
|
460
605
|
|
|
461
606
|
const globalConfig = await getGlobalConfig()
|
|
462
607
|
|
|
463
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
const frameworks = await project.detectFrameworks()
|
|
467
|
-
|
|
608
|
+
// ==================================================
|
|
609
|
+
// Perform analytics reporting
|
|
610
|
+
// ==================================================
|
|
468
611
|
const frameworkIDs = frameworks?.map((framework) => framework.id)
|
|
469
|
-
|
|
470
612
|
if (frameworkIDs?.length !== 0) {
|
|
471
613
|
this.setAnalyticsPayload({ frameworks: frameworkIDs })
|
|
472
614
|
}
|
|
473
|
-
|
|
474
615
|
this.setAnalyticsPayload({
|
|
475
|
-
|
|
476
|
-
|
|
616
|
+
monorepo: Boolean(this.project.workspace),
|
|
617
|
+
packageManager: this.project.packageManager?.name,
|
|
618
|
+
buildSystem: this.project.buildSystems.map(({ id }) => id),
|
|
477
619
|
})
|
|
478
620
|
|
|
621
|
+
// set the project and the netlify api object on the command,
|
|
622
|
+
// to be accessible inside each command.
|
|
623
|
+
actionCommand.project = this.project
|
|
624
|
+
actionCommand.workingDir = this.workingDir
|
|
625
|
+
actionCommand.workspacePackage = this.workspacePackage
|
|
626
|
+
actionCommand.jsWorkspaceRoot = this.jsWorkspaceRoot
|
|
627
|
+
|
|
628
|
+
// Either an existing configuration file from `@netlify/config` or a file path
|
|
629
|
+
// that should be used for creating it.
|
|
630
|
+
const configFilePath = configPath || join(this.workingDir, 'netlify.toml')
|
|
631
|
+
|
|
479
632
|
actionCommand.netlify = {
|
|
480
633
|
// api methods
|
|
481
634
|
api,
|
|
482
635
|
apiOpts,
|
|
636
|
+
// The absolute repository root (detected through @netlify/config)
|
|
483
637
|
repositoryRoot,
|
|
638
|
+
configFilePath,
|
|
639
|
+
relConfigFilePath: relative(repositoryRoot, configFilePath),
|
|
484
640
|
// current site context
|
|
485
641
|
site: {
|
|
486
642
|
root: buildDir,
|
|
@@ -508,26 +664,38 @@ export default class BaseCommand extends Command {
|
|
|
508
664
|
|
|
509
665
|
/**
|
|
510
666
|
* Find and resolve the Netlify configuration
|
|
511
|
-
* @param {
|
|
512
|
-
* @
|
|
667
|
+
* @param {object} config
|
|
668
|
+
* @param {string} config.cwd
|
|
669
|
+
* @param {string|null=} config.token
|
|
670
|
+
* @param {*} config.state
|
|
671
|
+
* @param {boolean=} config.offline
|
|
672
|
+
* @param {string=} config.configFilePath An optional path to the netlify configuration file e.g. netlify.toml
|
|
673
|
+
* @param {string=} config.packagePath
|
|
674
|
+
* @param {string=} config.repositoryRoot
|
|
675
|
+
* @param {string=} config.host
|
|
676
|
+
* @param {string=} config.pathPrefix
|
|
677
|
+
* @param {string=} config.scheme
|
|
678
|
+
* @returns {ReturnType<typeof resolveConfig>}
|
|
513
679
|
*/
|
|
514
680
|
async getConfig(config) {
|
|
515
|
-
|
|
516
|
-
const
|
|
681
|
+
// the flags that are passed to the command like `--debug` or `--offline`
|
|
682
|
+
const flags = this.opts()
|
|
517
683
|
|
|
518
684
|
try {
|
|
519
685
|
return await resolveConfig({
|
|
520
|
-
config:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
686
|
+
config: config.configFilePath,
|
|
687
|
+
packagePath: config.packagePath,
|
|
688
|
+
repositoryRoot: config.repositoryRoot,
|
|
689
|
+
cwd: config.cwd,
|
|
690
|
+
context: flags.context || process.env.CONTEXT || this.getDefaultContext(),
|
|
691
|
+
debug: flags.debug,
|
|
692
|
+
siteId: flags.siteId || (typeof flags.site === 'string' && flags.site) || config.state.get('siteId'),
|
|
693
|
+
token: config.token,
|
|
526
694
|
mode: 'cli',
|
|
527
|
-
host,
|
|
528
|
-
pathPrefix,
|
|
529
|
-
scheme,
|
|
530
|
-
offline,
|
|
695
|
+
host: config.host,
|
|
696
|
+
pathPrefix: config.pathPrefix,
|
|
697
|
+
scheme: config.scheme,
|
|
698
|
+
offline: config.offline ?? flags.offline,
|
|
531
699
|
siteFeatureFlagPrefix: 'cli',
|
|
532
700
|
})
|
|
533
701
|
} catch (error_) {
|
|
@@ -539,17 +707,17 @@ export default class BaseCommand extends Command {
|
|
|
539
707
|
//
|
|
540
708
|
// @todo Replace this with a mechanism for calling `resolveConfig` with more granularity (i.e. having
|
|
541
709
|
// the option to say that we don't need API data.)
|
|
542
|
-
if (isUserError && !offline && token) {
|
|
543
|
-
if (
|
|
710
|
+
if (isUserError && !config.offline && config.token) {
|
|
711
|
+
if (flags.debug) {
|
|
544
712
|
error(error_, { exit: false })
|
|
545
713
|
warn('Failed to resolve config, falling back to offline resolution')
|
|
546
714
|
}
|
|
547
|
-
|
|
715
|
+
// recursive call with trying to resolve offline
|
|
716
|
+
return this.getConfig({ ...config, offline: true })
|
|
548
717
|
}
|
|
549
718
|
|
|
550
719
|
const message = isUserError ? error_.message : error_.stack
|
|
551
|
-
|
|
552
|
-
exit(1)
|
|
720
|
+
error(message, { exit: true })
|
|
553
721
|
}
|
|
554
722
|
}
|
|
555
723
|
|
|
@@ -558,13 +726,22 @@ export default class BaseCommand extends Command {
|
|
|
558
726
|
* set. The default context is `dev` most of the time, but some commands may
|
|
559
727
|
* wish to override that.
|
|
560
728
|
*
|
|
561
|
-
* @returns {
|
|
729
|
+
* @returns {'production' | 'dev'}
|
|
562
730
|
*/
|
|
563
731
|
getDefaultContext() {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
732
|
+
return this.name() === 'serve' ? 'production' : 'dev'
|
|
733
|
+
}
|
|
734
|
+
}
|
|
567
735
|
|
|
568
|
-
|
|
736
|
+
/**
|
|
737
|
+
* Retrieves the repository root through a git command.
|
|
738
|
+
* Returns undefined if not a git project.
|
|
739
|
+
* @param {string} [cwd] The optional current working directory
|
|
740
|
+
* @returns {Promise<string|undefined>}
|
|
741
|
+
*/
|
|
742
|
+
async function getRepositoryRoot(cwd) {
|
|
743
|
+
const res = await findUp('.git', { cwd, type: 'directory' })
|
|
744
|
+
if (res) {
|
|
745
|
+
return join(res, '..')
|
|
569
746
|
}
|
|
570
747
|
}
|
|
@@ -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,
|