netlify-cli 10.16.0 → 10.17.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 +16 -16
- package/package.json +4 -3
- package/src/commands/dev/dev.js +13 -1
- package/src/commands/env/env-get.js +31 -4
- package/src/commands/env/env-list.js +59 -16
- package/src/commands/init/init.js +1 -1
- package/src/utils/dev.js +2 -2
- package/src/utils/env/index.js +156 -1
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.17.0",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "netlify-cli",
|
|
9
|
-
"version": "10.
|
|
9
|
+
"version": "10.17.0",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@netlify/edge-bundler": "^1.12.1",
|
|
16
16
|
"@netlify/framework-info": "^9.2.0",
|
|
17
17
|
"@netlify/local-functions-proxy": "^1.1.1",
|
|
18
|
-
"@netlify/plugins-list": "^6.
|
|
18
|
+
"@netlify/plugins-list": "^6.38.0",
|
|
19
19
|
"@netlify/zip-it-and-ship-it": "^5.13.4",
|
|
20
20
|
"@octokit/rest": "^18.0.0",
|
|
21
21
|
"@sindresorhus/slugify": "^1.1.0",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"multiparty": "^4.2.1",
|
|
82
82
|
"netlify": "^12.0.0",
|
|
83
83
|
"netlify-headers-parser": "^6.0.2",
|
|
84
|
-
"netlify-onegraph-internal": "0.8.
|
|
84
|
+
"netlify-onegraph-internal": "0.8.3",
|
|
85
85
|
"netlify-redirect-parser": "^13.0.5",
|
|
86
86
|
"netlify-redirector": "^0.2.1",
|
|
87
87
|
"node-fetch": "^2.6.0",
|
|
@@ -3890,9 +3890,9 @@
|
|
|
3890
3890
|
"integrity": "sha512-ni6R1xdR8EtH0iB8ixGt9ocuboW+Q8eN4ilTX8lfNHS6Y6Q2S+O/aB2n1BnAgv39wopeQsQ2meL9vfEePURl7w=="
|
|
3891
3891
|
},
|
|
3892
3892
|
"node_modules/@netlify/plugins-list": {
|
|
3893
|
-
"version": "6.
|
|
3894
|
-
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.
|
|
3895
|
-
"integrity": "sha512-
|
|
3893
|
+
"version": "6.38.0",
|
|
3894
|
+
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.38.0.tgz",
|
|
3895
|
+
"integrity": "sha512-fa6BfTCFF3gU2ygS4i175P2Zez8FKXgxKWtjdJPm8eh4UhJ0flIHdooTLZWTpBDL5YiDizk2cviPvOL8fQS93w==",
|
|
3896
3896
|
"engines": {
|
|
3897
3897
|
"node": "^12.20.0 || ^14.14.0 || >=16.0.0"
|
|
3898
3898
|
}
|
|
@@ -16319,9 +16319,9 @@
|
|
|
16319
16319
|
}
|
|
16320
16320
|
},
|
|
16321
16321
|
"node_modules/netlify-onegraph-internal": {
|
|
16322
|
-
"version": "0.8.
|
|
16323
|
-
"resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.8.
|
|
16324
|
-
"integrity": "sha512-
|
|
16322
|
+
"version": "0.8.3",
|
|
16323
|
+
"resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.8.3.tgz",
|
|
16324
|
+
"integrity": "sha512-qxdUkP6IcXUN/6FmYrfLM+M1pXK5TUTxkhhVKXHr+jpApFV4pmEsiuWU7jX4j4AqpaF7+glVJolyVD5F3SBpvg==",
|
|
16325
16325
|
"dependencies": {
|
|
16326
16326
|
"graphql": "16.5.0",
|
|
16327
16327
|
"node-fetch": "^2.6.0",
|
|
@@ -25319,9 +25319,9 @@
|
|
|
25319
25319
|
"integrity": "sha512-ni6R1xdR8EtH0iB8ixGt9ocuboW+Q8eN4ilTX8lfNHS6Y6Q2S+O/aB2n1BnAgv39wopeQsQ2meL9vfEePURl7w=="
|
|
25320
25320
|
},
|
|
25321
25321
|
"@netlify/plugins-list": {
|
|
25322
|
-
"version": "6.
|
|
25323
|
-
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.
|
|
25324
|
-
"integrity": "sha512-
|
|
25322
|
+
"version": "6.38.0",
|
|
25323
|
+
"resolved": "https://registry.npmjs.org/@netlify/plugins-list/-/plugins-list-6.38.0.tgz",
|
|
25324
|
+
"integrity": "sha512-fa6BfTCFF3gU2ygS4i175P2Zez8FKXgxKWtjdJPm8eh4UhJ0flIHdooTLZWTpBDL5YiDizk2cviPvOL8fQS93w=="
|
|
25325
25325
|
},
|
|
25326
25326
|
"@netlify/run-utils": {
|
|
25327
25327
|
"version": "4.0.1",
|
|
@@ -34795,9 +34795,9 @@
|
|
|
34795
34795
|
}
|
|
34796
34796
|
},
|
|
34797
34797
|
"netlify-onegraph-internal": {
|
|
34798
|
-
"version": "0.8.
|
|
34799
|
-
"resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.8.
|
|
34800
|
-
"integrity": "sha512-
|
|
34798
|
+
"version": "0.8.3",
|
|
34799
|
+
"resolved": "https://registry.npmjs.org/netlify-onegraph-internal/-/netlify-onegraph-internal-0.8.3.tgz",
|
|
34800
|
+
"integrity": "sha512-qxdUkP6IcXUN/6FmYrfLM+M1pXK5TUTxkhhVKXHr+jpApFV4pmEsiuWU7jX4j4AqpaF7+glVJolyVD5F3SBpvg==",
|
|
34801
34801
|
"requires": {
|
|
34802
34802
|
"graphql": "16.5.0",
|
|
34803
34803
|
"node-fetch": "^2.6.0",
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netlify-cli",
|
|
3
3
|
"description": "Netlify command line tool",
|
|
4
|
-
"version": "10.
|
|
4
|
+
"version": "10.17.0",
|
|
5
5
|
"author": "Netlify Inc.",
|
|
6
6
|
"contributors": [
|
|
7
|
+
"@whitep4nth3r (https://twitter.com/whitep4nth3r)",
|
|
7
8
|
"Abraham Schilling <AbrahamSchilling@gmail.com> (https://gitlab.com/n4bb12)",
|
|
8
9
|
"Alberto De Agostini (https://twitter.com/albertodeago88)",
|
|
9
10
|
"Alejandro Ñáñez Ortiz <git@alejandro.dev> (https://twitter.com/alejandronanez)",
|
|
@@ -225,7 +226,7 @@
|
|
|
225
226
|
"@netlify/edge-bundler": "^1.12.1",
|
|
226
227
|
"@netlify/framework-info": "^9.2.0",
|
|
227
228
|
"@netlify/local-functions-proxy": "^1.1.1",
|
|
228
|
-
"@netlify/plugins-list": "^6.
|
|
229
|
+
"@netlify/plugins-list": "^6.38.0",
|
|
229
230
|
"@netlify/zip-it-and-ship-it": "^5.13.4",
|
|
230
231
|
"@octokit/rest": "^18.0.0",
|
|
231
232
|
"@sindresorhus/slugify": "^1.1.0",
|
|
@@ -291,7 +292,7 @@
|
|
|
291
292
|
"multiparty": "^4.2.1",
|
|
292
293
|
"netlify": "^12.0.0",
|
|
293
294
|
"netlify-headers-parser": "^6.0.2",
|
|
294
|
-
"netlify-onegraph-internal": "0.8.
|
|
295
|
+
"netlify-onegraph-internal": "0.8.3",
|
|
295
296
|
"netlify-redirect-parser": "^13.0.5",
|
|
296
297
|
"netlify-redirector": "^0.2.1",
|
|
297
298
|
"node-fetch": "^2.6.0",
|
package/src/commands/dev/dev.js
CHANGED
|
@@ -38,6 +38,7 @@ const {
|
|
|
38
38
|
error,
|
|
39
39
|
exit,
|
|
40
40
|
generateNetlifyGraphJWT,
|
|
41
|
+
getEnvelopeEnv,
|
|
41
42
|
getSiteInformation,
|
|
42
43
|
getToken,
|
|
43
44
|
injectEnvVariables,
|
|
@@ -428,7 +429,12 @@ const dev = async (options, command) => {
|
|
|
428
429
|
...options,
|
|
429
430
|
}
|
|
430
431
|
|
|
431
|
-
|
|
432
|
+
let { env } = command.netlify.cachedConfig
|
|
433
|
+
if (siteInfo.use_envelope) {
|
|
434
|
+
env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await injectEnvVariables({ devConfig, env, site })
|
|
432
438
|
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
|
|
433
439
|
|
|
434
440
|
const { addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
|
|
@@ -621,6 +627,11 @@ const createDevCommand = (program) => {
|
|
|
621
627
|
`Local dev server\nThe dev command will run a local dev server with Netlify's proxy and redirect rules`,
|
|
622
628
|
)
|
|
623
629
|
.option('-c ,--command <command>', 'command to run')
|
|
630
|
+
.addOption(
|
|
631
|
+
new Option('--context <context>', 'Specify a deploy context for environment variables')
|
|
632
|
+
.choices(['production', 'deploy-preview', 'branch-deploy', 'dev'])
|
|
633
|
+
.default('dev'),
|
|
634
|
+
)
|
|
624
635
|
.option('-p ,--port <port>', 'port of netlify dev', (value) => Number.parseInt(value))
|
|
625
636
|
.option('--targetPort <port>', 'port of target app server', (value) => Number.parseInt(value))
|
|
626
637
|
.option('--framework <name>', 'framework to use. Defaults to #auto which automatically detects a framework')
|
|
@@ -670,6 +681,7 @@ const createDevCommand = (program) => {
|
|
|
670
681
|
'netlify dev',
|
|
671
682
|
'netlify dev -d public',
|
|
672
683
|
'netlify dev -c "hugo server -w" --targetPort 1313',
|
|
684
|
+
'netlify dev --context production',
|
|
673
685
|
'netlify dev --graph',
|
|
674
686
|
'netlify dev --edgeInspect',
|
|
675
687
|
'netlify dev --edgeInspect=127.0.0.1:9229',
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
-
const {
|
|
2
|
+
const { Option } = require('commander')
|
|
3
|
+
|
|
4
|
+
const { chalk, error, getEnvelopeEnv, log, logJson } = require('../../utils')
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* The env:get command
|
|
@@ -8,6 +10,7 @@ const { log, logJson } = require('../../utils')
|
|
|
8
10
|
* @param {import('../base-command').BaseCommand} command
|
|
9
11
|
*/
|
|
10
12
|
const envGet = async (name, options, command) => {
|
|
13
|
+
const { context, scope } = options
|
|
11
14
|
const { api, cachedConfig, site } = command.netlify
|
|
12
15
|
const siteId = site.id
|
|
13
16
|
|
|
@@ -16,9 +19,21 @@ const envGet = async (name, options, command) => {
|
|
|
16
19
|
return false
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
const
|
|
22
|
+
const { siteInfo } = cachedConfig
|
|
23
|
+
let { env } = cachedConfig
|
|
24
|
+
|
|
25
|
+
if (siteInfo.use_envelope) {
|
|
26
|
+
env = await getEnvelopeEnv({ api, context, env, key: name, scope, siteInfo })
|
|
27
|
+
} else if (context !== 'dev' || scope !== 'any') {
|
|
28
|
+
error(
|
|
29
|
+
`To specify a context or scope, please run ${chalk.yellowBright(
|
|
30
|
+
'netlify open:admin',
|
|
31
|
+
)} to open the Netlify UI and opt in to the new environment variables experience from Site settings`,
|
|
32
|
+
)
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
20
35
|
|
|
21
|
-
const { value } =
|
|
36
|
+
const { value } = env[name] || {}
|
|
22
37
|
|
|
23
38
|
// Return json response for piping commands
|
|
24
39
|
if (options.json) {
|
|
@@ -27,7 +42,9 @@ const envGet = async (name, options, command) => {
|
|
|
27
42
|
}
|
|
28
43
|
|
|
29
44
|
if (!value) {
|
|
30
|
-
|
|
45
|
+
const withContext = `in the ${chalk.magentaBright(context)} context`
|
|
46
|
+
const withScope = scope === 'any' ? '' : ` in the ${chalk.magentaBright(context)} scope`
|
|
47
|
+
log(`No value set ${withContext}${withScope} for environment variable ${chalk.yellowBright(name)}`)
|
|
31
48
|
return false
|
|
32
49
|
}
|
|
33
50
|
|
|
@@ -43,6 +60,16 @@ const createEnvGetCommand = (program) =>
|
|
|
43
60
|
program
|
|
44
61
|
.command('env:get')
|
|
45
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'),
|
|
67
|
+
)
|
|
68
|
+
.addOption(
|
|
69
|
+
new Option('-s, --scope <scope>', 'Specify a scope')
|
|
70
|
+
.choices(['builds', 'functions', 'post_processing', 'runtime', 'any'])
|
|
71
|
+
.default('any'),
|
|
72
|
+
)
|
|
46
73
|
.description('Get resolved value of specified environment variable (includes netlify.toml)')
|
|
47
74
|
.action(async (name, options, command) => {
|
|
48
75
|
await envGet(name, options, command)
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
const AsciiTable = require('ascii-table')
|
|
3
3
|
const { isCI } = require('ci-info')
|
|
4
|
+
const { Option } = require('commander')
|
|
4
5
|
const inquirer = require('inquirer')
|
|
5
6
|
const isEmpty = require('lodash/isEmpty')
|
|
6
7
|
|
|
7
|
-
const { chalk, log, logJson } = require('../../utils')
|
|
8
|
+
const { chalk, error, getEnvelopeEnv, getHumanReadableScopes, log, logJson } = require('../../utils')
|
|
8
9
|
|
|
9
10
|
const [logUpdatePromise, ansiEscapesPromise] = [import('log-update'), import('ansi-escapes')]
|
|
10
11
|
|
|
11
12
|
const MASK_LENGTH = 50
|
|
12
13
|
const MASK = '*'.repeat(MASK_LENGTH)
|
|
13
14
|
|
|
14
|
-
const getTable = ({ environment, hideValues }) => {
|
|
15
|
+
const getTable = ({ environment, hideValues, scopesColumn }) => {
|
|
15
16
|
const table = new AsciiTable(`Environment variables`)
|
|
16
|
-
|
|
17
|
-
table.
|
|
17
|
+
const headings = ['Key', 'Value', scopesColumn && 'Scope'].filter(Boolean)
|
|
18
|
+
table.setHeading(...headings)
|
|
19
|
+
table.addRowMatrix(
|
|
20
|
+
Object.entries(environment).map(([key, variable]) =>
|
|
21
|
+
[
|
|
22
|
+
// Key
|
|
23
|
+
key,
|
|
24
|
+
// Value
|
|
25
|
+
hideValues ? MASK : variable.value,
|
|
26
|
+
// Scope
|
|
27
|
+
scopesColumn && getHumanReadableScopes(variable.scopes),
|
|
28
|
+
].filter(Boolean),
|
|
29
|
+
),
|
|
30
|
+
)
|
|
18
31
|
return table.toString()
|
|
19
32
|
}
|
|
20
33
|
|
|
@@ -25,6 +38,7 @@ const getTable = ({ environment, hideValues }) => {
|
|
|
25
38
|
* @returns {Promise<boolean>}
|
|
26
39
|
*/
|
|
27
40
|
const envList = async (options, command) => {
|
|
41
|
+
const { context, scope } = options
|
|
28
42
|
const { api, cachedConfig, site } = command.netlify
|
|
29
43
|
const siteId = site.id
|
|
30
44
|
|
|
@@ -33,36 +47,55 @@ const envList = async (options, command) => {
|
|
|
33
47
|
return false
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
const { env, siteInfo } = cachedConfig
|
|
51
|
+
const isUsingEnvelope = siteInfo.use_envelope
|
|
52
|
+
let environment = env
|
|
53
|
+
|
|
54
|
+
if (isUsingEnvelope) {
|
|
55
|
+
environment = await getEnvelopeEnv({ api, context, env, scope, siteInfo })
|
|
56
|
+
} else if (context !== 'dev' || scope !== 'any') {
|
|
57
|
+
error(
|
|
58
|
+
`To specify a context or scope, please run ${chalk.yellowBright(
|
|
59
|
+
'netlify open:admin',
|
|
60
|
+
)} to open the Netlify UI and opt in to the new environment variables experience from Site settings`,
|
|
61
|
+
)
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// filter out general sources
|
|
66
|
+
environment = Object.fromEntries(
|
|
67
|
+
Object.entries(environment).filter(([, variable]) => variable.sources[0] !== 'general'),
|
|
42
68
|
)
|
|
43
69
|
|
|
44
70
|
// Return json response for piping commands
|
|
45
71
|
if (options.json) {
|
|
46
|
-
|
|
72
|
+
const envDictionary = Object.fromEntries(
|
|
73
|
+
Object.entries(environment).map(([key, variable]) => [key, variable.value]),
|
|
74
|
+
)
|
|
75
|
+
logJson(envDictionary)
|
|
47
76
|
return false
|
|
48
77
|
}
|
|
49
78
|
|
|
79
|
+
const forSite = `for site ${chalk.greenBright(siteInfo.name)}`
|
|
80
|
+
const withContext = isUsingEnvelope ? `in the ${chalk.magentaBright(options.context)} context` : ''
|
|
81
|
+
const withScope = isUsingEnvelope && scope !== 'any' ? `and ${chalk.yellowBright(options.scope)} scope` : ''
|
|
50
82
|
if (isEmpty(environment)) {
|
|
51
|
-
log(`No environment variables set
|
|
83
|
+
log(`No environment variables set ${forSite} ${withContext} ${withScope}`)
|
|
52
84
|
return false
|
|
53
85
|
}
|
|
54
86
|
|
|
55
87
|
// List environment in a table
|
|
56
|
-
|
|
88
|
+
const count = Object.keys(environment).length
|
|
89
|
+
log(`${count} environment variable${count === 1 ? '' : 's'} ${forSite} ${withContext} ${withScope}`)
|
|
57
90
|
|
|
58
91
|
if (isCI) {
|
|
59
|
-
log(getTable({ environment, hideValues: false }))
|
|
92
|
+
log(getTable({ environment, hideValues: false, scopesColumn: isUsingEnvelope }))
|
|
60
93
|
return false
|
|
61
94
|
}
|
|
62
95
|
|
|
63
96
|
const { default: logUpdate } = await logUpdatePromise
|
|
64
97
|
|
|
65
|
-
logUpdate(getTable({ environment, hideValues: true }))
|
|
98
|
+
logUpdate(getTable({ environment, hideValues: true, scopesColumn: isUsingEnvelope }))
|
|
66
99
|
const { showValues } = await inquirer.prompt([
|
|
67
100
|
{
|
|
68
101
|
type: 'confirm',
|
|
@@ -76,7 +109,7 @@ const envList = async (options, command) => {
|
|
|
76
109
|
const { default: ansiEscapes } = await ansiEscapesPromise
|
|
77
110
|
// since inquirer adds a prompt, we need to account for it when printing the table again
|
|
78
111
|
log(ansiEscapes.eraseLines(3))
|
|
79
|
-
logUpdate(getTable({ environment, hideValues: false }))
|
|
112
|
+
logUpdate(getTable({ environment, hideValues: false, scopesColumn: isUsingEnvelope }))
|
|
80
113
|
log(`${chalk.cyan('?')} Show values? ${chalk.cyan('Yes')}`)
|
|
81
114
|
}
|
|
82
115
|
}
|
|
@@ -89,6 +122,16 @@ const envList = async (options, command) => {
|
|
|
89
122
|
const createEnvListCommand = (program) =>
|
|
90
123
|
program
|
|
91
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'),
|
|
129
|
+
)
|
|
130
|
+
.addOption(
|
|
131
|
+
new Option('-s, --scope <scope>', 'Specify a scope')
|
|
132
|
+
.choices(['builds', 'functions', 'post_processing', 'runtime', 'any'])
|
|
133
|
+
.default('any'),
|
|
134
|
+
)
|
|
92
135
|
.description('Lists resolved environment variables for site (includes netlify.toml)')
|
|
93
136
|
.action(async (options, command) => {
|
|
94
137
|
await envList(options, command)
|
package/src/utils/dev.js
CHANGED
|
@@ -14,7 +14,7 @@ const { loadDotEnvFiles } = require('./dot-env')
|
|
|
14
14
|
// Possible sources of environment variables. For the purpose of printing log messages only. Order does not matter.
|
|
15
15
|
const ENV_VAR_SOURCES = {
|
|
16
16
|
account: {
|
|
17
|
-
name: 'shared
|
|
17
|
+
name: 'shared',
|
|
18
18
|
printFn: chalk.magenta,
|
|
19
19
|
},
|
|
20
20
|
addons: {
|
|
@@ -34,7 +34,7 @@ const ENV_VAR_SOURCES = {
|
|
|
34
34
|
printFn: chalk.red,
|
|
35
35
|
},
|
|
36
36
|
ui: {
|
|
37
|
-
name: '
|
|
37
|
+
name: 'site settings',
|
|
38
38
|
printFn: chalk.blue,
|
|
39
39
|
},
|
|
40
40
|
}
|
package/src/utils/env/index.js
CHANGED
|
@@ -1,3 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finds a matching environment variable value from a given context
|
|
3
|
+
* @param {Array<object>} values - An array of environment variable values from Envelope
|
|
4
|
+
* @param {enum<dev,branch-deploy,deploy-preview,production>} context - The deploy context of the environment variable value
|
|
5
|
+
* @returns {object<context: enum<dev,branch-deploy,deploy-preview,production>, value: string>} The matching environment variable value object
|
|
6
|
+
*/
|
|
7
|
+
const findValueFromContext = (values, context) => values.find((val) => [context, 'all'].includes(val.context))
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Finds environment variables that match a given source
|
|
11
|
+
* @param {object} env - The dictionary of environment variables
|
|
12
|
+
* @param {enum<general,account,addons,ui,configFile>} source - The source of the environment variable
|
|
13
|
+
* @returns {object} The dictionary of env vars that match the given source
|
|
14
|
+
*/
|
|
15
|
+
const filterEnvBySource = (env, source) =>
|
|
16
|
+
Object.fromEntries(Object.entries(env).filter(([, variable]) => variable.sources[0] === source))
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Fetches data from Envelope
|
|
20
|
+
* @param {string} accountId - The account id
|
|
21
|
+
* @param {object} api - The api singleton object
|
|
22
|
+
* @param {string} key - If present, fetch a single key (case-sensitive)
|
|
23
|
+
* @param {string} siteId - The site id
|
|
24
|
+
* @returns {Array<object>} An array of environment variables from the Envelope service
|
|
25
|
+
*/
|
|
26
|
+
const fetchEnvelopeItems = async function ({ accountId, api, key, siteId }) {
|
|
27
|
+
if (accountId === undefined) {
|
|
28
|
+
return {}
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
// if a single key is passed, fetch that single env var
|
|
32
|
+
if (key) {
|
|
33
|
+
const envelopeItem = await api.getEnvVar({ accountId, key, siteId })
|
|
34
|
+
return [envelopeItem]
|
|
35
|
+
}
|
|
36
|
+
// otherwise, fetch the entire list of env vars
|
|
37
|
+
const envelopeItems = await api.getEnvVars({ accountId, siteId })
|
|
38
|
+
return envelopeItems
|
|
39
|
+
} catch {
|
|
40
|
+
// Collaborators aren't allowed to read shared env vars,
|
|
41
|
+
// so return an empty array silently in that case
|
|
42
|
+
return []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Filters and sorts data from Envelope by a given context and/or scope
|
|
48
|
+
* @param {enum<dev,branch-deploy,deploy-preview,production>} context - The deploy context of the environment variable value
|
|
49
|
+
* @param {Array<object>} envelopeItems - An array of environment variables from the Envelope service
|
|
50
|
+
* @param {enum<any,builds,functions,runtime,post_processing>} scope - The scope of the environment variables
|
|
51
|
+
* @param {enum<general,account,addons,ui,configFile>} source - The source of the environment variable
|
|
52
|
+
* @returns {object} A dicionary in the following format:
|
|
53
|
+
* {
|
|
54
|
+
* FOO: {
|
|
55
|
+
* context: 'dev',
|
|
56
|
+
* scopes: ['builds', 'functions'],
|
|
57
|
+
* sources: ['ui'],
|
|
58
|
+
* value: 'bar',
|
|
59
|
+
* },
|
|
60
|
+
* BAZ: {
|
|
61
|
+
* context: 'dev',
|
|
62
|
+
* scopes: ['runtime'],
|
|
63
|
+
* sources: ['account'],
|
|
64
|
+
* value: 'bang',
|
|
65
|
+
* },
|
|
66
|
+
* }
|
|
67
|
+
*/
|
|
68
|
+
const formatEnvelopeData = ({ context = 'dev', envelopeItems = [], scope = 'any', source }) =>
|
|
69
|
+
envelopeItems
|
|
70
|
+
// filter by context
|
|
71
|
+
.filter(({ values }) => Boolean(findValueFromContext(values, context)))
|
|
72
|
+
// filter by scope
|
|
73
|
+
.filter(({ scopes }) => (scope === 'any' ? true : scopes.includes(scope)))
|
|
74
|
+
// sort alphabetically, case insensitive
|
|
75
|
+
.sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1))
|
|
76
|
+
// format the data
|
|
77
|
+
.reduce((acc, cur) => {
|
|
78
|
+
const { context: ctx, value } = findValueFromContext(cur.values, context)
|
|
79
|
+
return {
|
|
80
|
+
...acc,
|
|
81
|
+
[cur.key]: {
|
|
82
|
+
context: ctx,
|
|
83
|
+
scopes: cur.scopes,
|
|
84
|
+
sources: [source],
|
|
85
|
+
value,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
}, {})
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Collects env vars from multiple sources and arranges them in the correct order of precedence
|
|
92
|
+
* @param {object} api - The api singleton object
|
|
93
|
+
* @param {enum<dev,branch-deploy,deploy-preview,production>} context - The deploy context of the environment variable
|
|
94
|
+
* @param {object} env - The dictionary of environment variables
|
|
95
|
+
* @param {string} key - If present, fetch a single key (case-sensitive)
|
|
96
|
+
* @param {enum<any,builds,functions,runtime,post_processing>} scope - The scope of the environment variables
|
|
97
|
+
* @param {object} siteInfo - The site object
|
|
98
|
+
* @returns {object} An object of environment variables keys and their metadata
|
|
99
|
+
*/
|
|
100
|
+
const getEnvelopeEnv = async ({ api, context = 'dev', env, key = '', scope = 'any', siteInfo }) => {
|
|
101
|
+
const { account_slug: accountId, id: siteId } = siteInfo
|
|
102
|
+
|
|
103
|
+
const [accountEnvelopeItems, siteEnvelopeItems] = await Promise.all([
|
|
104
|
+
fetchEnvelopeItems({ api, accountId, key }),
|
|
105
|
+
fetchEnvelopeItems({ api, accountId, key, siteId }),
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
const accountEnv = formatEnvelopeData({ context, envelopeItems: accountEnvelopeItems, scope, source: 'account' })
|
|
109
|
+
const siteEnv = formatEnvelopeData({ context, envelopeItems: siteEnvelopeItems, scope, source: 'ui' })
|
|
110
|
+
const generalEnv = filterEnvBySource(env, 'general')
|
|
111
|
+
const addonsEnv = filterEnvBySource(env, 'addons')
|
|
112
|
+
const configFileEnv = filterEnvBySource(env, 'configFile')
|
|
113
|
+
|
|
114
|
+
// filter out configFile env vars if a non-configFile scope is passed
|
|
115
|
+
const includeConfigEnvVars = ['any', 'builds', 'post_processing'].includes(scope)
|
|
116
|
+
|
|
117
|
+
// Sources of environment variables, in ascending order of precedence.
|
|
118
|
+
return {
|
|
119
|
+
...generalEnv,
|
|
120
|
+
...accountEnv,
|
|
121
|
+
...(includeConfigEnvVars ? addonsEnv : {}),
|
|
122
|
+
...siteEnv,
|
|
123
|
+
...(includeConfigEnvVars ? configFileEnv : {}),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Returns a human-readable, comma-separated list of scopes
|
|
129
|
+
* @param {Array<enum<builds,functions,runtime,post_processing>>} scopes - An array of scopes
|
|
130
|
+
* @returns {string} A human-readable, comma-separated list of scopes
|
|
131
|
+
*/
|
|
132
|
+
const getHumanReadableScopes = (scopes) => {
|
|
133
|
+
const AVAILABLE_SCOPES = {
|
|
134
|
+
builds: 'Builds',
|
|
135
|
+
functions: 'Functions',
|
|
136
|
+
post_processing: 'Post processing',
|
|
137
|
+
runtime: 'Runtime',
|
|
138
|
+
}
|
|
139
|
+
if (!scopes) {
|
|
140
|
+
// if `scopes` is not available, the env var comes from netlify.toml
|
|
141
|
+
// env vars specified in netlify.toml are present in the `builds` and `post_processing` scope
|
|
142
|
+
return 'Builds, Post processing'
|
|
143
|
+
}
|
|
144
|
+
if (scopes.length === Object.keys(AVAILABLE_SCOPES).length) {
|
|
145
|
+
// shorthand instead of listing every available scope
|
|
146
|
+
return 'All'
|
|
147
|
+
}
|
|
148
|
+
return scopes.map((scope) => AVAILABLE_SCOPES[scope]).join(', ')
|
|
149
|
+
}
|
|
150
|
+
|
|
1
151
|
/**
|
|
2
152
|
* Translates a Mongo env into an Envelope env
|
|
3
153
|
* @param {object} env - The site's env as it exists in Mongo
|
|
@@ -38,6 +188,11 @@ const translateFromEnvelopeToMongo = (envVars = []) =>
|
|
|
38
188
|
}, {})
|
|
39
189
|
|
|
40
190
|
module.exports = {
|
|
41
|
-
|
|
191
|
+
findValueFromContext,
|
|
192
|
+
filterEnvBySource,
|
|
193
|
+
formatEnvelopeData,
|
|
194
|
+
getEnvelopeEnv,
|
|
195
|
+
getHumanReadableScopes,
|
|
42
196
|
translateFromEnvelopeToMongo,
|
|
197
|
+
translateFromMongoToEnvelope,
|
|
43
198
|
}
|