netlify-cli 10.6.3 → 10.8.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
3
  "description": "Netlify command line tool",
4
- "version": "10.6.3",
4
+ "version": "10.8.0",
5
5
  "author": "Netlify Inc.",
6
6
  "contributors": [
7
7
  "Abraham Schilling <AbrahamSchilling@gmail.com> (https://gitlab.com/n4bb12)",
@@ -31,6 +31,7 @@
31
31
  "Dan Loewenherz <dan@lionheartsw.com> (https://twitter.com/dwlz)",
32
32
  "Daniel Tschinder (https://twitter.com/TschinderDaniel)",
33
33
  "Daniel Woelfel <dwwoelfel@gmail.com> (https://twitter.com/danielwoelfel)",
34
+ "Dario De Vito",
34
35
  "Dave Ackerman <dmackerman@gmail.com>",
35
36
  "David Calavera <david@netlify.com> (https://twitter.com/calavera)",
36
37
  "David Lemler <dlemler@pm.me> (https://twitter.com/davidlemlerm)",
@@ -55,6 +56,7 @@
55
56
  "Jake Jarvis <jake@jarv.is> (https://twitter.com/jakejarvis)",
56
57
  "Jakob Warkotsch <j.warkotsch@gmail.com>",
57
58
  "James George (https://twitter.com/james_madhacks)",
59
+ "Jason Barry (https://twitter.com/jasbarry)",
58
60
  "Jason Lengstorf <jason@lengstorf.com> (https://twitter.com/jlengstorf)",
59
61
  "Jeremy Monson (www.surfline.com)",
60
62
  "Jessica Parsons",
@@ -90,6 +92,7 @@
90
92
  "Nicolas Gonzalez",
91
93
  "Pankaj Patil <pankaj.patil2099@hotmail.com> (https://twitter.com/pankajpatil16)",
92
94
  "Peter",
95
+ "Prince Wilson (https://twitter.com/maxcell)",
93
96
  "Rachael Stavchansky",
94
97
  "Raees Iqbal <raees@netlify.com> (https://raeesbhatti.com/)",
95
98
  "Ricardo Mendes <rokusu@gmail.com> (https://twitter.com/locks)",
@@ -138,6 +141,7 @@
138
141
  "dustincrogers",
139
142
  "ehmicky (https://twitter.com/ehmicky)",
140
143
  "internal tools netlibot",
144
+ "just toby",
141
145
  "kvn-shn",
142
146
  "netlibot (https://www.netlify.com)",
143
147
  "nikoladev",
@@ -212,18 +216,18 @@
212
216
  "prettier": "--ignore-path .gitignore --loglevel=warn \"{src,tools,scripts,site,tests,.github}/**/*.{mjs,cjs,js,md,yml,json,html}\" \"*.{mjs,cjs,js,yml,json,html}\" \".*.{mjs,cjs,js,yml,json,html}\" \"!CHANGELOG.md\" \"!npm-shrinkwrap.json\" \"!**/*/package-lock.json\" \"!.github/**/*.md\""
213
217
  },
