netlify-cli 15.9.0 → 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.
- package/npm-shrinkwrap.json +749 -147
- package/package.json +6 -7
- package/scripts/postinstall.mjs +8 -8
- package/src/commands/base-command.mjs +195 -59
- package/src/commands/deploy/deploy.mjs +21 -9
- package/src/commands/dev/dev.mjs +21 -15
- package/src/commands/functions/functions-build.mjs +2 -2
- package/src/commands/functions/functions-create.mjs +0 -3
- package/src/commands/functions/functions-invoke.mjs +8 -5
- package/src/commands/init/init.mjs +1 -1
- package/src/commands/link/link.mjs +5 -5
- package/src/commands/main.mjs +1 -1
- package/src/commands/serve/serve.mjs +11 -5
- package/src/commands/sites/sites-create-template.mjs +1 -1
- package/src/commands/sites/sites-create.mjs +1 -1
- package/src/lib/completion/generate-autocompletion.mjs +4 -4
- package/src/lib/edge-functions/bootstrap.mjs +1 -1
- package/src/lib/edge-functions/internal.mjs +5 -3
- package/src/lib/edge-functions/proxy.mjs +30 -5
- package/src/lib/functions/registry.mjs +1 -3
- package/src/lib/functions/runtimes/js/builders/zisi.mjs +3 -8
- package/src/lib/functions/server.mjs +6 -6
- package/src/lib/spinner.mjs +1 -1
- package/src/recipes/vscode/index.mjs +1 -1
- package/src/utils/build-info.mjs +19 -0
- package/src/utils/command-helpers.mjs +16 -7
- package/src/utils/deploy/hash-fns.mjs +1 -2
- package/src/utils/detect-server-settings.mjs +148 -200
- package/src/utils/execa.mjs +4 -2
- package/src/utils/framework-server.mjs +2 -2
- package/src/utils/functions/functions.mjs +7 -0
- package/src/utils/functions/get-functions.mjs +2 -2
- 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 -0
- package/src/utils/run-build.mjs +58 -7
- package/src/utils/shell.mjs +14 -3
- 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.9.0",
|
|
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.
|
|
48
|
-
"@netlify/build-info": "7.
|
|
49
|
-
"@netlify/config": "20.6.
|
|
47
|
+
"@netlify/build": "29.17.1",
|
|
48
|
+
"@netlify/build-info": "7.7.1",
|
|
49
|
+
"@netlify/config": "20.6.4",
|
|
50
50
|
"@netlify/edge-bundler": "8.16.4",
|
|
51
|
-
"@netlify/framework-info": "9.8.10",
|
|
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
55
|
"@skn0tt/lambda-local": "2.0.3",
|
|
57
56
|
"ansi-escapes": "6.2.0",
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -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 =
|
|
17
|
+
const format = (message, styles) => {
|
|
12
18
|
let func = id
|
|
13
19
|
try {
|
|
14
|
-
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
422
|
-
|
|
423
|
-
//
|
|
424
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
445
|
-
certificateFile:
|
|
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 &&
|
|
458
|
-
siteData = await getSiteByName(api,
|
|
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
|
-
//
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
476
|
-
|
|
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 {
|
|
512
|
-
* @
|
|
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
|
-
|
|
516
|
-
const
|
|
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:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
package/src/commands/dev/dev.mjs
CHANGED
|
@@ -9,7 +9,6 @@ import { printBanner } from '../../utils/banner.mjs'
|
|
|
9
9
|
import {
|
|
10
10
|
BANG,
|
|
11
11
|
chalk,
|
|
12
|
-
exit,
|
|
13
12
|
log,
|
|
14
13
|
NETLIFYDEV,
|
|
15
14
|
NETLIFYDEVERR,
|
|
@@ -35,7 +34,7 @@ import { createDevExecCommand } from './dev-exec.mjs'
|
|
|
35
34
|
* @param {object} config
|
|
36
35
|
* @param {*} config.api
|
|
37
36
|
* @param {import('commander').OptionValues} config.options
|
|
38
|
-
* @param {
|
|
37
|
+
* @param {import('../../utils/types.js').ServerSettings} config.settings
|
|
39
38
|
* @param {*} config.site
|
|
40
39
|
* @param {*} config.state
|
|
41
40
|
* @returns
|
|
@@ -68,6 +67,9 @@ const handleLiveTunnel = async ({ api, options, settings, site, state }) => {
|
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} args
|
|
72
|
+
*/
|
|
71
73
|
const validateShortFlagArgs = (args) => {
|
|
72
74
|
if (args.startsWith('=')) {
|
|
73
75
|
throw new Error(
|
|
@@ -94,9 +96,10 @@ const dev = async (options, command) => {
|
|
|
94
96
|
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
|
|
95
97
|
config.dev = { ...config.dev }
|
|
96
98
|
config.build = { ...config.build }
|
|
97
|
-
/** @type {import('./types').DevConfig} */
|
|
99
|
+
/** @type {import('./types.js').DevConfig} */
|
|
98
100
|
const devConfig = {
|
|
99
101
|
framework: '#auto',
|
|
102
|
+
autoLaunch: Boolean(options.open),
|
|
100
103
|
...(config.functionsDirectory && { functions: config.functionsDirectory }),
|
|
101
104
|
...(config.build.publish && { publish: config.build.publish }),
|
|
102
105
|
...config.dev,
|
|
@@ -124,20 +127,17 @@ const dev = async (options, command) => {
|
|
|
124
127
|
siteInfo,
|
|
125
128
|
})
|
|
126
129
|
|
|
127
|
-
/** @type {
|
|
128
|
-
let settings
|
|
130
|
+
/** @type {import('../../utils/types.js').ServerSettings} */
|
|
131
|
+
let settings
|
|
129
132
|
try {
|
|
130
|
-
settings = await detectServerSettings(devConfig, options,
|
|
131
|
-
site: {
|
|
132
|
-
id: site.id,
|
|
133
|
-
url: siteUrl,
|
|
134
|
-
},
|
|
135
|
-
})
|
|
133
|
+
settings = await detectServerSettings(devConfig, options, command)
|
|
136
134
|
|
|
137
135
|
cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
|
|
138
136
|
} catch (error_) {
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
if (error_ && typeof error_ === 'object' && 'message' in error_) {
|
|
138
|
+
log(NETLIFYDEVERR, error_.message)
|
|
139
|
+
}
|
|
140
|
+
process.exit(1)
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
command.setAnalyticsPayload({ live: options.live })
|
|
@@ -154,6 +154,7 @@ const dev = async (options, command) => {
|
|
|
154
154
|
cachedConfig,
|
|
155
155
|
options,
|
|
156
156
|
settings,
|
|
157
|
+
projectDir: command.workingDir,
|
|
157
158
|
site,
|
|
158
159
|
env: {
|
|
159
160
|
URL: url,
|
|
@@ -188,8 +189,11 @@ const dev = async (options, command) => {
|
|
|
188
189
|
|
|
189
190
|
// TODO: We should consolidate this with the existing config watcher.
|
|
190
191
|
const getUpdatedConfig = async () => {
|
|
191
|
-
const
|
|
192
|
-
|
|
192
|
+
const { config: newConfig } = await command.getConfig({
|
|
193
|
+
cwd: command.workingDir,
|
|
194
|
+
offline: true,
|
|
195
|
+
state,
|
|
196
|
+
})
|
|
193
197
|
const normalizedNewConfig = normalizeConfig(newConfig)
|
|
194
198
|
|
|
195
199
|
return normalizedNewConfig
|
|
@@ -202,6 +206,7 @@ const dev = async (options, command) => {
|
|
|
202
206
|
config,
|
|
203
207
|
configPath: configPathOverride,
|
|
204
208
|
debug: options.debug,
|
|
209
|
+
projectDir: command.workingDir,
|
|
205
210
|
env,
|
|
206
211
|
getUpdatedConfig,
|
|
207
212
|
inspectSettings,
|
|
@@ -248,6 +253,7 @@ export const createDevCommand = (program) => {
|
|
|
248
253
|
.argParser((value) => Number.parseInt(value))
|
|
249
254
|
.hideHelp(true),
|
|
250
255
|
)
|
|
256
|
+
.addOption(new Option('--no-open', 'disables the automatic opening of a browser window'))
|
|
251
257
|
.option('--target-port <port>', 'port of target app server', (value) => Number.parseInt(value))
|
|
252
258
|
.option('--framework <name>', 'framework to use. Defaults to #auto which automatically detects a framework')
|
|
253
259
|
.option('-d ,--dir <path>', 'dir with static files')
|