From 7d3c78c65f229ef54c6123ab217fe002cd72a396 Mon Sep 17 00:00:00 2001 From: Sean Roberts Date: Wed, 25 Mar 2026 10:36:53 -0400 Subject: [PATCH 1/3] fix: improve env setting instructions --- src/commands/env/env-clone.ts | 1 + src/commands/env/env-import.ts | 1 + src/commands/env/env-set.ts | 1 + src/commands/env/env-unset.ts | 1 + .../integration/commands/env/__snapshots__/env.test.ts.snap | 5 ++++- 5 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/commands/env/env-clone.ts b/src/commands/env/env-clone.ts index 39194ed1a48..50b661aa141 100644 --- a/src/commands/env/env-clone.ts +++ b/src/commands/env/env-clone.ts @@ -101,6 +101,7 @@ export const envClone = async (options: OptionValues, command: BaseCommand) => { } log(`Successfully cloned environment variables from ${chalk.green(siteFrom.name)} to ${chalk.green(siteTo.name)}`) + log(`Changes will require a redeploy to take effect on any deployed versions of your project.`) return true } diff --git a/src/commands/env/env-import.ts b/src/commands/env/env-import.ts index e3971a978e4..4ce84c44df0 100644 --- a/src/commands/env/env-import.ts +++ b/src/commands/env/env-import.ts @@ -90,4 +90,5 @@ export const envImport = async (fileName: string, options: OptionValues, command table.setHeading('Key', 'Value') table.addRowMatrix(Object.entries(importedEnv)) log(table.toString()) + log(`Changes will require a redeploy to take effect on any deployed versions of your project.`) } diff --git a/src/commands/env/env-set.ts b/src/commands/env/env-set.ts index 276ea2412f0..07eea6d7301 100644 --- a/src/commands/env/env-set.ts +++ b/src/commands/env/env-set.ts @@ -138,4 +138,5 @@ export const envSet = async (key: string, value: string, options: OptionValues, `${key}${value && !secret ? `=${value}` : ''}`, )}${withScope}${withSecret} in the ${chalk.magenta(context || 'all')} ${contextType}`, ) + log(`Changes will require a redeploy to take effect on any deployed versions of your project.`) } diff --git a/src/commands/env/env-unset.ts b/src/commands/env/env-unset.ts index 3b1d0cb7849..d166c504619 100644 --- a/src/commands/env/env-unset.ts +++ b/src/commands/env/env-unset.ts @@ -89,4 +89,5 @@ export const envUnset = async (key: string, options: OptionValues, command: Base const contextType = SUPPORTED_CONTEXTS.includes(context || 'all') ? 'context' : 'branch' log(`Unset environment variable ${chalk.yellow(key)} in the ${chalk.magenta(context || 'all')} ${contextType}`) + log(`Changes will require a redeploy to take effect on any deployed versions of your project.`) } diff --git a/tests/integration/commands/env/__snapshots__/env.test.ts.snap b/tests/integration/commands/env/__snapshots__/env.test.ts.snap index 8e72f5210f1..2709831c81a 100644 --- a/tests/integration/commands/env/__snapshots__/env.test.ts.snap +++ b/tests/integration/commands/env/__snapshots__/env.test.ts.snap @@ -2,7 +2,10 @@ exports[`commands/env > env:clone > should exit if the folder is not linked to a project, and --from is not provided 1`] = `"Please include the source project ID as the \`--from\` option, or run \`netlify link\` to link this folder to a Netlify project"`; -exports[`commands/env > env:clone > should return success message 1`] = `"Successfully cloned environment variables from site-name to site-name-a"`; +exports[`commands/env > env:clone > should return success message 1`] = ` +"Successfully cloned environment variables from site-name to site-name-a +To take effect on any deployed versions of your project, redeploy with netlify deploy --production." +`; exports[`commands/env > env:clone > should return without clone if there's no env in source project 1`] = `"site-name has no environment variables, nothing to clone"`; From bdea24963f7e8b07bbb166cb2d0b3f660a45afed Mon Sep 17 00:00:00 2001 From: Sean Roberts Date: Wed, 25 Mar 2026 12:09:50 -0400 Subject: [PATCH 2/3] Support --site for env commands because agents assume it should be used --- src/commands/env/env-get.ts | 3 ++- src/commands/env/env-import.ts | 5 +++-- src/commands/env/env-list.ts | 4 +++- src/commands/env/env-set.ts | 3 ++- src/commands/env/env-unset.ts | 3 ++- src/commands/env/env.ts | 5 +++++ src/commands/env/utils.ts | 12 ++++++++++++ .../commands/env/__snapshots__/env.test.ts.snap | 2 +- 8 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 src/commands/env/utils.ts diff --git a/src/commands/env/env-get.ts b/src/commands/env/env-get.ts index ab0d836127e..4ca9e8e179b 100644 --- a/src/commands/env/env-get.ts +++ b/src/commands/env/env-get.ts @@ -3,6 +3,7 @@ import { OptionValues } from 'commander' import { chalk, log, logJson } from '../../utils/command-helpers.js' import { SUPPORTED_CONTEXTS, getEnvelopeEnv } from '../../utils/env/index.js' import BaseCommand from '../base-command.js' +import { getSiteInfo } from './utils.js' export const envGet = async (name: string, options: OptionValues, command: BaseCommand) => { const { context, scope } = options @@ -14,7 +15,7 @@ export const envGet = async (name: string, options: OptionValues, command: BaseC return false } - const { siteInfo } = cachedConfig + const siteInfo = await getSiteInfo(api, siteId, cachedConfig) const env = await getEnvelopeEnv({ api, context, env: cachedConfig.env, key: name, scope, siteInfo }) // @ts-expect-error FIXME(ndhoule) diff --git a/src/commands/env/env-import.ts b/src/commands/env/env-import.ts index 4ce84c44df0..1a14da1d555 100644 --- a/src/commands/env/env-import.ts +++ b/src/commands/env/env-import.ts @@ -7,6 +7,7 @@ import dotenv from 'dotenv' import { exit, log, logJson } from '../../utils/command-helpers.js' import { translateFromEnvelopeToMongo, translateFromMongoToEnvelope } from '../../utils/env/index.js' import BaseCommand from '../base-command.js' +import { getSiteInfo } from './utils.js' /** * Saves the imported env in the Envelope service @@ -58,6 +59,8 @@ export const envImport = async (fileName: string, options: OptionValues, command return false } + const siteInfo = await getSiteInfo(api, siteId, cachedConfig) + let importedEnv = {} try { const envFileContents = await readFile(fileName, 'utf-8') @@ -73,8 +76,6 @@ export const envImport = async (fileName: string, options: OptionValues, command return false } - const { siteInfo } = cachedConfig - const finalEnv = await importDotEnv({ api, importedEnv, options, siteInfo }) // Return new environment variables of site if using json flag diff --git a/src/commands/env/env-list.ts b/src/commands/env/env-list.ts index a530ae797fc..b96412b36f7 100644 --- a/src/commands/env/env-list.ts +++ b/src/commands/env/env-list.ts @@ -9,6 +9,7 @@ import { chalk, log, logJson } from '../../utils/command-helpers.js' import { SUPPORTED_CONTEXTS, getEnvelopeEnv, getHumanReadableScopes } from '../../utils/env/index.js' import type BaseCommand from '../base-command.js' import { EnvironmentVariables } from '../../utils/types.js' +import { getSiteInfo } from './utils.js' const MASK_LENGTH = 50 const MASK = '*'.repeat(MASK_LENGTH) @@ -50,7 +51,8 @@ export const envList = async (options: OptionValues, command: BaseCommand) => { return false } - const { env, siteInfo } = cachedConfig + const siteInfo = await getSiteInfo(api, siteId, cachedConfig) + const { env } = cachedConfig let environment = await getEnvelopeEnv({ api, context, env, scope, siteInfo }) // filter out general sources diff --git a/src/commands/env/env-set.ts b/src/commands/env/env-set.ts index 07eea6d7301..d1eac1b1e84 100644 --- a/src/commands/env/env-set.ts +++ b/src/commands/env/env-set.ts @@ -4,6 +4,7 @@ import { chalk, logAndThrowError, log, logJson } from '../../utils/command-helpe import { SUPPORTED_CONTEXTS, ALL_ENVELOPE_SCOPES, translateFromEnvelopeToMongo } from '../../utils/env/index.js' import { promptOverwriteEnvVariable } from '../../utils/prompts/env-set-prompts.js' import BaseCommand from '../base-command.js' +import { getSiteInfo } from './utils.js' /** * Updates the env for a site configured with Envelope with a new key/value pair @@ -115,7 +116,7 @@ export const envSet = async (key: string, value: string, options: OptionValues, log('No project id found, please run inside a project folder or `netlify link`') return false } - const { siteInfo } = cachedConfig + const siteInfo = await getSiteInfo(api, siteId, cachedConfig) // Get current environment variables set in the UI const finalEnv = await setInEnvelope({ api, siteInfo, force, key, value, context, scope, secret }) diff --git a/src/commands/env/env-unset.ts b/src/commands/env/env-unset.ts index d166c504619..3e3baebf8e5 100644 --- a/src/commands/env/env-unset.ts +++ b/src/commands/env/env-unset.ts @@ -4,6 +4,7 @@ import { chalk, log, logJson } from '../../utils/command-helpers.js' import { SUPPORTED_CONTEXTS, translateFromEnvelopeToMongo } from '../../utils/env/index.js' import { promptOverwriteEnvVariable } from '../../utils/prompts/env-unset-prompts.js' import BaseCommand from '../base-command.js' +import { getSiteInfo } from './utils.js' /** * Deletes a given key from the env of a site configured with Envelope * @returns {Promise} @@ -77,7 +78,7 @@ export const envUnset = async (key: string, options: OptionValues, command: Base return false } - const { siteInfo } = cachedConfig + const siteInfo = await getSiteInfo(api, siteId, cachedConfig) const finalEnv = await unsetInEnvelope({ api, context, force, siteInfo, key }) diff --git a/src/commands/env/env.ts b/src/commands/env/env.ts index f284cea24d0..5f733196241 100644 --- a/src/commands/env/env.ts +++ b/src/commands/env/env.ts @@ -19,6 +19,7 @@ export const createEnvCommand = (program: BaseCommand) => { 'dev', ) .option('--json', 'Output environment variables as JSON') + .option('--site ', 'A project name or ID to target') .addOption( new Option('-s, --scope ', 'Specify a scope') .choices(['builds', 'functions', 'post-processing', 'runtime', 'any']) @@ -45,6 +46,7 @@ export const createEnvCommand = (program: BaseCommand) => { false, ) .option('--json', 'Output environment variables as JSON') + .option('-s, --site ', 'A project name or ID to target') .description('Import and set environment variables from .env file') .action(async (fileName: string, options: OptionValues, command: BaseCommand) => { const { envImport } = await import('./env-import.js') @@ -60,6 +62,7 @@ export const createEnvCommand = (program: BaseCommand) => { 'dev', ) .option('--json', 'Output environment variables as JSON') + .option('--site ', 'A project name or ID to target') .addOption(new Option('--plain', 'Output environment variables as plaintext').conflicts('json')) .addOption( new Option('-s, --scope ', 'Specify a scope') @@ -90,6 +93,7 @@ export const createEnvCommand = (program: BaseCommand) => { (context: string, previous: string[] = []) => [...previous, normalizeContext(context)], ) .option('--json', 'Output environment variables as JSON') + .option('--site ', 'A project name or ID to target') .addOption( new Option('-s, --scope ', 'Specify a scope (default: all scopes)').choices([ 'builds', @@ -126,6 +130,7 @@ export const createEnvCommand = (program: BaseCommand) => { (context: string, previous: string[] = []) => [...previous, normalizeContext(context)], ) .option('--json', 'Output environment variables as JSON') + .option('-s, --site ', 'A project name or ID to target') .addExamples([ 'netlify env:unset VAR_NAME # unset in all contexts', 'netlify env:unset VAR_NAME --context production', diff --git a/src/commands/env/utils.ts b/src/commands/env/utils.ts new file mode 100644 index 00000000000..bae690a5581 --- /dev/null +++ b/src/commands/env/utils.ts @@ -0,0 +1,12 @@ +import type { NetlifyAPI } from '@netlify/api' + +import type { CachedConfig } from '../../lib/build.js' +import type { SiteInfo } from '../../utils/types.js' + +export const getSiteInfo = async (api: NetlifyAPI, siteId: string, cachedConfig: CachedConfig): Promise => { + const { siteInfo: cachedSiteInfo } = cachedConfig + if (siteId !== cachedSiteInfo.id) { + return (await api.getSite({ siteId })) as unknown as SiteInfo + } + return cachedSiteInfo +} diff --git a/tests/integration/commands/env/__snapshots__/env.test.ts.snap b/tests/integration/commands/env/__snapshots__/env.test.ts.snap index 2709831c81a..41231145f9b 100644 --- a/tests/integration/commands/env/__snapshots__/env.test.ts.snap +++ b/tests/integration/commands/env/__snapshots__/env.test.ts.snap @@ -4,7 +4,7 @@ exports[`commands/env > env:clone > should exit if the folder is not linked to a exports[`commands/env > env:clone > should return success message 1`] = ` "Successfully cloned environment variables from site-name to site-name-a -To take effect on any deployed versions of your project, redeploy with netlify deploy --production." +Changes will require a redeploy to take effect on any deployed versions of your project." `; exports[`commands/env > env:clone > should return without clone if there's no env in source project 1`] = `"site-name has no environment variables, nothing to clone"`; From 38854d94d68204806e736a7dc25c1b2616b17eda Mon Sep 17 00:00:00 2001 From: Sean Roberts Date: Wed, 25 Mar 2026 12:19:01 -0400 Subject: [PATCH 3/3] fix: docs/snapshot --- docs/commands/env.md | 15 ++++++++++----- .../envelope/__snapshots__/envelope.test.ts.snap | 5 ++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/commands/env.md b/docs/commands/env.md index f21e118241a..930790b7fbb 100644 --- a/docs/commands/env.md +++ b/docs/commands/env.md @@ -90,9 +90,10 @@ netlify env:get - `context` (*string*) - Specify a deploy context for environment variables (”production”, ”deploy-preview”, ”branch-deploy”, ”dev”) or `branch:your-branch` where `your-branch` is the name of a branch - `filter` (*string*) - For monorepos, specify the name of the application to run the command in - `json` (*boolean*) - Output environment variables as JSON +- `scope` (*builds | functions | post-processing | runtime | any*) - Specify a scope - `debug` (*boolean*) - Print debugging information - `auth` (*string*) - Netlify auth token - can be used to run this command without logging in -- `scope` (*builds | functions | post-processing | runtime | any*) - Specify a scope +- `site` (*string*) - A project name or ID to target **Examples** @@ -125,6 +126,7 @@ netlify env:import - `replace-existing` (*boolean*) - Replace all existing variables instead of merging them with the current ones - `debug` (*boolean*) - Print debugging information - `auth` (*string*) - Netlify auth token - can be used to run this command without logging in +- `site` (*string*) - A project name or ID to target --- ## `env:list` @@ -142,10 +144,11 @@ netlify env:list - `context` (*string*) - Specify a deploy context for environment variables (”production”, ”deploy-preview”, ”branch-deploy”, ”dev”) or `branch:your-branch` where `your-branch` is the name of a branch (default: all contexts) - `filter` (*string*) - For monorepos, specify the name of the application to run the command in - `json` (*boolean*) - Output environment variables as JSON -- `scope` (*builds | functions | post-processing | runtime | any*) - Specify a scope +- `plain` (*boolean*) - Output environment variables as plaintext - `debug` (*boolean*) - Print debugging information - `auth` (*string*) - Netlify auth token - can be used to run this command without logging in -- `plain` (*boolean*) - Output environment variables as plaintext +- `scope` (*builds | functions | post-processing | runtime | any*) - Specify a scope +- `site` (*string*) - A project name or ID to target **Examples** @@ -179,10 +182,11 @@ netlify env:set - `filter` (*string*) - For monorepos, specify the name of the application to run the command in - `force` (*boolean*) - Bypasses prompts & Force the command to run. - `json` (*boolean*) - Output environment variables as JSON -- `secret` (*boolean*) - Indicate whether the environment variable value can be read again. +- `scope` (*builds | functions | post-processing | runtime*) - Specify a scope (default: all scopes) - `debug` (*boolean*) - Print debugging information - `auth` (*string*) - Netlify auth token - can be used to run this command without logging in -- `scope` (*builds | functions | post-processing | runtime*) - Specify a scope (default: all scopes) +- `secret` (*boolean*) - Indicate whether the environment variable value can be read again. +- `site` (*string*) - A project name or ID to target **Examples** @@ -220,6 +224,7 @@ netlify env:unset - `json` (*boolean*) - Output environment variables as JSON - `debug` (*boolean*) - Print debugging information - `auth` (*string*) - Netlify auth token - can be used to run this command without logging in +- `site` (*string*) - A project name or ID to target **Examples** diff --git a/tests/integration/commands/envelope/__snapshots__/envelope.test.ts.snap b/tests/integration/commands/envelope/__snapshots__/envelope.test.ts.snap index 865f7bda868..7c1cab7fe8b 100644 --- a/tests/integration/commands/envelope/__snapshots__/envelope.test.ts.snap +++ b/tests/integration/commands/envelope/__snapshots__/envelope.test.ts.snap @@ -1,3 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`command/envelope > env:clone should return success message 1`] = `"Successfully cloned environment variables from site-name-a to site-name-b"`; +exports[`command/envelope > env:clone should return success message 1`] = ` +"Successfully cloned environment variables from site-name-a to site-name-b +Changes will require a redeploy to take effect on any deployed versions of your project." +`;