netlify-cli 15.9.1 → 15.10.0-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/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 +22 -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
|
@@ -11,11 +11,11 @@ import { track } from '../../utils/telemetry/index.mjs'
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
*
|
|
14
|
-
* @param {import('../base-command.mjs').
|
|
14
|
+
* @param {import('../base-command.mjs').default} command
|
|
15
15
|
* @param {import('commander').OptionValues} options
|
|
16
16
|
*/
|
|
17
|
-
const linkPrompt = async (
|
|
18
|
-
const { api, state } = netlify
|
|
17
|
+
const linkPrompt = async (command, options) => {
|
|
18
|
+
const { api, state } = command.netlify
|
|
19
19
|
|
|
20
20
|
const SITE_NAME_PROMPT = 'Search by full or partial site name'
|
|
21
21
|
const SITE_LIST_PROMPT = 'Choose from a list of your recently updated sites'
|
|
@@ -24,7 +24,7 @@ const linkPrompt = async (netlify, options) => {
|
|
|
24
24
|
let GIT_REMOTE_PROMPT = 'Use the current git remote origin URL'
|
|
25
25
|
let site
|
|
26
26
|
// Get git remote data if exists
|
|
27
|
-
const repoData = await getRepoData({ remoteName: options.gitRemoteName })
|
|
27
|
+
const repoData = await getRepoData({ workingDir: command.workingDir, remoteName: options.gitRemoteName })
|
|
28
28
|
|
|
29
29
|
let linkChoices = [SITE_NAME_PROMPT, SITE_LIST_PROMPT, SITE_ID_PROMPT]
|
|
30
30
|
|
|
@@ -326,7 +326,7 @@ export const link = async (options, command) => {
|
|
|
326
326
|
kind: 'byName',
|
|
327
327
|
})
|
|
328
328
|
} else {
|
|
329
|
-
siteData = await linkPrompt(command
|
|
329
|
+
siteData = await linkPrompt(command, options)
|
|
330
330
|
}
|
|
331
331
|
return siteData
|
|
332
332
|
}
|
|
@@ -38,6 +38,7 @@ const serve = async (options, command) => {
|
|
|
38
38
|
const devConfig = {
|
|
39
39
|
...(config.functionsDirectory && { functions: config.functionsDirectory }),
|
|
40
40
|
...(config.build.publish && { publish: config.build.publish }),
|
|
41
|
+
|
|
41
42
|
...config.dev,
|
|
42
43
|
...options,
|
|
43
44
|
// Override the `framework` value so that we start a static server and not
|
|
@@ -69,10 +70,9 @@ const serve = async (options, command) => {
|
|
|
69
70
|
// Netlify Build are loaded.
|
|
70
71
|
await getInternalFunctionsDir({ base: site.root, ensureExists: true })
|
|
71
72
|
|
|
72
|
-
/** @type {
|
|
73
|
-
let settings = {}
|
|
73
|
+
let settings = /** @type {import('../../utils/types.js').ServerSettings} */ ({})
|
|
74
74
|
try {
|
|
75
|
-
settings = await detectServerSettings(devConfig, options,
|
|
75
|
+
settings = await detectServerSettings(devConfig, options, command)
|
|
76
76
|
|
|
77
77
|
cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
|
|
78
78
|
} catch (error_) {
|
|
@@ -87,7 +87,11 @@ const serve = async (options, command) => {
|
|
|
87
87
|
`${NETLIFYDEVWARN} Changes will not be hot-reloaded, so if you need to rebuild your site you must exit and run 'netlify serve' again`,
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
-
const { configPath: configPathOverride } = await runBuildTimeline({
|
|
90
|
+
const { configPath: configPathOverride } = await runBuildTimeline({
|
|
91
|
+
command,
|
|
92
|
+
settings,
|
|
93
|
+
options,
|
|
94
|
+
})
|
|
91
95
|
|
|
92
96
|
await startFunctionsServer({
|
|
93
97
|
api,
|
|
@@ -117,8 +121,7 @@ const serve = async (options, command) => {
|
|
|
117
121
|
|
|
118
122
|
// TODO: We should consolidate this with the existing config watcher.
|
|
119
123
|
const getUpdatedConfig = async () => {
|
|
120
|
-
const
|
|
121
|
-
const { config: newConfig } = await command.getConfig({ cwd, offline: true, state })
|
|
124
|
+
const { config: newConfig } = await command.getConfig({ cwd: command.workingDir, offline: true, state })
|
|
122
125
|
const normalizedNewConfig = normalizeConfig(newConfig)
|
|
123
126
|
|
|
124
127
|
return normalizedNewConfig
|
|
@@ -135,6 +138,7 @@ const serve = async (options, command) => {
|
|
|
135
138
|
getUpdatedConfig,
|
|
136
139
|
inspectSettings,
|
|
137
140
|
offline: options.offline,
|
|
141
|
+
projectDir: command.workingDir,
|
|
138
142
|
settings,
|
|
139
143
|
site,
|
|
140
144
|
siteInfo,
|
|
@@ -197,7 +197,7 @@ const sitesCreateTemplate = async (repository, options, command) => {
|
|
|
197
197
|
|
|
198
198
|
if (options.withCi) {
|
|
199
199
|
log('Configuring CI')
|
|
200
|
-
const repoData = await getRepoData()
|
|
200
|
+
const repoData = await getRepoData({ workingDir: command.workingDir })
|
|
201
201
|
await configureRepo({ command, siteId: site.id, repoData, manual: options.manual })
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -102,7 +102,7 @@ export const sitesCreate = async (options, command) => {
|
|
|
102
102
|
|
|
103
103
|
if (options.withCi) {
|
|
104
104
|
log('Configuring CI')
|
|
105
|
-
const repoData = await getRepoData()
|
|
105
|
+
const repoData = await getRepoData({ workingDir: command.workingDir })
|
|
106
106
|
await configureRepo({ command, siteId: site.id, repoData, manual: options.manual })
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"node_modules/@types/node": {
|
|
29
|
-
"version": "14.18.
|
|
30
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.
|
|
31
|
-
"integrity": "sha512-
|
|
29
|
+
"version": "14.18.54",
|
|
30
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz",
|
|
31
|
+
"integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw=="
|
|
32
32
|
},
|
|
33
33
|
"node_modules/is-promise": {
|
|
34
34
|
"version": "4.0.0",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"@types/node": {
|
|
61
|
-
"version": "14.18.
|
|
62
|
-
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.
|
|
63
|
-
"integrity": "sha512-
|
|
61
|
+
"version": "14.18.54",
|
|
62
|
+
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.54.tgz",
|
|
63
|
+
"integrity": "sha512-uq7O52wvo2Lggsx1x21tKZgqkJpvwCseBBPtX/nKQfpVlEsLOb11zZ1CRsWUKvJF0+lzuA9jwvA7Pr2Wt7i3xw=="
|
|
64
64
|
},
|
|
65
65
|
"is-promise": {
|
|
66
66
|
"version": "4.0.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { env } from 'process'
|
|
2
2
|
|
|
3
|
-
const latestBootstrapURL = 'https://
|
|
3
|
+
const latestBootstrapURL = 'https://64c264287e9cbb0008621df3--edge.netlify.com/bootstrap/index-combined.ts'
|
|
4
4
|
|
|
5
5
|
export const getBootstrapURL = () => env.NETLIFY_EDGE_BOOTSTRAP || latestBootstrapURL
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { readFile, stat } from 'fs/promises'
|
|
3
3
|
import { dirname, join, resolve } from 'path'
|
|
4
|
-
import { cwd } from 'process'
|
|
5
4
|
|
|
6
5
|
import { getPathInProject } from '../settings.mjs'
|
|
7
6
|
|
|
8
7
|
import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from './consts.mjs'
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} workingDir
|
|
11
|
+
*/
|
|
12
|
+
export const getInternalFunctions = async (workingDir) => {
|
|
13
|
+
const path = join(workingDir, getPathInProject([INTERNAL_EDGE_FUNCTIONS_FOLDER]))
|
|
12
14
|
|
|
13
15
|
try {
|
|
14
16
|
const stats = await stat(path)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { Buffer } from 'buffer'
|
|
3
3
|
import { relative } from 'path'
|
|
4
|
-
import {
|
|
4
|
+
import { env } from 'process'
|
|
5
5
|
|
|
6
6
|
// eslint-disable-next-line import/no-namespace
|
|
7
7
|
import * as bundler from '@netlify/edge-bundler'
|
|
@@ -62,6 +62,26 @@ export const createAccountInfoHeader = (accountInfo = {}) => {
|
|
|
62
62
|
return Buffer.from(accountString).toString('base64')
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param {object} config
|
|
68
|
+
* @param {*} config.accountId
|
|
69
|
+
* @param {*} config.config
|
|
70
|
+
* @param {*} config.configPath
|
|
71
|
+
* @param {*} config.debug
|
|
72
|
+
* @param {*} config.env
|
|
73
|
+
* @param {*} config.geoCountry
|
|
74
|
+
* @param {*} config.geolocationMode
|
|
75
|
+
* @param {*} config.getUpdatedConfig
|
|
76
|
+
* @param {*} config.inspectSettings
|
|
77
|
+
* @param {*} config.mainPort
|
|
78
|
+
* @param {boolean=} config.offline
|
|
79
|
+
* @param {*} config.passthroughPort
|
|
80
|
+
* @param {*} config.projectDir
|
|
81
|
+
* @param {*} config.siteInfo
|
|
82
|
+
* @param {*} config.state
|
|
83
|
+
* @returns
|
|
84
|
+
*/
|
|
65
85
|
export const initializeProxy = async ({
|
|
66
86
|
accountId,
|
|
67
87
|
config,
|
|
@@ -79,7 +99,11 @@ export const initializeProxy = async ({
|
|
|
79
99
|
siteInfo,
|
|
80
100
|
state,
|
|
81
101
|
}) => {
|
|
82
|
-
const {
|
|
102
|
+
const {
|
|
103
|
+
functions: internalFunctions,
|
|
104
|
+
importMap,
|
|
105
|
+
path: internalFunctionsPath,
|
|
106
|
+
} = await getInternalFunctions(projectDir)
|
|
83
107
|
const userFunctionsPath = config.build.edge_functions
|
|
84
108
|
const isolatePort = await getAvailablePort()
|
|
85
109
|
|
|
@@ -114,7 +138,8 @@ export const initializeProxy = async ({
|
|
|
114
138
|
if (!registry) return
|
|
115
139
|
|
|
116
140
|
// Setting header with geolocation and site info.
|
|
117
|
-
req.headers[headers.Geo] = JSON.stringify(geoLocation)
|
|
141
|
+
req.headers[headers.Geo] = Buffer.from(JSON.stringify(geoLocation)).toString('base64')
|
|
142
|
+
req.headers[headers.DeployID] = '0'
|
|
118
143
|
req.headers[headers.Site] = createSiteInfoHeader(siteInfo)
|
|
119
144
|
req.headers[headers.Account] = createAccountInfoHeader({ id: accountId })
|
|
120
145
|
|
|
@@ -132,7 +157,7 @@ export const initializeProxy = async ({
|
|
|
132
157
|
)} matches declaration for edge function ${chalk.yellow(
|
|
133
158
|
functionName,
|
|
134
159
|
)}, but there's no matching function file in ${chalk.yellow(
|
|
135
|
-
relative(
|
|
160
|
+
relative(projectDir, userFunctionsPath),
|
|
136
161
|
)}. Please visit ${chalk.blue('https://ntl.fyi/edge-create')} for more information.`,
|
|
137
162
|
)
|
|
138
163
|
})
|
|
@@ -3,7 +3,7 @@ import { dirname } from 'path'
|
|
|
3
3
|
import { pathToFileURL } from 'url'
|
|
4
4
|
import { Worker } from 'worker_threads'
|
|
5
5
|
|
|
6
|
-
import lambdaLocal from '
|
|
6
|
+
import lambdaLocal from 'lambda-local'
|
|
7
7
|
import winston from 'winston'
|
|
8
8
|
|
|
9
9
|
import detectNetlifyLambdaBuilder from './builders/netlify-lambda.mjs'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createServer } from 'net'
|
|
2
2
|
import { isMainThread, workerData, parentPort } from 'worker_threads'
|
|
3
3
|
|
|
4
|
-
import lambdaLocal from '@skn0tt/lambda-local'
|
|
5
4
|
import { isStream } from 'is-stream'
|
|
5
|
+
import lambdaLocal from 'lambda-local'
|
|
6
6
|
import sourceMapSupport from 'source-map-support'
|
|
7
7
|
|
|
8
8
|
if (isMainThread) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import { Buffer } from 'buffer'
|
|
3
|
+
|
|
2
4
|
import express from 'express'
|
|
3
5
|
import expressLogging from 'express-logging'
|
|
4
6
|
import jwtDecode from 'jwt-decode'
|
|
@@ -47,7 +49,6 @@ const hasBody = (req) =>
|
|
|
47
49
|
// eslint-disable-next-line unicorn/prefer-number-properties
|
|
48
50
|
(req.header('transfer-encoding') !== undefined || !isNaN(req.header('content-length'))) &&
|
|
49
51
|
// we expect a string or a buffer, because we use the two bodyParsers(text, raw) from express
|
|
50
|
-
// eslint-disable-next-line n/prefer-global/buffer
|
|
51
52
|
(typeof req.body === 'string' || Buffer.isBuffer(req.body))
|
|
52
53
|
|
|
53
54
|
export const createHandler = function (options) {
|
|
@@ -114,7 +115,7 @@ export const createHandler = function (options) {
|
|
|
114
115
|
'client-ip': [remoteAddress],
|
|
115
116
|
'x-nf-client-connection-ip': [remoteAddress],
|
|
116
117
|
'x-nf-account-id': [options.accountId],
|
|
117
|
-
[efHeaders.Geo]: JSON.stringify(geoLocation),
|
|
118
|
+
[efHeaders.Geo]: Buffer.from(JSON.stringify(geoLocation)).toString('base64'),
|
|
118
119
|
}).reduce((prev, [key, value]) => ({ ...prev, [key]: Array.isArray(value) ? value : [value] }), {})
|
|
119
120
|
const rawQuery = new URLSearchParams(requestQuery).toString()
|
|
120
121
|
const protocol = options.config?.dev?.https ? 'https' : 'http'
|
package/src/lib/spinner.mjs
CHANGED
|
@@ -17,7 +17,7 @@ export const startSpinner = ({ text }) =>
|
|
|
17
17
|
* Stops the spinner with the following text
|
|
18
18
|
* @param {object} config
|
|
19
19
|
* @param {ora.Ora} config.spinner
|
|
20
|
-
* @param {
|
|
20
|
+
* @param {boolean} [config.error]
|
|
21
21
|
* @param {string} [config.text]
|
|
22
22
|
* @returns {void}
|
|
23
23
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @ts-check
|
|
1
2
|
import { join } from 'path'
|
|
2
3
|
|
|
3
4
|
import { DenoBridge } from '@netlify/edge-bundler'
|
|
@@ -27,15 +28,24 @@ const getPrompt = ({ fileExists, path }) => {
|
|
|
27
28
|
const getEdgeFunctionsPath = ({ config, repositoryRoot }) =>
|
|
28
29
|
config.build.edge_functions || join(repositoryRoot, 'netlify', 'edge-functions')
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} repositoryRoot
|
|
33
|
+
*/
|
|
30
34
|
const getSettingsPath = (repositoryRoot) => join(repositoryRoot, '.vscode', 'settings.json')
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} repositoryRoot
|
|
38
|
+
*/
|
|
39
|
+
const hasDenoVSCodeExt = async (repositoryRoot) => {
|
|
40
|
+
const { stdout: extensions } = await execa('code', ['--list-extensions'], { stderr: 'inherit', cwd: repositoryRoot })
|
|
34
41
|
return extensions.split('\n').includes('denoland.vscode-deno')
|
|
35
42
|
}
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
/**
|
|
45
|
+
* @param {string} repositoryRoot
|
|
46
|
+
*/
|
|
47
|
+
const getDenoVSCodeExt = async (repositoryRoot) => {
|
|
48
|
+
await execa('code', ['--install-extension', 'denoland.vscode-deno'], { stdio: 'inherit', cwd: repositoryRoot })
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
const getDenoExtPrompt = () => {
|
|
@@ -49,6 +59,12 @@ const getDenoExtPrompt = () => {
|
|
|
49
59
|
})
|
|
50
60
|
}
|
|
51
61
|
|
|
62
|
+
/**
|
|
63
|
+
* @param {object} params
|
|
64
|
+
* @param {*} params.config
|
|
65
|
+
* @param {string} params.repositoryRoot
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
52
68
|
export const run = async ({ config, repositoryRoot }) => {
|
|
53
69
|
const deno = new DenoBridge({
|
|
54
70
|
onBeforeDownload: () =>
|
|
@@ -66,9 +82,11 @@ export const run = async ({ config, repositoryRoot }) => {
|
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
try {
|
|
69
|
-
if (!(await hasDenoVSCodeExt())) {
|
|
85
|
+
if (!(await hasDenoVSCodeExt(repositoryRoot))) {
|
|
70
86
|
const { confirm: denoExtConfirm } = await getDenoExtPrompt()
|
|
71
|
-
if (denoExtConfirm)
|
|
87
|
+
if (denoExtConfirm) {
|
|
88
|
+
getDenoVSCodeExt(repositoryRoot)
|
|
89
|
+
}
|
|
72
90
|
}
|
|
73
91
|
} catch {
|
|
74
92
|
log(
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fuzzy from 'fuzzy'
|
|
4
|
+
import inquirer from 'inquirer'
|
|
5
|
+
|
|
6
|
+
import { chalk, log } from './command-helpers.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Filters the inquirer settings based on the input
|
|
10
|
+
* @param {ReturnType<typeof formatSettingsArrForInquirer>} scriptInquirerOptions
|
|
11
|
+
* @param {string} input
|
|
12
|
+
*/
|
|
13
|
+
const filterSettings = function (scriptInquirerOptions, input) {
|
|
14
|
+
const filterOptions = scriptInquirerOptions.map((scriptInquirerOption) => scriptInquirerOption.name)
|
|
15
|
+
// TODO: remove once https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1394 is fixed
|
|
16
|
+
// eslint-disable-next-line unicorn/no-array-method-this-argument
|
|
17
|
+
const filteredSettings = fuzzy.filter(input, filterOptions)
|
|
18
|
+
const filteredSettingNames = new Set(
|
|
19
|
+
filteredSettings.map((filteredSetting) => (input ? filteredSetting.string : filteredSetting)),
|
|
20
|
+
)
|
|
21
|
+
return scriptInquirerOptions.filter((t) => filteredSettingNames.has(t.name))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** @typedef {import('@netlify/build-info').Settings} Settings */
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {Settings[]} settings
|
|
28
|
+
* @param {'dev' | 'build'} type The type of command (dev or build)
|
|
29
|
+
*/
|
|
30
|
+
const formatSettingsArrForInquirer = function (settings, type = 'dev') {
|
|
31
|
+
return settings.map((setting) => {
|
|
32
|
+
const cmd = type === 'dev' ? setting.devCommand : setting.buildCommand
|
|
33
|
+
return {
|
|
34
|
+
name: `[${chalk.yellow(setting.framework.name)}] '${cmd}'`,
|
|
35
|
+
value: { ...setting, commands: [cmd] },
|
|
36
|
+
short: `${setting.name}-${cmd}`,
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Uses @netlify/build-info to detect the dev settings and port based on the framework
|
|
43
|
+
* and the build system that is used.
|
|
44
|
+
* @param {import('../commands/base-command.mjs').default} command
|
|
45
|
+
* @param {'dev' | 'build'} type The type of command (dev or build)
|
|
46
|
+
* @returns {Promise<Settings | undefined>}
|
|
47
|
+
*/
|
|
48
|
+
export const detectFrameworkSettings = async (command, type = 'dev') => {
|
|
49
|
+
const { relConfigFilePath } = command.netlify
|
|
50
|
+
const settings = await detectBuildSettings(command)
|
|
51
|
+
if (settings.length === 1) {
|
|
52
|
+
return settings[0]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (settings.length > 1) {
|
|
56
|
+
/** multiple matching detectors, make the user choose */
|
|
57
|
+
const scriptInquirerOptions = formatSettingsArrForInquirer(settings, type)
|
|
58
|
+
/** @type {{chosenSettings: Settings}} */
|
|
59
|
+
const { chosenSettings } = await inquirer.prompt({
|
|
60
|
+
name: 'chosenSettings',
|
|
61
|
+
message: `Multiple possible ${type} commands found`,
|
|
62
|
+
type: 'autocomplete',
|
|
63
|
+
source(/** @type {string} */ _, input = '') {
|
|
64
|
+
if (!input) return scriptInquirerOptions
|
|
65
|
+
// only show filtered results
|
|
66
|
+
return filterSettings(scriptInquirerOptions, input)
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
log(`
|
|
71
|
+
Update your ${relConfigFilePath} to avoid this selection prompt next time:
|
|
72
|
+
|
|
73
|
+
[build]
|
|
74
|
+
command = "${chosenSettings.buildCommand}"
|
|
75
|
+
publish = "${chosenSettings.dist}"
|
|
76
|
+
|
|
77
|
+
[dev]
|
|
78
|
+
command = "${chosenSettings.devCommand}"
|
|
79
|
+
`)
|
|
80
|
+
return chosenSettings
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Detects and filters the build setting for a project and a command
|
|
86
|
+
* @param {import('../commands/base-command.mjs').default} command
|
|
87
|
+
*/
|
|
88
|
+
export const detectBuildSettings = async (command) => {
|
|
89
|
+
const { project, workspacePackage } = command
|
|
90
|
+
const buildSettings = await project.getBuildSettings(project.workspace ? workspacePackage : '')
|
|
91
|
+
return buildSettings
|
|
92
|
+
.filter((setting) => {
|
|
93
|
+
if (project.workspace && project.relativeBaseDirectory && setting.packagePath) {
|
|
94
|
+
return project.relativeBaseDirectory.startsWith(setting.packagePath)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return true
|
|
98
|
+
})
|
|
99
|
+
.filter((setting) => setting.devCommand)
|
|
100
|
+
}
|
|
@@ -24,7 +24,7 @@ const argv = process.argv.slice(2)
|
|
|
24
24
|
* Chalk instance for CLI that can be initialized with no colors mode
|
|
25
25
|
* needed for json outputs where we don't want to have colors
|
|
26
26
|
* @param {boolean} noColors - disable chalk colors
|
|
27
|
-
* @return {
|
|
27
|
+
* @return {import('chalk').ChalkInstance} - default or custom chalk instance
|
|
28
28
|
*/
|
|
29
29
|
const safeChalk = function (noColors) {
|
|
30
30
|
if (noColors) {
|
|
@@ -174,12 +174,18 @@ export const warn = (message = '') => {
|
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
176
|
* throws an error or log it
|
|
177
|
-
* @param {
|
|
177
|
+
* @param {unknown} message
|
|
178
178
|
* @param {object} [options]
|
|
179
179
|
* @param {boolean} [options.exit]
|
|
180
180
|
*/
|
|
181
181
|
export const error = (message = '', options = {}) => {
|
|
182
|
-
const err =
|
|
182
|
+
const err =
|
|
183
|
+
message instanceof Error
|
|
184
|
+
? message
|
|
185
|
+
: // eslint-disable-next-line unicorn/no-nested-ternary
|
|
186
|
+
typeof message === 'string'
|
|
187
|
+
? new Error(message)
|
|
188
|
+
: /** @type {Error} */ ({ message, stack: undefined, name: 'Error' })
|
|
183
189
|
|
|
184
190
|
if (options.exit === false) {
|
|
185
191
|
const bang = chalk.red(BANG)
|
|
@@ -198,10 +204,13 @@ export const exit = (code = 0) => {
|
|
|
198
204
|
process.exit(code)
|
|
199
205
|
}
|
|
200
206
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
207
|
+
/**
|
|
208
|
+
* When `build.publish` is not set by the user, the CLI behavior differs in
|
|
209
|
+
* several ways. It detects it by checking if `build.publish` is `undefined`.
|
|
210
|
+
* However, `@netlify/config` adds a default value to `build.publish`.
|
|
211
|
+
* This removes 'publish' and 'publishOrigin' in this case.
|
|
212
|
+
* @param {*} config
|
|
213
|
+
*/
|
|
205
214
|
export const normalizeConfig = (config) => {
|
|
206
215
|
// Unused var here is in order to omit 'publish' from build config
|
|
207
216
|
// eslint-disable-next-line no-unused-vars
|