netlify-cli 10.18.0 → 11.1.0-rc.1

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,18 +1,18 @@
1
1
  {
2
2
  "name": "netlify-cli",
3
- "version": "10.18.0",
3
+ "version": "11.1.0-rc.1",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "netlify-cli",
9
- "version": "10.18.0",
9
+ "version": "11.1.0-rc.1",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
13
13
  "@netlify/build": "^27.14.0",
14
14
  "@netlify/config": "^18.2.0",
15
- "@netlify/edge-bundler": "^1.12.1",
15
+ "@netlify/edge-bundler": "^1.13.0",
16
16
  "@netlify/framework-info": "^9.2.0",
17
17
  "@netlify/local-functions-proxy": "^1.1.1",
18
18
  "@netlify/plugins-list": "^6.39.0",
@@ -2343,9 +2343,9 @@
2343
2343
  }
2344
2344
  },
2345
2345
  "node_modules/@netlify/edge-bundler": {
2346
- "version": "1.12.1",
2347
- "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-1.12.1.tgz",
2348
- "integrity": "sha512-yROvOGpapj79wJIIjtuGX/p82gZt2K8on13cTg1tfCGRrGOvE+RNSKi5XuAXpocceyDJXnEyETXsc0mdpMMdhg==",
2346
+ "version": "1.13.0",
2347
+ "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-1.13.0.tgz",
2348
+ "integrity": "sha512-5LDNouZP2tUt3m6NyKkEgor9PycIEq7wxUgrRSpiUcZVV7CNU/CUxxpXJb11/wEFq2bWZp3/xqzxqbR7vpqFNg==",
2349
2349
  "dependencies": {
2350
2350
  "common-path-prefix": "^3.0.0",
2351
2351
  "del": "^6.0.0",
@@ -2354,6 +2354,7 @@
2354
2354
  "glob-to-regexp": "^0.4.1",
2355
2355
  "node-fetch": "^3.1.1",
2356
2356
  "node-stream-zip": "^1.15.0",
2357
+ "p-retry": "^5.1.1",
2357
2358
  "p-wait-for": "^4.1.0",
2358
2359
  "path-key": "^4.0.0",
2359
2360
  "semver": "^7.3.5",
@@ -4353,6 +4354,11 @@
4353
4354
  "@types/node": "*"
4354
4355
  }
4355
4356
  },
4357
+ "node_modules/@types/retry": {
4358
+ "version": "0.12.1",
4359
+ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz",
4360
+ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g=="
4361
+ },
4356
4362
  "node_modules/@types/semver": {
4357
4363
  "version": "7.3.9",
4358
4364
  "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
@@ -17355,6 +17361,21 @@
17355
17361
  "url": "https://github.com/sponsors/sindresorhus"
17356
17362
  }
17357
17363
  },
17364
+ "node_modules/p-retry": {
17365
+ "version": "5.1.1",
17366
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.1.1.tgz",
17367
+ "integrity": "sha512-i69WkEU5ZAL8mrmdmVviWwU+DN+IUF8f4sSJThoJ3z5A7Nn5iuO5ROX3Boye0u+uYQLOSfgFl7SuFZCjlAVbQA==",
17368
+ "dependencies": {
17369
+ "@types/retry": "0.12.1",
17370
+ "retry": "^0.13.1"
17371
+ },
17372
+ "engines": {
17373
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
17374
+ },
17375
+ "funding": {
17376
+ "url": "https://github.com/sponsors/sindresorhus"
17377
+ }
17378
+ },
17358
17379
  "node_modules/p-timeout": {
17359
17380
  "version": "4.1.0",
17360
17381
  "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz",
@@ -19173,6 +19194,14 @@
19173
19194
  "node": ">=0.12"
19174
19195
  }
19175
19196
  },
19197
+ "node_modules/retry": {
19198
+ "version": "0.13.1",
19199
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
19200
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
19201
+ "engines": {
19202
+ "node": ">= 4"
19203
+ }
19204
+ },
19176
19205
  "node_modules/reusify": {
19177
19206
  "version": "1.0.4",
19178
19207
  "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -24046,9 +24075,9 @@
24046
24075
  }
24047
24076
  },
