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.
@@ -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 injectNetlifyGraphEnv = async function (command, { api, buildOptions, site }) {
19
- const siteData = await api.getSite({ siteId: site.id })
20
- const authlifyTokenId = siteData && siteData.authlify_token_id
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: command.netlify.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
- await injectNetlifyGraphEnv(command, { api, site, buildOptions })
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
- await injectEnvVariables({ devConfig: { ...config.dev }, env: cachedConfig.env, site })
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
  )
@@ -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 = await getBuildOptions({
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
- .addOption(
679
- new Option('--context <context>', 'Specify a deploy context for environment variables')
680
- .choices(['production', 'deploy-preview', 'branch-deploy', 'dev'])
681
- .default('dev'),
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
- .addOption(
64
- new Option('-c, --context <context>', 'Specify a deploy context')
65
- .choices(['production', 'deploy-preview', 'branch-deploy', 'dev'])
66
- .default('dev'),
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 siteData = await api.getSite({ siteId })
40
+ const { siteInfo } = cachedConfig
41
41
 
42
- const importIntoService = siteData.use_envelope ? importIntoEnvelope : importIntoMongo
43
- const finalEnv = await importIntoService({ api, importedEnv, options, siteData })
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: ${siteData.name}`)
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, siteData }) => {
65
- const { env = {} } = siteData.build_settings
66
- const siteId = siteData.id
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, siteData }) => {
89
+ const importIntoEnvelope = async ({ api, importedEnv, options, siteInfo }) => {
90
90
  // fetch env vars
91
- const accountId = siteData.account_slug
92
- const siteId = siteData.id
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
- .addOption(
126
- new Option('-c, --context <context>', 'Specify a deploy context')
127
- .choices(['production', 'deploy-preview', 'branch-deploy', 'dev'])
128
- .default('dev'),
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 { log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
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 { api, site } = command.netlify
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 siteData = await api.getSite({ siteId })
34
+ const { siteInfo } = cachedConfig
35
+ let finalEnv
22
36
 
23
37
  // Get current environment variables set in the UI
24
- const setInService = siteData.use_envelope ? setInEnvelope : setInMongo
25
- const finalEnv = await setInService({ api, siteData, key, value })
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
- log(`Set environment variable ${key}=${value} for site ${siteData.name}`)
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, siteData, value }) => {
41
- const { env = {} } = siteData.build_settings
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: siteData.id,
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, siteData, value }) => {
63
- const accountId = siteData.account_slug
64
- const siteId = siteData.id
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 scopes = ['builds', 'functions', 'runtime', 'post_processing']
68
- const values = [{ context: 'all', value }]
69
- // check if we need to create or update
70
- const exists = envelopeVariables.some((envVar) => envVar.key === key)
71
- const method = exists ? api.updateEnvVar : api.createEnvVars
72
- const body = exists ? { key, scopes, values } : [{ key, scopes, values }]
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
- await method({ accountId, siteId, key, body })
76
- } catch (error) {
77
- throw error.json ? error.json.msg : error
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 { log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
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 { api, site } = command.netlify
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 siteData = await api.getSite({ siteId })
21
-
22
- const unsetInService = siteData.use_envelope ? unsetInEnvelope : unsetInMongo
23
- const finalEnv = await unsetInService({ api, siteData, key })
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} for site ${siteData.name}`)
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, siteData }) => {
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
- } = siteData
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: siteData.id,
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, siteData }) => {
67
- const accountId = siteData.account_slug
68
- const siteId = siteData.id
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 env = translateFromEnvelopeToMongo(envelopeVariables)
74
- if (!env[key]) {
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
- // delete the given key
102
+ const params = { accountId, siteId, key }
80
103
  try {
81
- await api.deleteEnvVar({ accountId, siteId, key })
82
- } catch (error) {
83
- throw error.json ? error.json.msg : error
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)
@@ -15,6 +15,6 @@
15
15
  "author": "Netlify",
16
16
  "license": "MIT",
17
17
  "dependencies": {
18
- "@netlify/functions": "^1.1.0"
18
+ "@netlify/functions": "^1.2.0"
19
19
  }
20
20
  }