diff --git a/package-lock.json b/package-lock.json index 51429dde4..3350bc7d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -96,6 +96,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -698,7 +699,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -716,7 +716,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -734,7 +733,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -752,7 +750,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -770,7 +767,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -788,7 +784,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -806,7 +801,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -824,7 +818,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -842,7 +835,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -860,7 +852,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -878,7 +869,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -896,7 +886,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -914,7 +903,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -932,7 +920,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -950,7 +937,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -968,7 +954,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -986,7 +971,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -1004,7 +988,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1022,7 +1005,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1040,7 +1022,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1058,7 +1039,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1076,7 +1056,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -1094,7 +1073,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -1112,7 +1090,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1130,7 +1107,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1148,7 +1124,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -2844,6 +2819,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -3678,6 +3654,7 @@ "integrity": "sha512-WQ751WxWLBIeH3TDFt/LWQ2znyAKxpR5+gpv80oerwnVQs4GKajAfR6dIgExXZkjaPUHEFv2lVD9vM+frbprzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "c12": "^3.2.0", "citty": "^0.1.6", @@ -4567,21 +4544,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nuxt/vite-builder/node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@nuxt/vite-builder/node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -4729,6 +4691,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -5662,6 +5625,7 @@ "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -6279,8 +6243,7 @@ "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.50.1", @@ -6294,8 +6257,7 @@ "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.50.1", @@ -6309,8 +6271,7 @@ "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.50.1", @@ -6324,8 +6285,7 @@ "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.50.1", @@ -6339,8 +6299,7 @@ "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.50.1", @@ -6354,8 +6313,7 @@ "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.50.1", @@ -6369,8 +6327,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.50.1", @@ -6384,8 +6341,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.50.1", @@ -6399,8 +6355,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.50.1", @@ -6414,8 +6369,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { "version": "4.50.1", @@ -6429,8 +6383,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.50.1", @@ -6444,8 +6397,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.50.1", @@ -6459,8 +6411,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.50.1", @@ -6474,8 +6425,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.50.1", @@ -6489,8 +6439,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.50.1", @@ -6504,8 +6453,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.50.1", @@ -6519,8 +6467,7 @@ "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.50.1", @@ -6534,8 +6481,7 @@ "optional": true, "os": [ "openharmony" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.50.1", @@ -6549,8 +6495,7 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.50.1", @@ -6564,8 +6509,7 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.50.1", @@ -6579,8 +6523,7 @@ "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -6762,6 +6705,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz", "integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -6915,6 +6859,7 @@ "integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/types": "8.43.0", @@ -7759,6 +7704,7 @@ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.28.3", "@vue/shared": "3.5.21", @@ -7782,6 +7728,7 @@ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.28.3", "@vue/compiler-core": "3.5.21", @@ -8065,6 +8012,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -8105,6 +8053,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -8540,6 +8489,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001737", "electron-to-chromium": "^1.5.211", @@ -10186,6 +10136,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -14535,6 +14486,7 @@ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -14590,6 +14542,7 @@ "integrity": "sha512-v9+uomgqyLSxlq3qlaMqJJtXg2+rUsa368p/zkmgi5OMGmcZAtZt5GIeSVFF84iNET+08Hdx/rUtd/FyIdfNFQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@oxc-project/types": "^0.86.0" }, @@ -14996,6 +14949,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -15169,6 +15123,7 @@ "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -15236,6 +15191,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -16704,6 +16660,7 @@ "integrity": "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -19114,6 +19071,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -19404,6 +19362,7 @@ "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -19692,6 +19651,7 @@ "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.21", "@vue/compiler-sfc": "3.5.21", @@ -19731,6 +19691,7 @@ "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "eslint-scope": "^8.2.0", @@ -19768,6 +19729,7 @@ "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^6.6.4" }, @@ -19784,6 +19746,7 @@ "integrity": "sha512-pXx4pkHigOJCzGPXhGA9Rdou1oIuNiF9n4n5GQ7C4QehTXFEpKUjcpvc3PZ6LvC6ccUL021qor8j1153Y7/6Ig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.27.0" }, @@ -19802,6 +19765,7 @@ "integrity": "sha512-Tbs8Whd43R2e2nxez4WXPvvdjGbW24rOSgRhLOHXzWiT4pcP4G7KeWh0YCn18rF4bVwv7tggLLZ6MJnO6jXPBg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@volar/typescript": "2.4.23", "@vue/language-core": "3.0.6" @@ -20603,14 +20567,6 @@ "node": ">=20.6.1" } }, - "packages/db/prod/node_modules/@electric-sql/pglite": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.2.17.tgz", - "integrity": "sha512-qEpKRT2oUaWDH6tjRxLHjdzMqRUGYDnGZlKrnL4dJ77JVMcP2Hpo3NYnOSPKdZdeec57B6QPprCUFg0picx5Pw==", - "license": "Apache-2.0", - "optional": true, - "peer": true - }, "packages/db/prod/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", @@ -21650,6 +21606,7 @@ "version": "20.19.11", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } diff --git a/packages/dev/README.md b/packages/dev/README.md index 05448d2c9..f9c70940f 100644 --- a/packages/dev/README.md +++ b/packages/dev/README.md @@ -30,7 +30,12 @@ import { NetlifyDev } from '@netlify/dev' const devServer = new NetlifyDev({ blobs: { enabled: true }, edgeFunctions: { enabled: true }, - environmentVariables: { enabled: true }, + environmentVariables: { + enabled: true, + // OPTIONAL: control whether user-defined environment variables are injected + // When false, only platform env vars (NETLIFY_LOCAL, CONTEXT, SITE_ID, etc.) are injected + injectUserEnv: true, // default: true + }, functions: { enabled: true }, redirects: { enabled: true }, staticFiles: { diff --git a/packages/dev/src/lib/env.ts b/packages/dev/src/lib/env.ts index 344af494b..5198f2433 100644 --- a/packages/dev/src/lib/env.ts +++ b/packages/dev/src/lib/env.ts @@ -42,10 +42,26 @@ interface InjectEnvironmentVariablesOptions { accountSlug?: string baseVariables: Record envAPI: EnvironmentVariables + /** + * Whether to inject user-defined environment variables. + * When false, only platform environment variables (from 'general' and 'internal' sources) + * are injected. When true, all environment variables are injected. + * @default true + */ + injectUserEnv?: boolean netlifyAPI?: NetlifyAPI siteID?: string } +/** + * Determines if an environment variable is a platform variable. + * Platform variables are from 'general' or 'internal' sources and include + * documented runtime variables like NETLIFY_LOCAL, CONTEXT, SITE_ID, etc. + */ +const isPlatformEnvironmentVariable = (variable: EnvironmentVariable): boolean => { + return variable.sources.includes('general') || variable.sources.includes('internal') +} + /** * Inject user-defined environment variables (from various sources, see `@netlify/config`) * into the provided `envAPI` (which may be a proxy to `process.env`, affecting the current proc), @@ -61,6 +77,7 @@ export const injectEnvVariables = async ({ accountSlug, baseVariables = {}, envAPI, + injectUserEnv = true, netlifyAPI, siteID, }: InjectEnvironmentVariablesOptions) => { @@ -80,6 +97,11 @@ export const injectEnvVariables = async ({ // Inject env vars which come from multiple `source`s and have been collected from // `@netlify/config` and/or Envelope. These have not been populated on the actual env yet. for (const [key, variable] of Object.entries(variables)) { + // If injectUserEnv is false, only inject platform env vars (from 'general' and 'internal' sources) + if (!injectUserEnv && !isPlatformEnvironmentVariable(variable)) { + continue + } + const existsInProcess = envAPI.has(key) const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources const isInternal = variable.sources.includes('internal') diff --git a/packages/dev/src/main.test.ts b/packages/dev/src/main.test.ts index 968a1dfaf..abcea8e92 100644 --- a/packages/dev/src/main.test.ts +++ b/packages/dev/src/main.test.ts @@ -1107,3 +1107,84 @@ describe('Handling requests', () => { }) }) }) + +describe('Environment variable injection', () => { + afterEach(() => { + vi.unstubAllEnvs() + }) + + test('injectUserEnv option controls user-defined env var injection', async () => { + const fixture = new Fixture() + .withFile( + 'netlify.toml', + `[build] + publish = "public" + [context.dev.environment] + MY_USER_VAR = "user value" + `, + ) + .withFile( + 'netlify/functions/env-test.mjs', + `export default async (req, context) => Response.json({ + NETLIFY_LOCAL: Netlify.env.get("NETLIFY_LOCAL"), + CONTEXT: Netlify.env.get("CONTEXT"), + MY_USER_VAR: Netlify.env.get("MY_USER_VAR"), + }); + export const config = { path: "/env-test" };`, + ) + .withStateFile({ siteId: 'site_id' }) + const directory = await fixture.create() + + // Test with injectUserEnv: false - only platform vars should be injected + const devWithoutUserEnv = new NetlifyDev({ + projectRoot: directory, + environmentVariables: { + enabled: true, + injectUserEnv: false, + }, + edgeFunctions: { enabled: false }, + geolocation: { enabled: false }, + }) + + await devWithoutUserEnv.start() + + const req1 = new Request('https://site.netlify/env-test') + const res1 = await devWithoutUserEnv.handle(req1) + const envVarsWithoutUserEnv = await res1?.json() + + await devWithoutUserEnv.stop() + + // Platform env vars should be present + expect(envVarsWithoutUserEnv).toHaveProperty('NETLIFY_LOCAL', 'true') + expect(envVarsWithoutUserEnv).toHaveProperty('CONTEXT', 'dev') + + // User-defined env vars should NOT be present + expect(envVarsWithoutUserEnv.MY_USER_VAR).toBeUndefined() + + // Test with injectUserEnv: true (default) - all vars should be injected + const devWithUserEnv = new NetlifyDev({ + projectRoot: directory, + environmentVariables: { + enabled: true, + injectUserEnv: true, + }, + edgeFunctions: { enabled: false }, + geolocation: { enabled: false }, + }) + + await devWithUserEnv.start() + + const req2 = new Request('https://site.netlify/env-test') + const res2 = await devWithUserEnv.handle(req2) + const envVarsWithUserEnv = await res2?.json() + + await devWithUserEnv.stop() + + // Both platform and user-defined env vars should be present + expect(envVarsWithUserEnv).toHaveProperty('NETLIFY_LOCAL', 'true') + expect(envVarsWithUserEnv).toHaveProperty('CONTEXT', 'dev') + expect(envVarsWithUserEnv).toHaveProperty('MY_USER_VAR', 'user value') + + await fixture.destroy() + }) +}) diff --git a/packages/dev/src/main.ts b/packages/dev/src/main.ts index c8f401716..865d51824 100644 --- a/packages/dev/src/main.ts +++ b/packages/dev/src/main.ts @@ -54,6 +54,15 @@ export interface Features { */ environmentVariables?: { enabled?: boolean + /** + * Whether to inject user-defined environment variables from the linked site. + * When false, only platform environment variables (NETLIFY_LOCAL, CONTEXT, SITE_ID, etc.) + * are injected. When true, all environment variables including user-defined ones + * from Netlify UI, account, and config files are injected. + * + * {@default} true + */ + injectUserEnv?: boolean } /** @@ -193,6 +202,7 @@ export class NetlifyDev { db: boolean edgeFunctions: boolean environmentVariables: boolean + environmentVariablesInjectUserEnv: boolean functions: boolean geolocation: boolean headers: boolean @@ -230,6 +240,7 @@ export class NetlifyDev { db: process.env.EXPERIMENTAL_NETLIFY_DB_ENABLED === '1', edgeFunctions: options.edgeFunctions?.enabled !== false, environmentVariables: options.environmentVariables?.enabled !== false, + environmentVariablesInjectUserEnv: options.environmentVariables?.injectUserEnv !== false, functions: options.functions?.enabled !== false, geolocation: options.geolocation?.enabled !== false, headers: options.headers?.enabled !== false, @@ -552,6 +563,7 @@ export class NetlifyDev { accountSlug: config?.siteInfo?.account_slug, baseVariables: config?.env || {}, envAPI: runtime.env, + injectUserEnv: this.#features.environmentVariablesInjectUserEnv, netlifyAPI: config?.api, siteID, }) diff --git a/packages/vite-plugin/README.md b/packages/vite-plugin/README.md index 9e309cc9e..30cdc4b44 100644 --- a/packages/vite-plugin/README.md +++ b/packages/vite-plugin/README.md @@ -31,6 +31,11 @@ The plugin accepts the following options: same way as the Netlify production environment - `blobs`: Configure blob storage functionality - `edgeFunctions`: Configure edge functions +- `environmentVariables`: Configure environment variable injection + - `enabled` (boolean, default: `true`): Enable environment variable injection + - `injectUserEnv` (boolean, default: `true`): Inject user-defined environment variables. When `false`, only platform + environment variables (like `NETLIFY_LOCAL`, `CONTEXT`, `SITE_ID`) are injected, excluding user-defined variables + from Netlify account settings, Netlify UI, config files, and addons - `functions`: Configure serverless functions - `headers`: Configure response headers - `images`: Configure Image CDN functionality @@ -49,3 +54,27 @@ export default defineConfig({ plugins: [netlify()], }) ``` + +### Environment Variables Configuration + +By default, the plugin injects all environment variables including user-defined ones from your Netlify site. If you want +to only inject platform environment variables (useful for frameworks that manage their own environment variables): + +```js +import { defineConfig } from 'vite' +import netlify from '@netlify/vite-plugin' + +export default defineConfig({ + plugins: [ + netlify({ + environmentVariables: { + enabled: true, + injectUserEnv: false, + }, + }), + ], +}) +``` + +With `injectUserEnv: false`, only platform variables like `NETLIFY_LOCAL`, `CONTEXT`, and `SITE_ID` are injected, which +are required for platform features like `purgeCache()` to work correctly. diff --git a/packages/vite-plugin/src/main.test.ts b/packages/vite-plugin/src/main.test.ts index c7581e858..ac11c4910 100644 --- a/packages/vite-plugin/src/main.test.ts +++ b/packages/vite-plugin/src/main.test.ts @@ -158,6 +158,61 @@ describe.for([['5.0.0'], ['6.0.0'], ['7.0.0']])('Vite %s', ([viteVersion]) => { await fixture.destroy() }) + test('Populates platform env vars when injectUserEnv is false', async () => { + const fixture = new Fixture() + .withFile( + 'vite.config.js', + `import { defineConfig } from 'vite'; + import netlify from '@netlify/vite-plugin'; + + export default defineConfig({ + plugins: [ + netlify({ + middleware: false, + environmentVariables: { + enabled: true, + injectUserEnv: false + } + }) + ] + });`, + ) + .withFile( + 'netlify.toml', + `[context.dev.environment] + MY_USER_VAR = "user value"`, + ) + .withFile( + 'index.html', + ` + + Hello World +

Hello from the browser

+ `, + ) + const directory = await fixture.create() + await fixture + .withPackages({ + vite: viteVersion, + '@netlify/vite-plugin': pathToFileURL(path.resolve(directory, PLUGIN_PATH)).toString(), + }) + .create() + + const { server } = await startTestServer({ + root: directory, + }) + + // Platform env vars should be present + expect(process.env).toHaveProperty('NETLIFY_LOCAL', 'true') + expect(process.env).toHaveProperty('CONTEXT', 'dev') + + // User-defined env vars should NOT be present when injectUserEnv is false + expect(process.env).not.toHaveProperty('MY_USER_VAR') + + await server.close() + await fixture.destroy() + }) + test('Prints a basic message on server start', async () => { const fixture = new Fixture() .withFile(