netlify-cli 10.18.0-rc2 → 11.1.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/npm-shrinkwrap.json +156 -107
- package/package.json +4 -4
- package/src/commands/build/build.js +21 -8
- package/src/commands/dev/dev-exec.js +15 -3
- package/src/commands/dev/dev.js +9 -7
- package/src/commands/env/env-get.js +12 -5
- package/src/commands/env/env-import.js +11 -11
- package/src/commands/env/env-list.js +13 -6
- package/src/commands/env/env-set.js +104 -23
- package/src/commands/env/env-unset.js +74 -19
- package/src/functions-templates/javascript/scheduled-function/package.json +1 -1
- package/src/functions-templates/typescript/hello-world/package-lock.json +7 -7
- package/src/functions-templates/typescript/hello-world/package.json +1 -1
- package/src/functions-templates/typescript/scheduled-function/package.json +1 -1
- package/src/lib/build.js +15 -0
- package/src/lib/edge-functions/registry.js +2 -0
- package/src/utils/env/index.js +50 -15
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
const process = require('process')
|
|
2
|
+
|
|
1
3
|
// @ts-check
|
|
2
4
|
const { getBuildOptions, runBuild } = require('../../lib/build')
|
|
3
|
-
const { error, exit, generateNetlifyGraphJWT, getToken } = require('../../utils')
|
|
5
|
+
const { error, exit, generateNetlifyGraphJWT, getEnvelopeEnv, getToken, normalizeContext } = require('../../utils')
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @param {import('../../lib/build').BuildConfig} options
|
|
@@ -15,9 +17,14 @@ const checkOptions = ({ cachedConfig: { siteInfo = {} }, token }) => {
|
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const authlifyTokenId =
|
|
20
|
+
const injectEnv = async function (command, { api, buildOptions, context, site, siteInfo }) {
|
|
21
|
+
const isUsingEnvelope = siteInfo && siteInfo.use_envelope
|
|
22
|
+
const authlifyTokenId = siteInfo && siteInfo.authlify_token_id
|
|
23
|
+
|
|
24
|
+
const { env } = buildOptions.cachedConfig
|
|
25
|
+
if (isUsingEnvelope) {
|
|
26
|
+
buildOptions.cachedConfig.env = await getEnvelopeEnv({ api, context, env, siteInfo })
|
|
27
|
+
}
|
|
21
28
|
|
|
22
29
|
if (authlifyTokenId) {
|
|
23
30
|
const netlifyToken = await command.authenticate()
|
|
@@ -48,12 +55,12 @@ const injectNetlifyGraphEnv = async function (command, { api, buildOptions, site
|
|
|
48
55
|
*/
|
|
49
56
|
const build = async (options, command) => {
|
|
50
57
|
command.setAnalyticsPayload({ dry: options.dry })
|
|
51
|
-
|
|
52
58
|
// Retrieve Netlify Build options
|
|
53
59
|
const [token] = await getToken()
|
|
54
60
|
|
|
61
|
+
const { cachedConfig, siteInfo } = command.netlify
|
|
55
62
|
const buildOptions = await getBuildOptions({
|
|
56
|
-
cachedConfig
|
|
63
|
+
cachedConfig,
|
|
57
64
|
token,
|
|
58
65
|
options,
|
|
59
66
|
})
|
|
@@ -61,7 +68,8 @@ const build = async (options, command) => {
|
|
|
61
68
|
if (!options.offline) {
|
|
62
69
|
checkOptions(buildOptions)
|
|
63
70
|
const { api, site } = command.netlify
|
|
64
|
-
|
|
71
|
+
const { context } = options
|
|
72
|
+
await injectEnv(command, { api, buildOptions, context, site, siteInfo })
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
const { exitCode } = await runBuild(buildOptions)
|
|
@@ -77,8 +85,13 @@ const createBuildCommand = (program) =>
|
|
|
77
85
|
program
|
|
78
86
|
.command('build')
|
|
79
87
|
.description('(Beta) Build on your local machine')
|
|
88
|
+
.option(
|
|
89
|
+
'--context <context>',
|
|
90
|
+
'Specify a build context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
|
|
91
|
+
normalizeContext,
|
|
92
|
+
process.env.CONTEXT || 'production',
|
|
93
|
+
)
|
|
80
94
|
.option('--dry', 'Dry run: show instructions without running them', false)
|
|
81
|
-
.option('--context [context]', 'Build context')
|
|
82
95
|
.option('-o, --offline', 'disables any features that require network access', false)
|
|
83
96
|
.addExamples(['netlify build'])
|
|
84
97
|
.action(build)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const execa = require('execa')
|
|
2
2
|
|
|
3
|
-
const { injectEnvVariables } = require('../../utils')
|
|
3
|
+
const { getEnvelopeEnv, injectEnvVariables, normalizeContext } = require('../../utils')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The dev:exec command
|
|
@@ -8,8 +8,14 @@ const { injectEnvVariables } = require('../../utils')
|
|
|
8
8
|
* @param {import('../base-command').BaseCommand} command
|
|
9
9
|
*/
|
|
10
10
|
const devExec = async (cmd, options, command) => {
|
|
11
|
-
const { cachedConfig, config, site } = command.netlify
|
|
12
|
-
|
|
11
|
+
const { api, cachedConfig, config, site, siteInfo } = command.netlify
|
|
12
|
+
|
|
13
|
+
let { env } = cachedConfig
|
|
14
|
+
if (siteInfo.use_envelope) {
|
|
15
|
+
env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await injectEnvVariables({ devConfig: { ...config.dev }, env, site })
|
|
13
19
|
|
|
14
20
|
await execa(cmd, command.args.slice(1), {
|
|
15
21
|
stdio: 'inherit',
|
|
@@ -25,6 +31,12 @@ const createDevExecCommand = (program) =>
|
|
|
25
31
|
program
|
|
26
32
|
.command('dev:exec')
|
|
27
33
|
.argument('<...cmd>', `the command that should be executed`)
|
|
34
|
+
.option(
|
|
35
|
+
'--context <context>',
|
|
36
|
+
'Specify a deploy context or branch for environment variables (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
|
|
37
|
+
normalizeContext,
|
|
38
|
+
'dev',
|
|
39
|
+
)
|
|
28
40
|
.description(
|
|
29
41
|
'Exec command\nRuns a command within the netlify dev environment, e.g. with env variables from any installed addons',
|
|
30
42
|
)
|
package/src/commands/dev/dev.js
CHANGED
|
@@ -44,6 +44,7 @@ const {
|
|
|
44
44
|
injectEnvVariables,
|
|
45
45
|
log,
|
|
46
46
|
normalizeConfig,
|
|
47
|
+
normalizeContext,
|
|
47
48
|
openBrowser,
|
|
48
49
|
processOnExit,
|
|
49
50
|
startLiveTunnel,
|
|
@@ -433,7 +434,7 @@ const dev = async (options, command) => {
|
|
|
433
434
|
}
|
|
434
435
|
|
|
435
436
|
let { env } = cachedConfig
|
|
436
|
-
if (siteInfo.use_envelope) {
|
|
437
|
+
if (!options.offline && siteInfo.use_envelope) {
|
|
437
438
|
env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
|
|
438
439
|
}
|
|
439
440
|
|
|
@@ -499,7 +500,7 @@ const dev = async (options, command) => {
|
|
|
499
500
|
log(`${NETLIFYDEVWARN} Setting up local development server`)
|
|
500
501
|
|
|
501
502
|
const devCommand = () => startFrameworkServer({ settings })
|
|
502
|
-
const startDevOptions =
|
|
503
|
+
const startDevOptions = getBuildOptions({
|
|
503
504
|
cachedConfig,
|
|
504
505
|
options,
|
|
505
506
|
})
|
|
@@ -647,7 +648,7 @@ const dev = async (options, command) => {
|
|
|
647
648
|
printBanner({ url })
|
|
648
649
|
}
|
|
649
650
|
|
|
650
|
-
const getBuildOptions = ({ cachedConfig, options: { context, cwd, debug, dry, offline }, token }) => ({
|
|
651
|
+
const getBuildOptions = ({ cachedConfig, options: { context, cwd = process.cwd(), debug, dry, offline }, token }) => ({
|
|
651
652
|
cachedConfig,
|
|
652
653
|
token,
|
|
653
654
|
dry,
|
|
@@ -675,10 +676,11 @@ const createDevCommand = (program) => {
|
|
|
675
676
|
`Local dev server\nThe dev command will run a local dev server with Netlify's proxy and redirect rules`,
|
|
676
677
|
)
|
|
677
678
|
.option('-c ,--command <command>', 'command to run')
|
|
678
|
-
.
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
679
|
+
.option(
|
|
680
|
+
'--context <context>',
|
|
681
|
+
'Specify a deploy context or branch for environment variables (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
|
|
682
|
+
normalizeContext,
|
|
683
|
+
'dev',
|
|
682
684
|
)
|
|
683
685
|
.option('-p ,--port <port>', 'port of netlify dev', (value) => Number.parseInt(value))
|
|
684
686
|
.option('--targetPort <port>', 'port of target app server', (value) => Number.parseInt(value))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
const { Option } = require('commander')
|
|
3
3
|
|
|
4
|
-
const { chalk, error, getEnvelopeEnv, log, logJson } = require('../../utils')
|
|
4
|
+
const { chalk, error, getEnvelopeEnv, log, logJson, normalizeContext } = require('../../utils')
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* The env:get command
|
|
@@ -60,16 +60,23 @@ const createEnvGetCommand = (program) =>
|
|
|
60
60
|
program
|
|
61
61
|
.command('env:get')
|
|
62
62
|
.argument('<name>', 'Environment variable name')
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
.option(
|
|
64
|
+
'-c, --context <context>',
|
|
65
|
+
'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
|
|
66
|
+
normalizeContext,
|
|
67
|
+
'dev',
|
|
67
68
|
)
|
|
68
69
|
.addOption(
|
|
69
70
|
new Option('-s, --scope <scope>', 'Specify a scope')
|
|
70
71
|
.choices(['builds', 'functions', 'post_processing', 'runtime', 'any'])
|
|
71
72
|
.default('any'),
|
|
72
73
|
)
|
|
74
|
+
.addExamples([
|
|
75
|
+
'netlify env:get MY_VAR # get value for MY_VAR in dev context',
|
|
76
|
+
'netlify env:get --context production',
|
|
77
|
+
'netlify env:get --context branch:staging',
|
|
78
|
+
'netlify env:get --scope functions',
|
|
79
|
+
])
|
|
73
80
|
.description('Get resolved value of specified environment variable (includes netlify.toml)')
|
|
74
81
|
.action(async (name, options, command) => {
|
|
75
82
|
await envGet(name, options, command)
|
|
@@ -15,7 +15,7 @@ const { exit, log, logJson, translateFromEnvelopeToMongo, translateFromMongoToEn
|
|
|
15
15
|
* @returns {Promise<boolean>}
|
|
16
16
|
*/
|
|
17
17
|
const envImport = async (fileName, options, command) => {
|
|
18
|
-
const { api, site } = command.netlify
|
|
18
|
+
const { api, cachedConfig, site } = command.netlify
|
|
19
19
|
const siteId = site.id
|
|
20
20
|
|
|
21
21
|
if (!siteId) {
|
|
@@ -37,10 +37,10 @@ const envImport = async (fileName, options, command) => {
|
|
|
37
37
|
return false
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const { siteInfo } = cachedConfig
|
|
41
41
|
|
|
42
|
-
const importIntoService =
|
|
43
|
-
const finalEnv = await importIntoService({ api, importedEnv, options,
|
|
42
|
+
const importIntoService = siteInfo.use_envelope ? importIntoEnvelope : importIntoMongo
|
|
43
|
+
const finalEnv = await importIntoService({ api, importedEnv, options, siteInfo })
|
|
44
44
|
|
|
45
45
|
// Return new environment variables of site if using json flag
|
|
46
46
|
if (options.json) {
|
|
@@ -49,7 +49,7 @@ const envImport = async (fileName, options, command) => {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// List newly imported environment variables in a table
|
|
52
|
-
log(`site: ${
|
|
52
|
+
log(`site: ${siteInfo.name}`)
|
|
53
53
|
const table = new AsciiTable(`Imported environment variables`)
|
|
54
54
|
|
|
55
55
|
table.setHeading('Key', 'Value')
|
|
@@ -61,9 +61,9 @@ const envImport = async (fileName, options, command) => {
|
|
|
61
61
|
* Updates the imported env in the site record
|
|
62
62
|
* @returns {Promise<object>}
|
|
63
63
|
*/
|
|
64
|
-
const importIntoMongo = async ({ api, importedEnv, options,
|
|
65
|
-
const { env = {} } =
|
|
66
|
-
const siteId =
|
|
64
|
+
const importIntoMongo = async ({ api, importedEnv, options, siteInfo }) => {
|
|
65
|
+
const { env = {} } = siteInfo.build_settings
|
|
66
|
+
const siteId = siteInfo.id
|
|
67
67
|
|
|
68
68
|
const finalEnv = options.replaceExisting ? importedEnv : { ...env, ...importedEnv }
|
|
69
69
|
|
|
@@ -86,10 +86,10 @@ const importIntoMongo = async ({ api, importedEnv, options, siteData }) => {
|
|
|
86
86
|
* Saves the imported env in the Envelope service
|
|
87
87
|
* @returns {Promise<object>}
|
|
88
88
|
*/
|
|
89
|
-
const importIntoEnvelope = async ({ api, importedEnv, options,
|
|
89
|
+
const importIntoEnvelope = async ({ api, importedEnv, options, siteInfo }) => {
|
|
90
90
|
// fetch env vars
|
|
91
|
-
const accountId =
|
|
92
|
-
const siteId =
|
|
91
|
+
const accountId = siteInfo.account_slug
|
|
92
|
+
const siteId = siteInfo.id
|
|
93
93
|
const dotEnvKeys = Object.keys(importedEnv)
|
|
94
94
|
const envelopeVariables = await api.getEnvVars({ accountId, siteId })
|
|
95
95
|
const envelopeKeys = envelopeVariables.map(({ key }) => key)
|
|
@@ -5,7 +5,7 @@ const { Option } = require('commander')
|
|
|
5
5
|
const inquirer = require('inquirer')
|
|
6
6
|
const isEmpty = require('lodash/isEmpty')
|
|
7
7
|
|
|
8
|
-
const { chalk, error, getEnvelopeEnv, getHumanReadableScopes, log, logJson } = require('../../utils')
|
|
8
|
+
const { chalk, error, getEnvelopeEnv, getHumanReadableScopes, log, logJson, normalizeContext } = require('../../utils')
|
|
9
9
|
|
|
10
10
|
const [logUpdatePromise, ansiEscapesPromise] = [import('log-update'), import('ansi-escapes')]
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ const getTable = ({ environment, hideValues, scopesColumn }) => {
|
|
|
22
22
|
// Key
|
|
23
23
|
key,
|
|
24
24
|
// Value
|
|
25
|
-
hideValues ? MASK : variable.value,
|
|
25
|
+
hideValues ? MASK : variable.value || ' ',
|
|
26
26
|
// Scope
|
|
27
27
|
scopesColumn && getHumanReadableScopes(variable.scopes),
|
|
28
28
|
].filter(Boolean),
|
|
@@ -122,16 +122,23 @@ const envList = async (options, command) => {
|
|
|
122
122
|
const createEnvListCommand = (program) =>
|
|
123
123
|
program
|
|
124
124
|
.command('env:list')
|
|
125
|
-
.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
125
|
+
.option(
|
|
126
|
+
'-c, --context <context>',
|
|
127
|
+
'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
|
|
128
|
+
normalizeContext,
|
|
129
|
+
'dev',
|
|
129
130
|
)
|
|
130
131
|
.addOption(
|
|
131
132
|
new Option('-s, --scope <scope>', 'Specify a scope')
|
|
132
133
|
.choices(['builds', 'functions', 'post_processing', 'runtime', 'any'])
|
|
133
134
|
.default('any'),
|
|
134
135
|
)
|
|
136
|
+
.addExamples([
|
|
137
|
+
'netlify env:list # list dev context and any scope',
|
|
138
|
+
'netlify env:list --context production',
|
|
139
|
+
'netlify env:list --context branch:staging',
|
|
140
|
+
'netlify env:list --scope functions',
|
|
141
|
+
])
|
|
135
142
|
.description('Lists resolved environment variables for site (includes netlify.toml)')
|
|
136
143
|
.action(async (options, command) => {
|
|
137
144
|
await envList(options, command)
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
const {
|
|
2
|
+
const { Option } = require('commander')
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
AVAILABLE_CONTEXTS,
|
|
6
|
+
AVAILABLE_SCOPES,
|
|
7
|
+
chalk,
|
|
8
|
+
error,
|
|
9
|
+
log,
|
|
10
|
+
logJson,
|
|
11
|
+
normalizeContext,
|
|
12
|
+
translateFromEnvelopeToMongo,
|
|
13
|
+
} = require('../../utils')
|
|
3
14
|
|
|
4
15
|
/**
|
|
5
16
|
* The env:set command
|
|
@@ -10,7 +21,9 @@ const { log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
|
|
|
10
21
|
* @returns {Promise<boolean>}
|
|
11
22
|
*/
|
|
12
23
|
const envSet = async (key, value, options, command) => {
|
|
13
|
-
const {
|
|
24
|
+
const { context, scope } = options
|
|
25
|
+
|
|
26
|
+
const { api, cachedConfig, site } = command.netlify
|
|
14
27
|
const siteId = site.id
|
|
15
28
|
|
|
16
29
|
if (!siteId) {
|
|
@@ -18,11 +31,26 @@ const envSet = async (key, value, options, command) => {
|
|
|
18
31
|
return false
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
const
|
|
34
|
+
const { siteInfo } = cachedConfig
|
|
35
|
+
let finalEnv
|
|
22
36
|
|
|
23
37
|
// Get current environment variables set in the UI
|
|
24
|
-
|
|
25
|
-
|
|
38
|
+
if (siteInfo.use_envelope) {
|
|
39
|
+
finalEnv = await setInEnvelope({ api, siteInfo, key, value, context, scope })
|
|
40
|
+
} else if (context || scope) {
|
|
41
|
+
error(
|
|
42
|
+
`To specify a context or scope, please run ${chalk.yellow(
|
|
43
|
+
'netlify open:admin',
|
|
44
|
+
)} to open the Netlify UI and opt in to the new environment variables experience from Site settings`,
|
|
45
|
+
)
|
|
46
|
+
return false
|
|
47
|
+
} else {
|
|
48
|
+
finalEnv = await setInMongo({ api, siteInfo, key, value })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!finalEnv) {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
26
54
|
|
|
27
55
|
// Return new environment variables of site if using json flag
|
|
28
56
|
if (options.json) {
|
|
@@ -30,22 +58,27 @@ const envSet = async (key, value, options, command) => {
|
|
|
30
58
|
return false
|
|
31
59
|
}
|
|
32
60
|
|
|
33
|
-
|
|
61
|
+
const withScope = scope ? ` scoped to ${chalk.white(scope)}` : ''
|
|
62
|
+
log(
|
|
63
|
+
`Set environment variable ${chalk.yellow(`${key}${value ? '=' : ''}${value}`)}${withScope} in the ${chalk.magenta(
|
|
64
|
+
context || 'all',
|
|
65
|
+
)} context`,
|
|
66
|
+
)
|
|
34
67
|
}
|
|
35
68
|
|
|
36
69
|
/**
|
|
37
70
|
* Updates the env for a site record with a new key/value pair
|
|
38
71
|
* @returns {Promise<object>}
|
|
39
72
|
*/
|
|
40
|
-
const setInMongo = async ({ api, key,
|
|
41
|
-
const { env = {} } =
|
|
73
|
+
const setInMongo = async ({ api, key, siteInfo, value }) => {
|
|
74
|
+
const { env = {} } = siteInfo.build_settings
|
|
42
75
|
const newEnv = {
|
|
43
76
|
...env,
|
|
44
77
|
[key]: value,
|
|
45
78
|
}
|
|
46
79
|
// Apply environment variable updates
|
|
47
80
|
await api.updateSite({
|
|
48
|
-
siteId:
|
|
81
|
+
siteId: siteInfo.id,
|
|
49
82
|
body: {
|
|
50
83
|
build_settings: {
|
|
51
84
|
env: newEnv,
|
|
@@ -59,28 +92,55 @@ const setInMongo = async ({ api, key, siteData, value }) => {
|
|
|
59
92
|
* Updates the env for a site configured with Envelope with a new key/value pair
|
|
60
93
|
* @returns {Promise<object>}
|
|
61
94
|
*/
|
|
62
|
-
const setInEnvelope = async ({ api, key,
|
|
63
|
-
const accountId =
|
|
64
|
-
const siteId =
|
|
95
|
+
const setInEnvelope = async ({ api, context, key, scope, siteInfo, value }) => {
|
|
96
|
+
const accountId = siteInfo.account_slug
|
|
97
|
+
const siteId = siteInfo.id
|
|
65
98
|
// fetch envelope env vars
|
|
66
99
|
const envelopeVariables = await api.getEnvVars({ accountId, siteId })
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
const contexts = context || ['all']
|
|
101
|
+
const scopes = scope || AVAILABLE_SCOPES
|
|
102
|
+
|
|
103
|
+
// if the passed context is unknown, it is actually a branch name
|
|
104
|
+
let values = contexts.map((ctx) =>
|
|
105
|
+
AVAILABLE_CONTEXTS.includes(ctx) ? { context: ctx, value } : { context: 'branch', context_parameter: ctx, value },
|
|
106
|
+
)
|
|
73
107
|
|
|
108
|
+
const existing = envelopeVariables.find((envVar) => envVar.key === key)
|
|
109
|
+
|
|
110
|
+
const params = { accountId, siteId, key }
|
|
74
111
|
try {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
if (existing) {
|
|
113
|
+
if (!value) {
|
|
114
|
+
// eslint-disable-next-line prefer-destructuring
|
|
115
|
+
values = existing.values
|
|
116
|
+
}
|
|
117
|
+
if (context && scope) {
|
|
118
|
+
error(
|
|
119
|
+
'Setting the context and scope at the same time on an existing env var is not allowed. Run the set command separately for each update.',
|
|
120
|
+
)
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
if (context) {
|
|
124
|
+
// update individual value(s)
|
|
125
|
+
await Promise.all(values.map((val) => api.setEnvVarValue({ ...params, body: val })))
|
|
126
|
+
} else {
|
|
127
|
+
// otherwise update whole env var
|
|
128
|
+
const body = { key, scopes, values }
|
|
129
|
+
await api.updateEnvVar({ ...params, body })
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
// create whole env var
|
|
133
|
+
const body = [{ key, scopes, values }]
|
|
134
|
+
await api.createEnvVars({ ...params, body })
|
|
135
|
+
}
|
|
136
|
+
} catch (error_) {
|
|
137
|
+
throw error_.json ? error_.json.msg : error_
|
|
78
138
|
}
|
|
79
139
|
|
|
80
|
-
const env = translateFromEnvelopeToMongo(envelopeVariables)
|
|
140
|
+
const env = translateFromEnvelopeToMongo(envelopeVariables, context ? context[0] : 'dev')
|
|
81
141
|
return {
|
|
82
142
|
...env,
|
|
83
|
-
[key]: value,
|
|
143
|
+
[key]: value || env[key],
|
|
84
144
|
}
|
|
85
145
|
}
|
|
86
146
|
|
|
@@ -94,7 +154,28 @@ const createEnvSetCommand = (program) =>
|
|
|
94
154
|
.command('env:set')
|
|
95
155
|
.argument('<key>', 'Environment variable key')
|
|
96
156
|
.argument('[value]', 'Value to set to', '')
|
|
157
|
+
.option(
|
|
158
|
+
'-c, --context <context...>',
|
|
159
|
+
'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev") (default: all contexts)',
|
|
160
|
+
// spread over an array for variadic options
|
|
161
|
+
(context, previous = []) => [...previous, normalizeContext(context)],
|
|
162
|
+
)
|
|
163
|
+
.addOption(
|
|
164
|
+
new Option('-s, --scope <scope...>', 'Specify a scope (default: all scopes)').choices([
|
|
165
|
+
'builds',
|
|
166
|
+
'functions',
|
|
167
|
+
'post_processing',
|
|
168
|
+
'runtime',
|
|
169
|
+
]),
|
|
170
|
+
)
|
|
97
171
|
.description('Set value of environment variable')
|
|
172
|
+
.addExamples([
|
|
173
|
+
'netlify env:set VAR_NAME value # set in all contexts and scopes',
|
|
174
|
+
'netlify env:set VAR_NAME value --context production',
|
|
175
|
+
'netlify env:set VAR_NAME value --context production deploy-preview',
|
|
176
|
+
'netlify env:set VAR_NAME value --scope builds',
|
|
177
|
+
'netlify env:set VAR_NAME value --scope builds functions',
|
|
178
|
+
])
|
|
98
179
|
.action(async (key, value, options, command) => {
|
|
99
180
|
await envSet(key, value, options, command)
|
|
100
181
|
})
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
const {
|
|
2
|
+
const {
|
|
3
|
+
AVAILABLE_CONTEXTS,
|
|
4
|
+
chalk,
|
|
5
|
+
error,
|
|
6
|
+
log,
|
|
7
|
+
logJson,
|
|
8
|
+
normalizeContext,
|
|
9
|
+
translateFromEnvelopeToMongo,
|
|
10
|
+
} = require('../../utils')
|
|
3
11
|
|
|
4
12
|
/**
|
|
5
13
|
* The env:unset command
|
|
@@ -9,7 +17,8 @@ const { log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
|
|
|
9
17
|
* @returns {Promise<boolean>}
|
|
10
18
|
*/
|
|
11
19
|
const envUnset = async (key, options, command) => {
|
|
12
|
-
const {
|
|
20
|
+
const { context } = options
|
|
21
|
+
const { api, cachedConfig, site } = command.netlify
|
|
13
22
|
const siteId = site.id
|
|
14
23
|
|
|
15
24
|
if (!siteId) {
|
|
@@ -17,10 +26,21 @@ const envUnset = async (key, options, command) => {
|
|
|
17
26
|
return false
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
const { siteInfo } = cachedConfig
|
|
30
|
+
|
|
31
|
+
let finalEnv
|
|
32
|
+
if (siteInfo.use_envelope) {
|
|
33
|
+
finalEnv = await unsetInEnvelope({ api, context, siteInfo, key })
|
|
34
|
+
} else if (context) {
|
|
35
|
+
error(
|
|
36
|
+
`To specify a context, please run ${chalk.yellow(
|
|
37
|
+
'netlify open:admin',
|
|
38
|
+
)} to open the Netlify UI and opt in to the new environment variables experience from Site settings`,
|
|
39
|
+
)
|
|
40
|
+
return false
|
|
41
|
+
} else {
|
|
42
|
+
finalEnv = await unsetInMongo({ api, siteInfo, key })
|
|
43
|
+
}
|
|
24
44
|
|
|
25
45
|
// Return new environment variables of site if using json flag
|
|
26
46
|
if (options.json) {
|
|
@@ -28,18 +48,18 @@ const envUnset = async (key, options, command) => {
|
|
|
28
48
|
return false
|
|
29
49
|
}
|
|
30
50
|
|
|
31
|
-
log(`Unset environment variable ${key}
|
|
51
|
+
log(`Unset environment variable ${chalk.yellow(key)} in the ${chalk.magenta(context || 'all')} context`)
|
|
32
52
|
}
|
|
33
53
|
|
|
34
54
|
/**
|
|
35
55
|
* Deletes a given key from the env of a site record
|
|
36
56
|
* @returns {Promise<object>}
|
|
37
57
|
*/
|
|
38
|
-
const unsetInMongo = async ({ api, key,
|
|
58
|
+
const unsetInMongo = async ({ api, key, siteInfo }) => {
|
|
39
59
|
// Get current environment variables set in the UI
|
|
40
60
|
const {
|
|
41
61
|
build_settings: { env = {} },
|
|
42
|
-
} =
|
|
62
|
+
} = siteInfo
|
|
43
63
|
|
|
44
64
|
const newEnv = env
|
|
45
65
|
|
|
@@ -48,7 +68,7 @@ const unsetInMongo = async ({ api, key, siteData }) => {
|
|
|
48
68
|
|
|
49
69
|
// Apply environment variable updates
|
|
50
70
|
await api.updateSite({
|
|
51
|
-
siteId:
|
|
71
|
+
siteId: siteInfo.id,
|
|
52
72
|
body: {
|
|
53
73
|
build_settings: {
|
|
54
74
|
env: newEnv,
|
|
@@ -63,24 +83,48 @@ const unsetInMongo = async ({ api, key, siteData }) => {
|
|
|
63
83
|
* Deletes a given key from the env of a site configured with Envelope
|
|
64
84
|
* @returns {Promise<object>}
|
|
65
85
|
*/
|
|
66
|
-
const unsetInEnvelope = async ({ api, key,
|
|
67
|
-
const accountId =
|
|
68
|
-
const siteId =
|
|
86
|
+
const unsetInEnvelope = async ({ api, context, key, siteInfo }) => {
|
|
87
|
+
const accountId = siteInfo.account_slug
|
|
88
|
+
const siteId = siteInfo.id
|
|
69
89
|
// fetch envelope env vars
|
|
70
90
|
const envelopeVariables = await api.getEnvVars({ accountId, siteId })
|
|
91
|
+
const contexts = context || ['all']
|
|
92
|
+
|
|
93
|
+
const env = translateFromEnvelopeToMongo(envelopeVariables, context ? context[0] : 'dev')
|
|
71
94
|
|
|
72
95
|
// check if the given key exists
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
96
|
+
const variable = envelopeVariables.find((envVar) => envVar.key === key)
|
|
97
|
+
if (!variable) {
|
|
75
98
|
// if not, no need to call delete; return early
|
|
76
99
|
return env
|
|
77
100
|
}
|
|
78
101
|
|
|
79
|
-
|
|
102
|
+
const params = { accountId, siteId, key }
|
|
80
103
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
104
|
+
if (context) {
|
|
105
|
+
// if context(s) are passed, delete the matching contexts / branches, and the `all` context
|
|
106
|
+
const values = variable.values.filter((val) =>
|
|
107
|
+
[...contexts, 'all'].includes(val.context_parameter || val.context),
|
|
108
|
+
)
|
|
109
|
+
if (values) {
|
|
110
|
+
await Promise.all(values.map((value) => api.deleteEnvVarValue({ ...params, id: value.id })))
|
|
111
|
+
// if this was the `all` context, we need to create 3 values in the other contexts
|
|
112
|
+
if (values.length === 1 && values[0].context === 'all') {
|
|
113
|
+
const newContexts = AVAILABLE_CONTEXTS.filter((ctx) => !context.includes(ctx))
|
|
114
|
+
const allValue = values[0].value
|
|
115
|
+
await Promise.all(
|
|
116
|
+
newContexts
|
|
117
|
+
.filter((ctx) => ctx !== 'all')
|
|
118
|
+
.map((ctx) => api.setEnvVarValue({ ...params, body: { context: ctx, value: allValue } })),
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
// otherwise, if no context passed, delete the whole key
|
|
124
|
+
await api.deleteEnvVar({ accountId, siteId, key })
|
|
125
|
+
}
|
|
126
|
+
} catch (error_) {
|
|
127
|
+
throw error_.json ? error_.json.msg : error_
|
|
84
128
|
}
|
|
85
129
|
|
|
86
130
|
delete env[key]
|
|
@@ -98,6 +142,17 @@ const createEnvUnsetCommand = (program) =>
|
|
|
98
142
|
.command('env:unset')
|
|
99
143
|
.aliases(['env:delete', 'env:remove'])
|
|
100
144
|
.argument('<key>', 'Environment variable key')
|
|
145
|
+
.option(
|
|
146
|
+
'-c, --context <context...>',
|
|
147
|
+
'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev") (default: all contexts)',
|
|
148
|
+
// spread over an array for variadic options
|
|
149
|
+
(context, previous = []) => [...previous, normalizeContext(context)],
|
|
150
|
+
)
|
|
151
|
+
.addExamples([
|
|
152
|
+
'netlify env:unset VAR_NAME # unset in all contexts',
|
|
153
|
+
'netlify env:unset VAR_NAME --context production',
|
|
154
|
+
'netlify env:unset VAR_NAME --context production deploy-preview',
|
|
155
|
+
])
|
|
101
156
|
.description('Unset an environment variable which removes it from the UI')
|
|
102
157
|
.action(async (key, options, command) => {
|
|
103
158
|
await envUnset(key, options, command)
|