24048
24077
  "@netlify/edge-bundler": {
24049
- "version": "1.12.1",
24050
- "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-1.12.1.tgz",
24051
- "integrity": "sha512-yROvOGpapj79wJIIjtuGX/p82gZt2K8on13cTg1tfCGRrGOvE+RNSKi5XuAXpocceyDJXnEyETXsc0mdpMMdhg==",
24078
+ "version": "1.13.0",
24079
+ "resolved": "https://registry.npmjs.org/@netlify/edge-bundler/-/edge-bundler-1.13.0.tgz",
24080
+ "integrity": "sha512-5LDNouZP2tUt3m6NyKkEgor9PycIEq7wxUgrRSpiUcZVV7CNU/CUxxpXJb11/wEFq2bWZp3/xqzxqbR7vpqFNg==",
24052
24081
  "requires": {
24053
24082
  "common-path-prefix": "^3.0.0",
24054
24083
  "del": "^6.0.0",
@@ -24057,6 +24086,7 @@
24057
24086
  "glob-to-regexp": "^0.4.1",
24058
24087
  "node-fetch": "^3.1.1",
24059
24088
  "node-stream-zip": "^1.15.0",
24089
+ "p-retry": "^5.1.1",
24060
24090
  "p-wait-for": "^4.1.0",
24061
24091
  "path-key": "^4.0.0",
24062
24092
  "semver": "^7.3.5",
@@ -25377,6 +25407,11 @@
25377
25407
  "@types/node": "*"
25378
25408
  }
25379
25409
  },
25410
+ "@types/retry": {
25411
+ "version": "0.12.1",
25412
+ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.1.tgz",
25413
+ "integrity": "sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g=="
25414
+ },
25380
25415
  "@types/semver": {
25381
25416
  "version": "7.3.9",
25382
25417
  "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
@@ -35173,6 +35208,15 @@
35173
35208
  "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz",
35174
35209
  "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q=="
35175
35210
  },
35211
+ "p-retry": {
35212
+ "version": "5.1.1",
35213
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-5.1.1.tgz",
35214
+ "integrity": "sha512-i69WkEU5ZAL8mrmdmVviWwU+DN+IUF8f4sSJThoJ3z5A7Nn5iuO5ROX3Boye0u+uYQLOSfgFl7SuFZCjlAVbQA==",
35215
+ "requires": {
35216
+ "@types/retry": "0.12.1",
35217
+ "retry": "^0.13.1"
35218
+ }
35219
+ },
35176
35220
  "p-timeout": {
35177
35221
  "version": "4.1.0",
35178
35222
  "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz",
@@ -36530,6 +36574,11 @@
36530
36574
  "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
36531
36575
  "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg=="
36532
36576
  },
