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/npm-shrinkwrap.json +963 -252
- package/package.json +13 -10
- package/src/commands/deploy/deploy.js +14 -5
- package/src/commands/dev/dev.js +36 -9
- package/src/commands/env/env-import.js +66 -20
- package/src/commands/env/env-migrate.js +140 -7
- package/src/commands/env/env-set.js +53 -18
- package/src/commands/env/env-unset.js +54 -14
- package/src/commands/graph/graph-edit.js +21 -7
- package/src/commands/graph/graph-operations.js +2 -2
- package/src/commands/graph/graph.js +2 -2
- package/src/commands/sites/sites-create-template.js +3 -4
- package/src/functions-templates/go/hello-world/go.mod +1 -1
- package/src/lib/edge-functions/deploy.js +2 -3
- package/src/lib/edge-functions/proxy.js +39 -40
- package/src/lib/functions/registry.js +1 -2
- package/src/lib/functions/scheduled.js +4 -2
- package/src/lib/one-graph/cli-client.js +41 -14
- package/src/utils/env/index.js +43 -0
- package/src/utils/index.js +2 -0
- package/src/utils/sites/utils.js +10 -1
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.
|
|
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.
|
|
216
|
-
"@netlify/config": "^18.1.
|
|
217
|
-
"@netlify/edge-bundler": "^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.
|
|
221
|
-
"@netlify/zip-it-and-ship-it": "^5.
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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
|
-
|
|
44
|
-
|
|
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,
|
|
565
|
+
return triggerDeploy({ api, options, siteData, siteId })
|
|
557
566
|
}
|
|
558
567
|
|
|
559
568
|
const { newConfig, configMutations = [] } = await handleBuild({
|
package/src/commands/dev/dev.js
CHANGED
|
@@ -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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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: '
|
|
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({
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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(
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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}
|
|
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 (
|
|
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
|
-
|
|
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
|
-
[
|
|
44
|
+
[key]: value,
|
|
31
45
|
}
|
|
32
|
-
|
|
33
46
|
// Apply environment variable updates
|
|
34
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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('<
|
|
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 (
|
|
64
|
-
await envSet(
|
|
98
|
+
.action(async (key, value, options, command) => {
|
|
99
|
+
await envSet(key, value, options, command)
|
|
65
100
|
})
|
|
66
101
|
|
|
67
102
|
module.exports = { createEnvSetCommand }
|