214
218
  "dependencies": {
215
- "@netlify/build": "^27.3.1",
216
- "@netlify/config": "^18.1.0",
217
- "@netlify/edge-bundler": "^1.4.1",
219
+ "@netlify/build": "^27.3.3",
220
+ "@netlify/config": "^18.1.1",
221
+ "@netlify/edge-bundler": "^1.5.0",
218
222
  "@netlify/framework-info": "^9.1.0",
219
223
  "@netlify/local-functions-proxy": "^1.1.1",
220
- "@netlify/plugins-list": "^6.28.0",
221
- "@netlify/zip-it-and-ship-it": "^5.11.1",
224
+ "@netlify/plugins-list": "^6.30.0",
225
+ "@netlify/zip-it-and-ship-it": "^5.12.0",
222
226
  "@octokit/rest": "^18.0.0",
223
227
  "@sindresorhus/slugify": "^1.1.0",
224
228
  "ansi-escapes": "^5.0.0",
225
229
  "ansi-styles": "^5.0.0",
226
- "ansi2html": "^0.0.1",
230
+ "ansi-to-html": "^0.7.2",
227
231
  "ascii-table": "0.0.9",
228
232
  "backoff": "^2.5.0",
229
233
  "better-opn": "^3.0.0",
@@ -232,7 +236,7 @@
232
236
  "chokidar": "^3.0.2",
233
237
  "ci-info": "^3.0.0",
234
238
  "clean-deep": "^3.0.2",
235
- "commander": "^9.0.0",
239
+ "commander": "^9.2.0",
236
240
  "concordance": "^5.0.0",
237
241
  "configstore": "^5.0.0",
238
242
  "content-type": "^1.0.4",
@@ -257,7 +261,6 @@
257
261
  "fuzzy": "^0.1.3",
258
262
  "get-port": "^5.1.0",
259
263
  "gh-release-fetch": "^3.0.0",
260
- "git-clone": "^0.2.0",
261
264
  "git-repo-info": "^2.1.0",
262
265
  "gitconfiglocal": "^2.1.0",
263
266
  "hasbin": "^1.2.3",
@@ -318,7 +321,7 @@
318
321
  "unixify": "^1.0.0",
319
322
  "update-notifier": "^5.0.0",
320
323
  "uuid": "^8.0.0",
321
- "wait-port": "^0.2.2",
324
+ "wait-port": "^0.2.14",
322
325
  "winston": "^3.2.1",
323
326
  "write-file-atomic": "^4.0.0"
324
327
  },
@@ -37,12 +37,21 @@ const { sitesCreate } = require('../sites')
37
37
 
38
38
  const DEFAULT_DEPLOY_TIMEOUT = 1.2e6
39
39
 