36577
+ "retry": {
36578
+ "version": "0.13.1",
36579
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
36580
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="
36581
+ },
36533
36582
  "reusify": {
36534
36583
  "version": "1.0.4",
36535
36584
  "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
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.18.0",
4
+ "version": "11.1.0-rc.1",
5
5
  "author": "Netlify Inc.",
6
6
  "contributors": [
7
7
  "@whitep4nth3r (https://twitter.com/whitep4nth3r)",
@@ -224,7 +224,7 @@
224
224
  "dependencies": {
225
225
  "@netlify/build": "^27.14.0",
226
226
  "@netlify/config": "^18.2.0",
227
- "@netlify/edge-bundler": "^1.12.1",
227
+ "@netlify/edge-bundler": "^1.13.0",
228
228
  "@netlify/framework-info": "^9.2.0",
229
229
  "@netlify/local-functions-proxy": "^1.1.1",
230
230
  "@netlify/plugins-list": "^6.39.0",
@@ -2,7 +2,7 @@ const process = require('process')
2
2
 
3
3
  // @ts-check
4
4
  const { getBuildOptions, runBuild } = require('../../lib/build')
5
- const { error, exit, generateNetlifyGraphJWT, getEnvelopeEnv, getToken } = require('../../utils')
5
+ const { error, exit, generateNetlifyGraphJWT, getEnvelopeEnv, getToken, normalizeContext } = require('../../utils')
6
6
 
7
7
  /**
8
8
  * @param {import('../../lib/build').BuildConfig} options
@@ -85,7 +85,12 @@ const createBuildCommand = (program) =>
85
85
  program
86
86
  .command('build')
87
87
  .description('(Beta) Build on your local machine')
88
- .option('--context <context>', 'Specify a build context', process.env.CONTEXT || 'production')
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
+ )
89
94
  .option('--dry', 'Dry run: show instructions without running them', false)
90
95
  .option('-o, --offline', 'disables any features that require network access', false)
91
96
  .addExamples(['netlify build'])
@@ -1,7 +1,6 @@
1
- const { Option } = require('commander')
2
1
  const execa = require('execa')
3
2
 
4
- const { getEnvelopeEnv, injectEnvVariables } = require('../../utils')
3
+ const { getEnvelopeEnv, injectEnvVariables, normalizeContext } = require('../../utils')
5
4
 
6
5
  /**
7
6
  * The dev:exec command
@@ -32,10 +31,11 @@ const createDevExecCommand = (program) =>
32
31
  program
33
32
  .command('dev:exec')
34
33
  .argument('<...cmd>', `the command that should be executed`)
35
- .addOption(
36
- new Option('--context <context>', 'Specify a deploy context for environment variables')
37
- .choices(['production', 'deploy-preview', 'branch-deploy', 'dev'])
38
- .default('dev'),
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
39
  )
40
40
  .description(
41
41
  'Exec command\nRuns a command within the netlify dev environment, e.g. with env variables from any installed addons',
@@ -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,
@@ -54,6 +55,8 @@ const {
54
55
 
55
56
  const { createDevExecCommand } = require('./dev-exec')
56
57
 
58
+ const netlifyBuildPromise = import('@netlify/build')
59
+
57
60
  const startStaticServer = async ({ settings }) => {
58
61
  const server = new StaticServer({
59
62
  rootPath: settings.dist,
@@ -417,7 +420,8 @@ const validateGeoCountryCode = (arg) => {
417
420
  */
418
421
  const dev = async (options, command) => {
419
422
  log(`${NETLIFYDEV}`)
420
- const { api, config, repositoryRoot, site, siteInfo, state } = command.netlify
423
+ const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
424
+ const netlifyBuild = await netlifyBuildPromise
421
425
  config.dev = { ...config.dev }
422
426
  config.build = { ...config.build }
423
427
  /** @type {import('./types').DevConfig} */
@@ -429,7 +433,7 @@ const dev = async (options, command) => {
429
433
  ...options,
430
434
  }
431
435
 
432
- let { env } = command.netlify.cachedConfig
436
+ let { env } = cachedConfig
433
437
  if (!options.offline && siteInfo.use_envelope) {
434
438
  env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
435
439
  }
@@ -449,6 +453,26 @@ const dev = async (options, command) => {
449
453
  let settings = {}
450
454
  try {
451
455
  settings = await detectServerSettings(devConfig, options, site.root)
456
+
457
+ // If there are plugins that we should be running for this site, add them
458
+ // to the config as if they were declared in netlify.toml. We must check
459
+ // whether the plugin has already been added by another source (like the
460
+ // TOML file or the UI), as we don't want to run the same plugin twice.
461
+ if (settings.plugins) {
462
+ const { plugins: existingPlugins = [] } = cachedConfig.config
463
+ const existingPluginNames = new Set(existingPlugins.map((plugin) => plugin.package))
464
+ const newPlugins = settings.plugins
465
+ .map((pluginName) => {
466
+ if (existingPluginNames.has(pluginName)) {
467
+ return
468
+ }
469
+
470
+ return { package: pluginName, origin: 'config', inputs: {} }
471
+ })
472
+ .filter(Boolean)
473
+
474
+ cachedConfig.config.plugins = [...newPlugins, ...cachedConfig.config.plugins]
475
+ }
452
476
  } catch (error_) {
453
477
  log(NETLIFYDEVERR, error_.message)
454
478
  exit(1)
@@ -472,7 +496,19 @@ const dev = async (options, command) => {
472
496
  capabilities,
473
497
  timeouts,
474
498
  })
475
- await startFrameworkServer({ settings })
499
+
500
+ log(`${NETLIFYDEVWARN} Setting up local development server`)
501
+
502
+ const devCommand = () => startFrameworkServer({ settings })
503
+ const startDevOptions = getBuildOptions({
504
+ cachedConfig,
505
+ options,
506
+ })
507
+ const { error: startDevError, success } = await netlifyBuild.startDev(devCommand, startDevOptions)
508
+
509
+ if (!success) {
510
+ error(`Could not start local development server\n\n${startDevError.message}\n\n${startDevError.stack}`)
511
+ }
476
512
 
477
513
  // TODO: We should consolidate this with the existing config watcher.
478
514
  const getUpdatedConfig = async () => {
@@ -612,6 +648,19 @@ const dev = async (options, command) => {
612
648
  printBanner({ url })
613
649
  }
614
650
 
651
+ const getBuildOptions = ({ cachedConfig, options: { context, cwd = process.cwd(), debug, dry, offline }, token }) => ({
652
+ cachedConfig,
653
+ token,
654
+ dry,
655
+ debug,
656
+ context,
657
+ mode: 'cli',
658
+ telemetry: false,
659
+ buffer: false,
660
+ offline,
661
+ cwd,
662
+ })
663
+
615
664
  /**
616
665
  * Creates the `netlify dev` command
617
666
  * @param {import('../base-command').BaseCommand} program
@@ -627,10 +676,11 @@ const createDevCommand = (program) => {
627
676
  `Local dev server\nThe dev command will run a local dev server with Netlify's proxy and redirect rules`,
628
677
  )
629
678
  .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'),
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',
634
684
  )
635
685
  .option('-p ,--port <port>', 'port of netlify dev', (value) => Number.parseInt(value))
636
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 { AVAILABLE_CONTEXTS, chalk, error, getEnvelopeEnv, log, logJson, normalizeContext } = require('../../utils')
5
5
 
6
6
  /**
7
7
  * The env:get command
@@ -42,7 +42,8 @@ const envGet = async (name, options, command) => {
42
42
  }
43
43
 
44
44
  if (!value) {
45
- const withContext = `in the ${chalk.magenta(context)} context`
45
+ const contextType = AVAILABLE_CONTEXTS.includes(context) ? 'context' : 'branch'
46
+ const withContext = `in the ${chalk.magenta(context)} ${contextType}`
46
47
  const withScope = scope === 'any' ? '' : ` and the ${chalk.magenta(scope)} scope`
47
48
  log(`No value set ${withContext}${withScope} for environment variable ${chalk.yellow(name)}`)
48
49
  return false
@@ -60,16 +61,23 @@ const createEnvGetCommand = (program) =>
60
61
  program
61
62
  .command('env:get')
62
63
  .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'),
64
+ .option(
65
+ '-c, --context <context>',
66
+ 'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
67
+ normalizeContext,
68
+ 'dev',
67
69
  )
68
70
  .addOption(
69
71
  new Option('-s, --scope <scope>', 'Specify a scope')
70
72
  .choices(['builds', 'functions', 'post_processing', 'runtime', 'any'])
71
73
  .default('any'),
72
74
  )
75
+ .addExamples([
76
+ 'netlify env:get MY_VAR # get value for MY_VAR in dev context',
77
+ 'netlify env:get --context production',
78
+ 'netlify env:get --context branch:staging',
79
+ 'netlify env:get --scope functions',
80
+ ])
73
81
  .description('Get resolved value of specified environment variable (includes netlify.toml)')
74
82
  .action(async (name, options, command) => {
75
83
  await envGet(name, options, command)
@@ -5,7 +5,16 @@ 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 {
9
+ AVAILABLE_CONTEXTS,
10
+ chalk,
11
+ error,
12
+ getEnvelopeEnv,
13
+ getHumanReadableScopes,
14
+ log,
15
+ logJson,
16
+ normalizeContext,
17
+ } = require('../../utils')
9
18
 
10
19
  const [logUpdatePromise, ansiEscapesPromise] = [import('log-update'), import('ansi-escapes')]
11
20
 
@@ -77,7 +86,8 @@ const envList = async (options, command) => {
77
86
  }
78
87
 
79
88
  const forSite = `for site ${chalk.green(siteInfo.name)}`
80
- const withContext = isUsingEnvelope ? `in the ${chalk.magenta(options.context)} context` : ''
89
+ const contextType = AVAILABLE_CONTEXTS.includes(context) ? 'context' : 'branch'
90
+ const withContext = isUsingEnvelope ? `in the ${chalk.magenta(options.context)} ${contextType}` : ''
81
91
  const withScope = isUsingEnvelope && scope !== 'any' ? `and ${chalk.yellow(options.scope)} scope` : ''
82
92
  if (isEmpty(environment)) {
83
93
  log(`No environment variables set ${forSite} ${withContext} ${withScope}`)
@@ -122,16 +132,23 @@ const envList = async (options, command) => {
122
132
  const createEnvListCommand = (program) =>
123
133
  program
124
134
  .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'),
135
+ .option(
136
+ '-c, --context <context>',
137
+ 'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev")',
138
+ normalizeContext,
139
+ 'dev',
129
140
  )
130
141
  .addOption(
131
142
  new Option('-s, --scope <scope>', 'Specify a scope')
132
143
  .choices(['builds', 'functions', 'post_processing', 'runtime', 'any'])
133
144
  .default('any'),
134
145
  )
146
+ .addExamples([
147
+ 'netlify env:list # list dev context and any scope',
148
+ 'netlify env:list --context production',
149
+ 'netlify env:list --context branch:staging',
150
+ 'netlify env:list --scope functions',
151
+ ])
135
152
  .description('Lists resolved environment variables for site (includes netlify.toml)')
136
153
  .action(async (options, command) => {
137
154
  await envList(options, command)
@@ -1,9 +1,16 @@
1
1
  // @ts-check
2
2
  const { Option } = require('commander')
3
3
 
4
- const { chalk, error, log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
5
-
6
- const AVAILABLE_SCOPES = ['builds', 'functions', 'runtime', 'post_processing']
4
+ const {
5
+ AVAILABLE_CONTEXTS,
6
+ AVAILABLE_SCOPES,
7
+ chalk,
8
+ error,
9
+ log,
10
+ logJson,
11
+ normalizeContext,
12
+ translateFromEnvelopeToMongo,
13
+ } = require('../../utils')
7
14
 
8
15
  /**
9
16
  * The env:set command
@@ -52,10 +59,11 @@ const envSet = async (key, value, options, command) => {
52
59
  }
53
60
 
54
61
  const withScope = scope ? ` scoped to ${chalk.white(scope)}` : ''
62
+ const contextType = AVAILABLE_CONTEXTS.includes(context || 'all') ? 'context' : 'branch'
55
63
  log(
56
64
  `Set environment variable ${chalk.yellow(`${key}${value ? '=' : ''}${value}`)}${withScope} in the ${chalk.magenta(
57
65
  context || 'all',
58
- )} context`,
66
+ )} ${contextType}`,
59
67
  )
60
68
  }
61
69
 
@@ -93,7 +101,10 @@ const setInEnvelope = async ({ api, context, key, scope, siteInfo, value }) => {
93
101
  const contexts = context || ['all']
94
102
  const scopes = scope || AVAILABLE_SCOPES
95
103
 
96
- let values = contexts.map((ctx) => ({ context: ctx, value }))
104
+ // if the passed context is unknown, it is actually a branch name
105
+ let values = contexts.map((ctx) =>
106
+ AVAILABLE_CONTEXTS.includes(ctx) ? { context: ctx, value } : { context: 'branch', context_parameter: ctx, value },
107
+ )
97
108
 
98
109
  const existing = envelopeVariables.find((envVar) => envVar.key === key)
99
110
 
@@ -144,13 +155,11 @@ const createEnvSetCommand = (program) =>
144
155
  .command('env:set')
145
156
  .argument('<key>', 'Environment variable key')
146
157
  .argument('[value]', 'Value to set to', '')
147
- .addOption(
148
- new Option('-c, --context <context...>', 'Specify a deploy context (default: all contexts)').choices([
149
- 'production',
150
- 'deploy-preview',
151
- 'branch-deploy',
152
- 'dev',
153
- ]),
158
+ .option(
159
+ '-c, --context <context...>',
160
+ 'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev") (default: all contexts)',
161
+ // spread over an array for variadic options
162
+ (context, previous = []) => [...previous, normalizeContext(context)],
154
163
  )
155
164
  .addOption(
156
165
  new Option('-s, --scope <scope...>', 'Specify a scope (default: all scopes)').choices([
@@ -1,9 +1,13 @@
1
- const { Option } = require('commander')
2
-
3
1
  // @ts-check
4
- const { chalk, error, log, logJson, translateFromEnvelopeToMongo } = require('../../utils')
5
-
6
- const AVAILABLE_CONTEXTS = ['production', 'deploy-preview', 'branch-deploy', 'dev']
2
+ const {
3
+ AVAILABLE_CONTEXTS,
4
+ chalk,
5
+ error,
6
+ log,
7
+ logJson,
8
+ normalizeContext,
9
+ translateFromEnvelopeToMongo,
10
+ } = require('../../utils')
7
11
 
8
12
  /**
9
13
  * The env:unset command
@@ -44,7 +48,8 @@ const envUnset = async (key, options, command) => {
44
48
  return false
45
49
  }
46
50
 
47
- log(`Unset environment variable ${chalk.yellow(key)} in the ${chalk.magenta(context || 'all')} context`)
51
+ const contextType = AVAILABLE_CONTEXTS.includes(context || 'all') ? 'context' : 'branch'
52
+ log(`Unset environment variable ${chalk.yellow(key)} in the ${chalk.magenta(context || 'all')} ${contextType}`)
48
53
  }
49
54
 
50
55
  /**
@@ -98,8 +103,10 @@ const unsetInEnvelope = async ({ api, context, key, siteInfo }) => {
98
103
  const params = { accountId, siteId, key }
99
104
  try {
100
105
  if (context) {
101
- // if context(s) are passed, delete the matching contexts, and the `all` context
102
- const values = variable.values.filter((val) => [...contexts, 'all'].includes(val.context))
106
+ // if context(s) are passed, delete the matching contexts / branches, and the `all` context
107
+ const values = variable.values.filter((val) =>
108
+ [...contexts, 'all'].includes(val.context_parameter || val.context),
109
+ )
103
110
  if (values) {
104
111
  await Promise.all(values.map((value) => api.deleteEnvVarValue({ ...params, id: value.id })))
105
112
  // if this was the `all` context, we need to create 3 values in the other contexts
@@ -107,7 +114,9 @@ const unsetInEnvelope = async ({ api, context, key, siteInfo }) => {
107
114
  const newContexts = AVAILABLE_CONTEXTS.filter((ctx) => !context.includes(ctx))
108
115
  const allValue = values[0].value
109
116
  await Promise.all(
110
- newContexts.map((ctx) => api.setEnvVarValue({ ...params, body: { context: ctx, value: allValue } })),
117
+ newContexts
118
+ .filter((ctx) => ctx !== 'all')
119
+ .map((ctx) => api.setEnvVarValue({ ...params, body: { context: ctx, value: allValue } })),
111
120
  )
112
121
  }
113
122
  }
@@ -134,13 +143,11 @@ const createEnvUnsetCommand = (program) =>
134
143
  .command('env:unset')
135
144
  .aliases(['env:delete', 'env:remove'])
136
145
  .argument('<key>', 'Environment variable key')
137
- .addOption(
138
- new Option('-c, --context <context...>', 'Specify a deploy context (default: all contexts)').choices([
139
- 'production',
140
- 'deploy-preview',
141
- 'branch-deploy',
142
- 'dev',
143
- ]),
146
+ .option(
147
+ '-c, --context <context...>',
148
+ 'Specify a deploy context or branch (contexts: "production", "deploy-preview", "branch-deploy", "dev") (default: all contexts)',
149
+ // spread over an array for variadic options
150
+ (context, previous = []) => [...previous, normalizeContext(context)],
144
151
  )
145
152
  .addExamples([
146
153
  'netlify env:unset VAR_NAME # unset in all contexts',
@@ -167,6 +167,7 @@ const getSettingsFromFramework = (framework) => {
167
167
  name: frameworkName,
168
168
  staticAssetsDirectory: staticDir,
169
169
  env = {},
170
+ plugins,
170
171
  } = framework
171
172
 
172
173
  return {
@@ -176,6 +177,7 @@ const getSettingsFromFramework = (framework) => {
176
177
  framework: frameworkName,
177
178
  env,
178
179
  pollingStrategies: pollingStrategies.map(({ name }) => name),
180
+ plugins,
179
181
  }
180
182
  }
181
183
 
@@ -311,6 +313,8 @@ const detectServerSettings = async (devConfig, options, projectDir) => {
311
313
  validateFrameworkConfig({ devConfig })
312
314
  settings = await mergeSettings({ devConfig, frameworkSettings })
313
315
  }
316
+
317
+ settings.plugins = frameworkSettings && frameworkSettings.plugins
314
318
  } else if (devConfig.framework === '#custom') {
315
319
  validateFrameworkConfig({ devConfig })
316
320
  // when the users wants to configure `command` and `targetPort`
@@ -1,10 +1,39 @@
1
+ const AVAILABLE_CONTEXTS = ['all', 'production', 'deploy-preview', 'branch-deploy', 'dev']
2
+ const AVAILABLE_SCOPES = ['builds', 'functions', 'runtime', 'post_processing']
3
+
4
+ /**
5
+ * @param {string|undefined} context - The deploy context or branch of the environment variable value
6
+ * @returns {Array<string|undefined>} The normalized context or branch name
7
+ */
8
+ const normalizeContext = (context) => {
9
+ if (!context) {
10
+ return context
11
+ }
12
+ const CONTEXT_SYNONYMS = {
13
+ dp: 'deploy-preview',
14
+ prod: 'production',
15
+ }
16
+ context = context.replace(/^branch:/, '')
17
+ if (CONTEXT_SYNONYMS[context]) {
18
+ context = CONTEXT_SYNONYMS[context]
19
+ }
20
+ return context
21
+ }
22
+
1
23
  /**
2
24
  * Finds a matching environment variable value from a given context
3
25
  * @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
26
+ * @param {string} context - The deploy context or branch of the environment variable value
27
+ * @returns {object<context: enum<dev,branch-deploy,deploy-preview,production,branch>, context_parameter: <string>, value: string>} The matching environment variable value object
6
28
  */
7
- const findValueFromContext = (values, context) => values.find((val) => [context, 'all'].includes(val.context))
29
+ const findValueInValues = (values, context) =>
30
+ values.find((val) => {
31
+ if (!AVAILABLE_CONTEXTS.includes(context)) {
32
+ // the "context" option passed in is actually the name of a branch
33
+ return ['branch', 'all'].includes(val.context) && val.context_parameter === context
34
+ }
35
+ return [context, 'all'].includes(val.context)
36
+ })
8
37
 
9
38
  /**
10
39
  * Finds environment variables that match a given source
@@ -45,7 +74,7 @@ const fetchEnvelopeItems = async function ({ accountId, api, key, siteId }) {
45
74
 
46
75
  /**
47
76
  * 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
77
+ * @param {string} context - The deploy context or branch of the environment variable value
49
78
  * @param {Array<object>} envelopeItems - An array of environment variables from the Envelope service
50
79
  * @param {enum<any,builds,functions,runtime,post_processing>} scope - The scope of the environment variables
51
80
  * @param {enum<general,account,addons,ui,configFile>} source - The source of the environment variable
@@ -58,7 +87,8 @@ const fetchEnvelopeItems = async function ({ accountId, api, key, siteId }) {
58
87
  * value: 'bar',
59
88
  * },
60
89
  * BAZ: {
61
- * context: 'dev',
90
+ * context: 'branch',
91
+ * branch: 'staging',
62
92
  * scopes: ['runtime'],
63
93
  * sources: ['account'],
64
94
  * value: 'bang',
@@ -68,18 +98,19 @@ const fetchEnvelopeItems = async function ({ accountId, api, key, siteId }) {
68
98
  const formatEnvelopeData = ({ context = 'dev', envelopeItems = [], scope = 'any', source }) =>
69
99
  envelopeItems
70
100
  // filter by context
71
- .filter(({ values }) => Boolean(findValueFromContext(values, context)))
101
+ .filter(({ values }) => Boolean(findValueInValues(values, context)))
72
102
  // filter by scope
73
103
  .filter(({ scopes }) => (scope === 'any' ? true : scopes.includes(scope)))
74
104
  // sort alphabetically, case insensitive
75
105
  .sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1))
76
106
  // format the data
77
107
  .reduce((acc, cur) => {
78
- const { context: ctx, value } = findValueFromContext(cur.values, context)
108
+ const { context: ctx, context_parameter: branch, value } = findValueInValues(cur.values, context)
79
109
  return {
80
110
  ...acc,
81
111
  [cur.key]: {
82
112
  context: ctx,
113
+ branch,
83
114
  scopes: cur.scopes,
84
115
  sources: [source],
85
116
  value,
@@ -90,7 +121,7 @@ const formatEnvelopeData = ({ context = 'dev', envelopeItems = [], scope = 'any'
90
121
  /**
91
122
  * Collects env vars from multiple sources and arranges them in the correct order of precedence
92
123
  * @param {object} api - The api singleton object
93
- * @param {enum<dev,branch-deploy,deploy-preview,production>} context - The deploy context of the environment variable
124
+ * @param {string} context - The deploy context or branch of the environment variable
94
125
  * @param {object} env - The dictionary of environment variables
95
126
  * @param {string} key - If present, fetch a single key (case-sensitive)
96
127
  * @param {enum<any,builds,functions,runtime,post_processing>} scope - The scope of the environment variables
@@ -130,7 +161,7 @@ const getEnvelopeEnv = async ({ api, context = 'dev', env, key = '', scope = 'an
130
161
  * @returns {string} A human-readable, comma-separated list of scopes
131
162
  */
132
163
  const getHumanReadableScopes = (scopes) => {
133
- const AVAILABLE_SCOPES = {
164
+ const HUMAN_SCOPES = {
134
165
  builds: 'Builds',
135
166
  functions: 'Functions',
136
167
  post_processing: 'Post processing',
@@ -141,11 +172,11 @@ const getHumanReadableScopes = (scopes) => {
141
172
  // env vars specified in netlify.toml are present in the `builds` and `post_processing` scope
142
173
  return 'Builds, Post processing'
143
174
  }
144
- if (scopes.length === Object.keys(AVAILABLE_SCOPES).length) {
175
+ if (scopes.length === Object.keys(HUMAN_SCOPES).length) {
145
176
  // shorthand instead of listing every available scope
146
177
  return 'All'
147
178
  }
148
- return scopes.map((scope) => AVAILABLE_SCOPES[scope]).join(', ')
179
+ return scopes.map((scope) => HUMAN_SCOPES[scope]).join(', ')
149
180
  }
150
181
 
151
182
  /**
@@ -156,7 +187,7 @@ const getHumanReadableScopes = (scopes) => {
156
187
  const translateFromMongoToEnvelope = (env = {}) => {
157
188
  const envVars = Object.entries(env).map(([key, value]) => ({
158
189
  key,
159
- scopes: ['builds', 'functions', 'runtime', 'post_processing'],
190
+ scopes: AVAILABLE_SCOPES,
160
191
  values: [
161
192
  {
162
193
  context: 'all',
@@ -171,14 +202,14 @@ const translateFromMongoToEnvelope = (env = {}) => {
171
202
  /**
172
203
  * Translates an Envelope env into a Mongo env
173
204
  * @param {Array<object>} envVars - The array of Envelope env vars
174
- * @param {enum<dev,branch-deploy,deploy-preview,production>} context - The deploy context of the environment variable
205
+ * @param {string} context - The deploy context or branch of the environment variable
175
206
  * @returns {object} The env object as compatible with Mongo
176
207
  */
177
208
  const translateFromEnvelopeToMongo = (envVars = [], context = 'dev') =>
178
209
  envVars
179
210
  .sort((left, right) => (left.key.toLowerCase() < right.key.toLowerCase() ? -1 : 1))
180
211
  .reduce((acc, cur) => {
181
- const envVar = cur.values.find((val) => [context, 'all'].includes(val.context))
212
+ const envVar = cur.values.find((val) => [context, 'all'].includes(val.context_parameter || val.context))
182
213
  if (envVar && envVar.value) {
183
214
  return {
184
215
  ...acc,
@@ -189,11 +220,14 @@ const translateFromEnvelopeToMongo = (envVars = [], context = 'dev') =>
189
220
  }, {})
190
221
 
191
222
  module.exports = {
192
- findValueFromContext,
223
+ AVAILABLE_CONTEXTS,
224
+ AVAILABLE_SCOPES,
225
+ findValueInValues,
193
226
  filterEnvBySource,
194
227
  formatEnvelopeData,
195
228
  getEnvelopeEnv,
196
229
  getHumanReadableScopes,
230
+ normalizeContext,
197
231
  translateFromEnvelopeToMongo,
198
232
  translateFromMongoToEnvelope,
199
233
  }