diff --git a/.changeset/fresh-ladybugs-think.md b/.changeset/fresh-ladybugs-think.md new file mode 100644 index 0000000000000..bdf29ac191e01 --- /dev/null +++ b/.changeset/fresh-ladybugs-think.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes use of private .env variables with the static build diff --git a/packages/astro/src/vite-plugin-env/index.ts b/packages/astro/src/vite-plugin-env/index.ts index c216b71118575..36a9835b1f55f 100644 --- a/packages/astro/src/vite-plugin-env/index.ts +++ b/packages/astro/src/vite-plugin-env/index.ts @@ -1,5 +1,6 @@ import type * as vite from 'vite'; import type { AstroConfig } from '../@types/astro'; +import type { TransformPluginContext } from 'rollup'; import MagicString from 'magic-string'; import { fileURLToPath } from 'url'; import { loadEnv } from 'vite'; @@ -30,7 +31,7 @@ function getPrivateEnv(viteConfig: vite.ResolvedConfig, astroConfig: AstroConfig if (privateKeys.length === 0) { return null; } - return Object.fromEntries(privateKeys.map((key) => [key, fullEnv[key]])); + return Object.fromEntries(privateKeys.map((key) => [key, JSON.stringify(fullEnv[key])])); } function referencesPrivateKey(source: string, privateEnv: Record) { @@ -43,39 +44,56 @@ function referencesPrivateKey(source: string, privateEnv: Record) { export default function envVitePlugin({ config: astroConfig }: EnvPluginOptions): vite.PluginOption { let privateEnv: Record | null; let config: vite.ResolvedConfig; + let replacements: Record; + let pattern: RegExp | undefined; return { name: 'astro:vite-plugin-env', enforce: 'pre', - configResolved(resolvedConfig) { config = resolvedConfig; - if (config.envPrefix) { - } }, - async transform(source, id, options) { const ssr = options?.ssr === true; - if (!ssr) return source; - if (!source.includes('import.meta')) return source; - if (!/\benv\b/.test(source)) return source; + + if(!ssr) { + return source; + } + + if(!source.includes('import.meta') || !/\benv\b/.test(source)) { + return source; + } if (typeof privateEnv === 'undefined') { privateEnv = getPrivateEnv(config, astroConfig); + if(privateEnv) { + const entries = Object.entries(privateEnv).map(([key, value]) => ([`import.meta.env.${key}`, value])); + replacements = Object.fromEntries(entries); + pattern = new RegExp( + // Do not allow preceding '.', but do allow preceding '...' for spread operations + '(? { + return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); + }) + .join('|') + + // prevent trailing assignments + ')\\b(?!\\s*?=[^=])', 'g'); + } } - if (!privateEnv) return source; + + if (!privateEnv || !pattern) return source; if (!referencesPrivateKey(source, privateEnv)) return source; + // Find matches for *private* env and do our own replacement. const s = new MagicString(source); - // prettier-ignore - s.prepend(`import.meta.env = new Proxy(import.meta.env, {` + - `get(target, prop, reciever) {` + - `const PRIVATE = ${JSON.stringify(privateEnv)};` + - `if (typeof PRIVATE[prop] !== 'undefined') {` + - `return PRIVATE[prop];` + - `}` + - `return Reflect.get(target, prop, reciever);` + - `}` + - `});\n`); + let match: RegExpExecArray | null + + while ((match = pattern.exec(source))) { + const start = match.index + const end = start + match[0].length + const replacement = '' + replacements[match[1]] + s.overwrite(start, end, replacement) + } return s.toString(); }, diff --git a/packages/astro/test/astro-envs.test.js b/packages/astro/test/astro-envs.test.js index f9056b4163620..3f4720c33090e 100644 --- a/packages/astro/test/astro-envs.test.js +++ b/packages/astro/test/astro-envs.test.js @@ -7,7 +7,6 @@ describe('Environment Variables', () => { before(async () => { fixture = await loadFixture({ projectRoot: './fixtures/astro-envs/', - buildOptions: { legacyBuild: true } // TODO make this test work without legacyBuild }); await fixture.build(); @@ -25,7 +24,7 @@ describe('Environment Variables', () => { }); it('includes public env in client-side JS', async () => { - let dirs = await fixture.readdir('/assets'); + let dirs = await fixture.readdir('/'); let found = false; // Look in all of the .js files to see if the public env is inlined. @@ -34,7 +33,7 @@ describe('Environment Variables', () => { await Promise.all( dirs.map(async (path) => { if (path.endsWith('.js')) { - let js = await fixture.readFile(`/assets/${path}`); + let js = await fixture.readFile(`/${path}`); if (js.includes('BLUE_BAYOU')) { found = true; } @@ -46,7 +45,7 @@ describe('Environment Variables', () => { }); it('does not include private env in client-side JS', async () => { - let dirs = await fixture.readdir('/assets'); + let dirs = await fixture.readdir('/'); let found = false; // Look in all of the .js files to see if the public env is inlined. @@ -55,7 +54,7 @@ describe('Environment Variables', () => { await Promise.all( dirs.map(async (path) => { if (path.endsWith('.js')) { - let js = await fixture.readFile(`/assets/${path}`); + let js = await fixture.readFile(`/${path}`); if (js.includes('CLUB_33')) { found = true; } diff --git a/packages/astro/test/lit-element.test.js b/packages/astro/test/lit-element.test.js index c761239e913f9..a341499a9fd66 100644 --- a/packages/astro/test/lit-element.test.js +++ b/packages/astro/test/lit-element.test.js @@ -18,9 +18,6 @@ describe('LitElement test', function () { fixture = await loadFixture({ projectRoot: './fixtures/lit-element/', renderers: ['@astrojs/renderer-lit'], - buildOptions: { - legacyBuild: true - } }); await fixture.build(); });