40
- const triggerDeploy = async ({ api, siteData, siteId }) => {
40
+ const triggerDeploy = async ({ api, options, siteData, siteId }) => {
41
41
  try {
42
42
  const siteBuild = await api.createSiteBuild({ siteId })
43
- log(
44
- `${NETLIFYDEV} A new deployment was triggered successfully. Visit https://app.netlify.com/sites/${siteData.name}/deploys/${siteBuild.deploy_id} to see the logs.`,
45
- )
43
+ if (options.json) {
44
+ logJson({
45
+ site_id: siteId,
46
+ site_name: siteData.name,
47
+ deploy_id: `${siteBuild.deploy_id}`,
48
+ logs: `https://app.netlify.com/sites/${siteData.name}/deploys/${siteBuild.deploy_id}`,
49
+ })
50
+ } else {
51
+ log(
52
+ `${NETLIFYDEV} A new deployment was triggered successfully. Visit https://app.netlify.com/sites/${siteData.name}/deploys/${siteBuild.deploy_id} to see the logs.`,
53
+ )
54
+ }
46
55
  } catch (error_) {
47
56
  if (error_.status === 404) {
48
57
  error('Site not found. Please rerun "netlify link" and make sure that your site has CI configured.')
@@ -553,7 +562,7 @@ const deploy = async (options, command) => {
553
562
  const deployToProduction = options.prod || (options.prodIfUnlocked && !siteData.published_deploy.locked)
554
563
 
555
564
  if (options.trigger) {
556
- return triggerDeploy({ api, siteId, siteData })
565
+ return triggerDeploy({ api, options, siteData, siteId })
557
566
  }
558
567
 
559
568
  const { newConfig, configMutations = [] } = await handleBuild({
@@ -26,6 +26,7 @@ const {
26
26
  getNetlifyGraphConfig,
27
27
  readGraphQLOperationsSourceFile,
28
28
  } = require('../../lib/one-graph/cli-netlify-graph')
29
+ const { startSpinner, stopSpinner } = require('../../lib/spinner')
29
30
  const {
30
31
  BANG,
31
32
  NETLIFYDEV,
@@ -115,7 +116,7 @@ const cleanupBeforeExit = async ({ exitCode }) => {
115
116
  * @param {NodeJS.ProcessEnv} env
116
117
  * @returns {execa.ExecaChildProcess<string>}
117
118
  */
118
- const runCommand = (command, env = {}) => {
119
+ const runCommand = (command, env = {}, spinner = null) => {
119
120
  const commandProcess = execa.command(command, {
120
121
  preferLocal: true,
121
122
  // we use reject=false to avoid rejecting synchronously when the command doesn't exist
@@ -125,8 +126,23 @@ const runCommand = (command, env = {}) => {
125
126
  windowsHide: false,
126
127
  })
127
128
 
128
- commandProcess.stdout.pipe(stripAnsiCc.stream()).pipe(process.stdout)
129
- commandProcess.stderr.pipe(stripAnsiCc.stream()).pipe(process.stderr)
129
+ // This ensures that an active spinner stays at the bottom of the commandline
130
+ // even though the actual framework command might be outputting stuff
131
+ const pipeDataWithSpinner = (writeStream, chunk) => {
132
+ if (spinner && spinner.isSpinning) {
133
+ spinner.clear()
134
+ spinner.isSilent = true
135
+ }
136
+ writeStream.write(chunk, () => {
137
+ if (spinner && spinner.isSpinning) {
138
+ spinner.isSilent = false
139
+ spinner.render()
140
+ }
141
+ })
142
+ }
143
+
144
+ commandProcess.stdout.pipe(stripAnsiCc.stream()).on('data', pipeDataWithSpinner.bind(null, process.stdout))
145
+ commandProcess.stderr.pipe(stripAnsiCc.stream()).on('data', pipeDataWithSpinner.bind(null, process.stderr))
130
146
  process.stdin.pipe(commandProcess.stdin)
131
147
 
132
148
  // we can't try->await->catch since we don't want to block on the framework server which
@@ -173,15 +189,16 @@ const startFrameworkServer = async function ({ settings }) {
173
189
 
174
190
  log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`)
175
191
 
176
- runCommand(settings.command, settings.env)
192
+ const spinner = startSpinner({
193
+ text: `Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
194
+ })
195
+
196
+ runCommand(settings.command, settings.env, spinner)
177
197
 
178
198
  try {
179
- log(
180
- `${NETLIFYDEVLOG} Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
181
- )
182
199
  const open = await waitPort({
183
200
  port: settings.frameworkPort,
184
- output: 'dots',
201
+ output: 'silent',
185
202
  timeout: FRAMEWORK_PORT_TIMEOUT,
186
203
  ...(settings.pollingStrategies.includes('HTTP') && { protocol: 'http' }),
187
204
  })
@@ -189,7 +206,10 @@ const startFrameworkServer = async function ({ settings }) {
189
206
  if (!open) {
190
207
  throw new Error(`Timed out waiting for port '${settings.frameworkPort}' to be open`)
191
208
  }
209
+
210
+ stopSpinner({ error: false, spinner })
192
211
  } catch {
212
+ stopSpinner({ error: true, spinner })
193
213
  log(NETLIFYDEVERR, `Netlify Dev could not connect to localhost:${settings.frameworkPort}.`)
194
214
  log(NETLIFYDEVERR, `Please make sure your framework server is running on port ${settings.frameworkPort}`)
195
215
  exit(1)
@@ -481,7 +501,13 @@ const dev = async (options, command) => {
481
501
  graphqlDocument = defaultExampleOperationsDoc
482
502
  }
483
503
 
484
- stopWatchingCLISessions = await startOneGraphCLISession({ netlifyGraphConfig, netlifyToken, site, state })
504
+ stopWatchingCLISessions = await startOneGraphCLISession({
505
+ netlifyGraphConfig,
506
+ netlifyToken,
507
+ site,
508
+ state,
509
+ oneGraphSessionId: options.sessionId,
510
+ })
485
511
 
486
512
  // Should be created by startOneGraphCLISession
487
513
  const oneGraphSessionId = loadCLISession(state)
@@ -579,6 +605,7 @@ const createDevCommand = (program) => {
579
605
  .hideHelp(),
580
606
  )
581
607
  .addOption(new Option('--graph', 'enable Netlify Graph support').hideHelp())
608
+ .addOption(new Option('--sessionId [sessionId]', '(Graph) connect to cloud session with ID [sessionId]'))
582
609
  .addOption(
583
610
  new Option(
584
611
  '-e, --edgeInspect [address]',
@@ -5,7 +5,7 @@ const AsciiTable = require('ascii-table')
5
5
  const dotenv = require('dotenv')
6
6
  const isEmpty = require('lodash/isEmpty')
7
7
 
8
- const { exit, log, logJson } = require('../../utils')
8
+ const { exit, log, logJson, translateFromEnvelopeToMongo, translateFromMongoToEnvelope } = require('../../utils')
9
9
 
10
10
  /**
11
11
  * The env:import command
@@ -23,13 +23,6 @@ const envImport = async (fileName, options, command) => {
23
23
  return false
24
24
  }
25
25
 
26
- const siteData = await api.getSite({ siteId })
27
-
28
- // Get current environment variables set in the UI
29
- const {
30
- build_settings: { env = {} },
31
- } = siteData
32
-
33
26
  let importedEnv = {}
34
27
  try {
35
28
  const envFileContents = await readFile(fileName, 'utf-8')
@@ -44,21 +37,14 @@ const envImport = async (fileName, options, command) => {
44
37
  return false
45
38
  }
46
39
 
47
- // Apply environment variable updates
48
- const siteResult = await api.updateSite({
49
- siteId,
50
- body: {
51
- build_settings: {
52
- // Only set imported variables if --replaceExisting or otherwise merge
53
- // imported ones with the current environment variables.
54
- env: options.replaceExisting ? importedEnv : { ...env, ...importedEnv },
55
- },
56
- },
57
- })
40
+ const siteData = await api.getSite({ siteId })
41
+
42
+ const importIntoService = siteData.use_envelope ? importIntoEnvelope : importIntoMongo
43
+ const finalEnv = await importIntoService({ api, importedEnv, options, siteData })
58
44
 
59
45
  // Return new environment variables of site if using json flag
60
46
  if (options.json) {
61
- logJson(siteResult.build_settings.env)
47
+ logJson(finalEnv)
62
48
  return false
63
49
  }
64
50
 
@@ -71,6 +57,66 @@ const envImport = async (fileName, options, command) => {
71
57
  log(table.toString())
72
58
  }
73
59
 
60
+ /**
61
+ * Updates the imported env in the site record
62
+ * @returns {Promise<object>}
63
+ */
64
+ const importIntoMongo = async ({ api, importedEnv, options, siteData }) => {
65
+ const { env = {} } = siteData.build_settings
66
+ const siteId = siteData.id
67
+
68
+ const finalEnv = options.replaceExisting ? importedEnv : { ...env, ...importedEnv }
69
+
70
+ // Apply environment variable updates
71
+ await api.updateSite({
72
+ siteId,
73
+ body: {
74
+ build_settings: {
75
+ // Only set imported variables if --replaceExisting or otherwise merge
76
+ // imported ones with the current environment variables.
77
+ env: finalEnv,
78
+ },
79
+ },
80
+ })
81
+
82
+ return finalEnv
83
+ }
84
+
85
+ /**
86
+ * Saves the imported env in the Envelope service
87
+ * @returns {Promise<object>}
88
+ */
89
+ const importIntoEnvelope = async ({ api, importedEnv, options, siteData }) => {
90
+ // fetch env vars
91
+ const accountId = siteData.account_slug
92
+ const siteId = siteData.id
93
+ const dotEnvKeys = Object.keys(importedEnv)
94
+ const envelopeVariables = await api.getEnvVars({ accountId, siteId })
95
+ const envelopeKeys = envelopeVariables.map(({ key }) => key)
96
+
97
+ // if user intends to replace all existing env vars
98
+ // either replace; delete all existing env vars on the site
99
+ // or, merge; delete only the existing env vars that would collide with new .env entries
100
+ const keysToDelete = options.replaceExisting ? envelopeKeys : envelopeKeys.filter((key) => dotEnvKeys.includes(key))
101
+
102
+ // delete marked env vars in parallel
103
+ await Promise.all(keysToDelete.map((key) => api.deleteEnvVar({ accountId, siteId, key })))
104
+
105
+ // hit create endpoint
106
+ const body = translateFromMongoToEnvelope(importedEnv)
107
+ try {
108
+ await api.createEnvVars({ accountId, siteId, body })
109
+ } catch (error) {
110
+ throw error.json ? error.json.msg : error
111
+ }
112
+
113
+ // return final env to aid in --json output (for testing)
114
+ return {
115
+ ...translateFromEnvelopeToMongo(envelopeVariables.filter(({ key }) => !keysToDelete.includes(key))),
116
+ ...importedEnv,
117
+ }
118
+ }
119
+
74
120
  /**
75
121
  * Creates the `netlify env:import` command
76
122
  * @param {import('../base-command').BaseCommand} program
@@ -2,7 +2,13 @@
2
2
 
3
3
  const { isEmpty } = require('lodash')
4
4
 
5
- const { chalk, error: logError, log } = require('../../utils')
5
+ const {
6
+ chalk,
7
+ error: logError,
8
+ log,
9
+ translateFromEnvelopeToMongo,
10
+ translateFromMongoToEnvelope,
11
+ } = require('../../utils')
6
12
 
7
13
  const safeGetSite = async (api, siteId) => {
8
14
  try {
@@ -51,6 +57,37 @@ const envMigrate = async (options, command) => {
51
57
  return false
52
58
  }
53
59
 
60
+ // determine if siteFrom and/or siteTo is on Envelope
61
+ let method
62
+ if (!siteFrom.use_envelope && !siteTo.use_envelope) {
63
+ method = mongoToMongo
64
+ } else if (!siteFrom.use_envelope && siteTo.use_envelope) {
65
+ method = mongoToEnvelope
66
+ } else if (siteFrom.use_envelope && !siteTo.use_envelope) {
67
+ method = envelopeToMongo
68
+ } else {
69
+ method = envelopeToEnvelope
70
+ }
71
+ const success = await method({ api, siteFrom, siteTo })
72
+
73
+ if (!success) {
74
+ return false
75
+ }
76
+
77
+ log(
78
+ `Successfully migrated environment variables from ${chalk.greenBright(siteFrom.name)} to ${chalk.greenBright(
79
+ siteTo.name,
80
+ )}`,
81
+ )
82
+
83
+ return true
84
+ }
85
+
86
+ /**
87
+ * Copies the env from a site not configured with Envelope to a different site not configured with Envelope
88
+ * @returns {Promise<boolean>}
89
+ */
90
+ const mongoToMongo = async ({ api, siteFrom, siteTo }) => {
54
91
  const [
55
92
  {
56
93
  build_settings: { env: envFrom = {} },
@@ -73,7 +110,7 @@ const envMigrate = async (options, command) => {
73
110
 
74
111
  // Apply environment variable updates
75
112
  await api.updateSite({
76
- siteId: siteId.to,
113
+ siteId: siteTo.id,
77
114
  body: {
78
115
  build_settings: {
79
116
  env: mergedEnv,
@@ -81,11 +118,107 @@ const envMigrate = async (options, command) => {
81
118
  },
82
119
  })
83
120
 
84
- log(
85
- `Successfully migrated environment variables from ${chalk.greenBright(siteFrom.name)} to ${chalk.greenBright(
86
- siteTo.name,
87
- )}`,
88
- )
121
+ return true
122
+ }
123
+
124
+ /**
125
+ * Copies the env from a site not configured with Envelope to a site configured with Envelope
126
+ * @returns {Promise<boolean>}
127
+ */
128
+ const mongoToEnvelope = async ({ api, siteFrom, siteTo }) => {
129
+ const envFrom = siteFrom.build_settings.env || {}
130
+ const keysFrom = Object.keys(envFrom)
131
+
132
+ if (isEmpty(envFrom)) {
133
+ log(`${chalk.greenBright(siteFrom.name)} has no environment variables, nothing to migrate`)
134
+ return false
135
+ }
136
+
137
+ const accountId = siteTo.account_slug
138
+ const siteId = siteTo.id
139
+
140
+ const envelopeTo = await api.getEnvVars({ accountId, siteId })
141
+
142
+ const envVarsToDelete = envelopeTo.filter(({ key }) => keysFrom.includes(key))
143
+ // delete marked env vars in parallel
144
+ await Promise.all(envVarsToDelete.map(({ key }) => api.deleteEnvVar({ accountId, siteId, key })))
145
+
146
+ // hit create endpoint
147
+ const body = translateFromMongoToEnvelope(envFrom)
148
+ try {
149
+ await api.createEnvVars({ accountId, siteId, body })
150
+ } catch (error) {
151
+ throw error.json ? error.json.msg : error
152
+ }
153
+
154
+ return true
155
+ }
156
+
157
+ /**
158
+ * Copies the env from a site configured with Envelope to a site not configured with Envelope
159
+ * @returns {Promise<boolean>}
160
+ */
161
+ const envelopeToMongo = async ({ api, siteFrom, siteTo }) => {
162
+ const envelopeVariables = await api.getEnvVars({ accountId: siteFrom.account_slug, siteId: siteFrom.id })
163
+ const envFrom = translateFromEnvelopeToMongo(envelopeVariables)
164
+
165
+ if (isEmpty(envFrom)) {
166
+ log(`${chalk.greenBright(siteFrom.name)} has no environment variables, nothing to migrate`)
167
+ return false
168
+ }
169
+
170
+ const envTo = siteTo.build_settings.env || {}
171
+
172
+ // Merge from site A to site B
173
+ const mergedEnv = {
174
+ ...envTo,
175
+ ...envFrom,
176
+ }
177
+
178
+ // Apply environment variable updates
179
+ await api.updateSite({
180
+ siteId: siteTo.id,
181
+ body: {
182
+ build_settings: {
183
+ env: mergedEnv,
184
+ },
185
+ },
186
+ })
187
+
188
+ return true
189
+ }
190
+
191
+ /**
192
+ * Copies the env from a site configured with Envelope to a different site configured with Envelope
193
+ * @returns {Promise<boolean>}
194
+ */
195
+ const envelopeToEnvelope = async ({ api, siteFrom, siteTo }) => {
196
+ const [envelopeFrom, envelopeTo] = await Promise.all([
197
+ api.getEnvVars({ accountId: siteFrom.account_slug, siteId: siteFrom.id }),
198
+ api.getEnvVars({ accountId: siteTo.account_slug, siteId: siteTo.id }),
199
+ ])
200
+
201
+ const keysFrom = envelopeFrom.map(({ key }) => key)
202
+
203
+ if (isEmpty(keysFrom)) {
204
+ log(`${chalk.greenBright(siteFrom.name)} has no environment variables, nothing to migrate`)
205
+ return false
206
+ }
207
+
208
+ const accountId = siteTo.account_slug
209
+ const siteId = siteTo.id
210
+ const envVarsToDelete = envelopeTo.filter(({ key }) => keysFrom.includes(key))
211
+ // delete marked env vars in parallel
212
+ await Promise.all(envVarsToDelete.map(({ key }) => api.deleteEnvVar({ accountId, siteId, key })))
213
+
214
+ // hit create endpoint
215
+ try {
216
+ await api.createEnvVars({ accountId, siteId, body: envelopeFrom })
217
+ } catch (error) {
218
+ throw error.json ? error.json.msg : error
219
+ }
220
+
221
+ return true
89
222
  }
90
223
 
91
224
  /**
@@ -1,15 +1,15 @@
1
1
  // @ts-check
2
- const { log, logJson } = require('../../utils')
2
+ const { log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
3
3
 
4
4
  /**
5
5
  * The env:set command
6
- * @param {string} name Environment variable name
6
+ * @param {string} key Environment variable key
7
7
  * @param {string} value Value to set to
8
8
  * @param {import('commander').OptionValues} options
9
9
  * @param {import('../base-command').BaseCommand} command
10
10
  * @returns {Promise<boolean>}
11
11
  */
12
- const envSet = async (name, value, options, command) => {
12
+ const envSet = async (key, value, options, command) => {
13
13
  const { api, site } = command.netlify
14
14
  const siteId = site.id
15
15
 
@@ -21,32 +21,67 @@ const envSet = async (name, value, options, command) => {
21
21
  const siteData = await api.getSite({ siteId })
22
22
 
23
23
  // Get current environment variables set in the UI
24
- const {
25
- build_settings: { env = {} },
26
- } = siteData
24
+ const setInService = siteData.use_envelope ? setInEnvelope : setInMongo
25
+ const finalEnv = await setInService({ api, siteData, key, value })
27
26
 
27
+ // Return new environment variables of site if using json flag
28
+ if (options.json) {
29
+ logJson(finalEnv)
30
+ return false
31
+ }
32
+
33
+ log(`Set environment variable ${key}=${value} for site ${siteData.name}`)
34
+ }
35
+
36
+ /**
37
+ * Updates the env for a site record with a new key/value pair
38
+ * @returns {Promise<object>}
39
+ */
40
+ const setInMongo = async ({ api, key, siteData, value }) => {
41
+ const { env = {} } = siteData.build_settings
28
42
  const newEnv = {
29
43
  ...env,
30
- [name]: value,
44
+ [key]: value,
31
45
  }
32
-
33
46
  // Apply environment variable updates
34
- const siteResult = await api.updateSite({
35
- siteId,
47
+ await api.updateSite({
48
+ siteId: siteData.id,
36
49
  body: {
37
50
  build_settings: {
38
51
  env: newEnv,
39
52
  },
40
53
  },
41
54
  })
55
+ return newEnv
56
+ }
42
57
 
43
- // Return new environment variables of site if using json flag
44
- if (options.json) {
45
- logJson(siteResult.build_settings.env)
46
- return false
58
+ /**
59
+ * Updates the env for a site configured with Envelope with a new key/value pair
60
+ * @returns {Promise<object>}
61
+ */
62
+ const setInEnvelope = async ({ api, key, siteData, value }) => {
63
+ const accountId = siteData.account_slug
64
+ const siteId = siteData.id
65
+ // fetch envelope env vars
66
+ 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 }]
73
+
74
+ try {
75
+ await method({ accountId, siteId, key, body })
76
+ } catch (error) {
77
+ throw error.json ? error.json.msg : error
47
78
  }
48
79
 
49
- log(`Set environment variable ${name}=${value} for site ${siteData.name}`)
80
+ const env = translateFromEnvelopeToMongo(envelopeVariables)
81
+ return {
82
+ ...env,
83
+ [key]: value,
84
+ }
50
85
  }
51
86
 
52
87
  /**
@@ -57,11 +92,11 @@ const envSet = async (name, value, options, command) => {
57
92
  const createEnvSetCommand = (program) =>
58
93
  program
59
94
  .command('env:set')
60
- .argument('<name>', 'Environment variable name')
95
+ .argument('<key>', 'Environment variable key')
61
96
  .argument('[value]', 'Value to set to', '')
62
97
  .description('Set value of environment variable')
63
- .action(async (name, value, options, command) => {
64
- await envSet(name, value, options, command)
98
+ .action(async (key, value, options, command) => {
99
+ await envSet(key, value, options, command)
65
100
  })
66
101
 
67
102
  module.exports = { createEnvSetCommand }