Compare commits
3 commits
main
...
fix/proces
Author | SHA1 | Date | |
---|---|---|---|
|
e43c2f56da | ||
|
02c525ecc0 | ||
|
832ecbabb1 |
21 changed files with 165 additions and 46 deletions
7
.changeset/khaki-walls-sniff.md
Normal file
7
.changeset/khaki-walls-sniff.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Expose `loadEnv` helper in `astro/config` to load environment variables inside of a config file.
|
||||
|
||||
Ensure that Vite's [built-in environment variables](https://vitejs.dev/guide/env-and-mode.html#env-variables) are always included.
|
5
packages/astro/config.d.ts
vendored
5
packages/astro/config.d.ts
vendored
|
@ -5,3 +5,8 @@ type AstroUserConfig = import('./dist/types/@types/astro').AstroUserConfig;
|
|||
* https://astro.build/config
|
||||
*/
|
||||
export function defineConfig(config: AstroUserConfig): AstroUserConfig;
|
||||
|
||||
/**
|
||||
* Synchronously load environment variables from default location
|
||||
*/
|
||||
export function loadEnv(): Promise<Record<string, any>>;
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
export async function loadEnv() {
|
||||
const { loadEnv: loadViteEnv } = await import('vite');
|
||||
const { MODE } = process.env;
|
||||
const PROD = MODE === 'production';
|
||||
const env = loadViteEnv(MODE, process.cwd(), '');
|
||||
return { ...env, MODE, DEV: !PROD, PROD };
|
||||
}
|
||||
|
||||
export function defineConfig(config) {
|
||||
return config;
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ export interface AstroComponentMetadata {
|
|||
|
||||
/** The flags supported by the Astro CLI */
|
||||
export interface CLIFlags {
|
||||
mode?: string;
|
||||
root?: string;
|
||||
site?: string;
|
||||
host?: string | boolean;
|
||||
|
|
|
@ -382,6 +382,7 @@ function resolveFlags(flags: Partial<Flags>): CLIFlags {
|
|||
site: typeof flags.site === 'string' ? flags.site : undefined,
|
||||
port: typeof flags.port === 'number' ? flags.port : undefined,
|
||||
config: typeof flags.config === 'string' ? flags.config : undefined,
|
||||
mode: typeof flags.mode === 'string' ? flags.mode : undefined,
|
||||
host:
|
||||
typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined,
|
||||
experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : undefined,
|
||||
|
@ -459,6 +460,14 @@ interface OpenConfigResult {
|
|||
root: string;
|
||||
}
|
||||
|
||||
const PRODUCTION_COMMANDS = new Set(['build', 'preview']);
|
||||
function resolveMode({ cmd, flags }: Omit<LoadConfigOptions, 'flags'> & { flags: CLIFlags }): string {
|
||||
const { mode } = flags;
|
||||
if (mode) return mode;
|
||||
if (PRODUCTION_COMMANDS.has(cmd)) return 'production';
|
||||
return 'development';
|
||||
}
|
||||
|
||||
/** Load a configuration file, returning both the userConfig and astroConfig */
|
||||
export async function openConfig(configOptions: LoadConfigOptions): Promise<OpenConfigResult> {
|
||||
const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd();
|
||||
|
@ -473,6 +482,14 @@ export async function openConfig(configOptions: LoadConfigOptions): Promise<Open
|
|||
);
|
||||
}
|
||||
|
||||
// Assign built-in env variables to process.env so they're available when using `loadEnv`
|
||||
// see https://vitejs.dev/guide/env-and-mode.html#env-variables
|
||||
const mode = resolveMode({ ...configOptions, flags });
|
||||
const PROD = mode === 'production';
|
||||
const DEV = !PROD;
|
||||
Object.assign(process.env, { MODE: mode, PROD: PROD || undefined, DEV: DEV || undefined })
|
||||
|
||||
|
||||
// Automatically load config file using Proload
|
||||
// If `userConfigPath` is `undefined`, Proload will search for `astro.config.[cm]?[jt]s`
|
||||
let config;
|
||||
|
@ -501,44 +518,6 @@ export async function openConfig(configOptions: LoadConfigOptions): Promise<Open
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to load an `astro.config.mjs` file
|
||||
* @deprecated
|
||||
*/
|
||||
export async function loadConfig(configOptions: LoadConfigOptions): Promise<AstroConfig> {
|
||||
const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd();
|
||||
const flags = resolveFlags(configOptions.flags || {});
|
||||
let userConfig: AstroUserConfig = {};
|
||||
let userConfigPath: string | undefined;
|
||||
|
||||
if (flags?.config) {
|
||||
userConfigPath = /^\.*\//.test(flags.config) ? flags.config : `./${flags.config}`;
|
||||
userConfigPath = fileURLToPath(
|
||||
new URL(userConfigPath, appendForwardSlash(pathToFileURL(root).toString()))
|
||||
);
|
||||
}
|
||||
|
||||
// Automatically load config file using Proload
|
||||
// If `userConfigPath` is `undefined`, Proload will search for `astro.config.[cm]?[jt]s`
|
||||
let config;
|
||||
try {
|
||||
config = await load('astro', {
|
||||
mustExist: !!userConfigPath,
|
||||
cwd: root,
|
||||
filePath: userConfigPath,
|
||||
});
|
||||
} catch (err) {
|
||||
if (err instanceof ProloadError && flags.config) {
|
||||
throw new Error(`Unable to resolve --config "${flags.config}"! Does the file exist?`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (config) {
|
||||
userConfig = config.value;
|
||||
}
|
||||
return resolveConfig(userConfig, root, flags, configOptions.cmd);
|
||||
}
|
||||
|
||||
/** Attempt to resolve an Astro configuration object. Normalize, validate, and return. */
|
||||
export async function resolveConfig(
|
||||
userConfig: AstroUserConfig,
|
||||
|
|
|
@ -77,6 +77,14 @@ export default function envVitePlugin({
|
|||
if (typeof privateEnv === 'undefined') {
|
||||
privateEnv = getPrivateEnv(config, astroConfig);
|
||||
if (privateEnv) {
|
||||
// Built-in env variables
|
||||
// See https://vitejs.dev/guide/env-and-mode.html#env-variables
|
||||
privateEnv.MODE = `'${config.mode}'`;
|
||||
privateEnv.PROD = config.mode === 'production' ? 'true' : 'false';
|
||||
privateEnv.DEV = config.mode !== 'production' ? 'true' : 'false';
|
||||
privateEnv.BASE_URL = astroConfig.base ? `'${astroConfig.base}'` : 'undefined';
|
||||
|
||||
// Astro built-in env variables
|
||||
privateEnv.SITE = astroConfig.site ? `'${astroConfig.site}'` : 'undefined';
|
||||
privateEnv.SSR = JSON.stringify(true);
|
||||
const entries = Object.entries(privateEnv).map(([key, value]) => [
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Environment Variables', () => {
|
||||
|
@ -21,6 +22,7 @@ describe('Environment Variables', () => {
|
|||
|
||||
expect(indexHtml).to.include('CLUB_33');
|
||||
expect(indexHtml).to.include('BLUE_BAYOU');
|
||||
expect(indexHtml).to.include('production');
|
||||
});
|
||||
|
||||
it('does render destructured public env and private env', async () => {
|
||||
|
@ -28,6 +30,7 @@ describe('Environment Variables', () => {
|
|||
|
||||
expect(indexHtml).to.include('CLUB_33');
|
||||
expect(indexHtml).to.include('BLUE_BAYOU');
|
||||
expect(indexHtml).to.include('production');
|
||||
});
|
||||
|
||||
it('does render builtin SITE env', async () => {
|
||||
|
@ -62,6 +65,27 @@ describe('Environment Variables', () => {
|
|||
expect(found).to.equal(true, 'found the public env variable in the JS build');
|
||||
});
|
||||
|
||||
it('includes built-in MODE in client-side JS', async () => {
|
||||
let dirs = await fixture.readdir('/');
|
||||
let found = false;
|
||||
|
||||
// Look in all of the .js files to see if the public env is inlined.
|
||||
// Testing this way prevents hardcoding expected js files.
|
||||
// If we find it in any of them that's good enough to know its working.
|
||||
await Promise.all(
|
||||
dirs.map(async (path) => {
|
||||
if (path.endsWith('.js')) {
|
||||
let js = await fixture.readFile(`/${path}`);
|
||||
if (js.includes('production')) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
expect(found).to.equal(true, 'found the public env variable in the JS build');
|
||||
});
|
||||
|
||||
it('does not include private env in client-side JS', async () => {
|
||||
let dirs = await fixture.readdir('/');
|
||||
let found = false;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { fileURLToPath } from 'url';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { build as astroBuild } from '#astro/build';
|
||||
import { loadConfig } from '#astro/config';
|
||||
import { openConfig } from '#astro/config';
|
||||
import { Benchmark } from './benchmark.js';
|
||||
import del from 'del';
|
||||
import { Writable } from 'stream';
|
||||
|
@ -24,7 +24,7 @@ export const errorWritable = new Writable({
|
|||
|
||||
let build;
|
||||
async function setupBuild() {
|
||||
const astroConfig = await loadConfig(fileURLToPath(snowpackExampleRoot));
|
||||
const { astroConfig } = await openConfig({ cwd: fileURLToPath(snowpackExampleRoot) });
|
||||
|
||||
const logging = {
|
||||
level: 'error',
|
||||
|
|
25
packages/astro/test/config-env.test.js
Normal file
25
packages/astro/test/config-env.test.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
// NOTE: These tests use two different (but identical) fixtures!
|
||||
// This ensures that Node's `require` cache doesn't break our tests
|
||||
describe('config loadEnv', () => {
|
||||
it('sets mode to development', async () => {
|
||||
const fixture = await loadFixture({ root: './fixtures/config-env-1/' }, { cmd: 'dev' });
|
||||
await fixture.build();
|
||||
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
expect($('#site').text()).to.match(/development\.my-site\.com/);
|
||||
});
|
||||
|
||||
it('sets mode to production', async () => {
|
||||
const fixture = await loadFixture({ root: './fixtures/config-env-2/' }, { cmd: 'build' });
|
||||
await fixture.build();
|
||||
|
||||
const html = await fixture.readFile('/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
expect($('#site').text()).to.match(/production\.my-site\.com/);
|
||||
});
|
||||
});
|
|
@ -2,6 +2,9 @@
|
|||
<div id="client-component">
|
||||
{{ PUBLIC_PLACE }}
|
||||
</div>
|
||||
<div id="client-mode">
|
||||
{{ MODE }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -10,6 +13,7 @@ export default {
|
|||
return {
|
||||
PUBLIC_PLACE: import.meta.env.PUBLIC_PLACE,
|
||||
SECRET_PLACE: import.meta.env.SECRET_PLACE,
|
||||
MODE: import.meta.env.MODE,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
---
|
||||
const { PUBLIC_PLACE, SECRET_PLACE, SITE } = import.meta.env;
|
||||
const { PUBLIC_PLACE, SECRET_PLACE, SITE, MODE } = import.meta.env;
|
||||
---
|
||||
<environment-variable>{PUBLIC_PLACE}</environment-variable>
|
||||
<environment-variable>{SECRET_PLACE}</environment-variable>
|
||||
<environment-variable>{SITE}</environment-variable>
|
||||
<environment-variable>{MODE}</environment-variable>
|
||||
|
|
|
@ -4,4 +4,5 @@ import Client from '../components/Client.vue';
|
|||
<environment-variable>{import.meta.env.PUBLIC_PLACE}</environment-variable>
|
||||
<environment-variable>{import.meta.env.SECRET_PLACE}</environment-variable>
|
||||
<environment-variable>{import.meta.env.SITE}</environment-variable>
|
||||
<environment-variable>{import.meta.env.MODE}</environment-variable>
|
||||
<Client client:load />
|
||||
|
|
7
packages/astro/test/fixtures/config-env-1/astro.config.ts
vendored
Normal file
7
packages/astro/test/fixtures/config-env-1/astro.config.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig, loadEnv } from 'astro/config';
|
||||
|
||||
const { MODE } = await loadEnv();
|
||||
|
||||
export default defineConfig({
|
||||
site: `https://${MODE}.my-site.com`
|
||||
})
|
8
packages/astro/test/fixtures/config-env-1/package.json
vendored
Normal file
8
packages/astro/test/fixtures/config-env-1/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/config-env",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
5
packages/astro/test/fixtures/config-env-1/src/pages/index.astro
vendored
Normal file
5
packages/astro/test/fixtures/config-env-1/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
const { site } = Astro;
|
||||
---
|
||||
|
||||
<div id="site">{site}</div>
|
7
packages/astro/test/fixtures/config-env-2/astro.config.ts
vendored
Normal file
7
packages/astro/test/fixtures/config-env-2/astro.config.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { defineConfig, loadEnv } from 'astro/config';
|
||||
|
||||
const { MODE } = await loadEnv();
|
||||
|
||||
export default defineConfig({
|
||||
site: `https://${MODE}.my-site.com`
|
||||
})
|
8
packages/astro/test/fixtures/config-env-2/package.json
vendored
Normal file
8
packages/astro/test/fixtures/config-env-2/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/config-env",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
5
packages/astro/test/fixtures/config-env-2/src/pages/index.astro
vendored
Normal file
5
packages/astro/test/fixtures/config-env-2/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
const { site } = Astro;
|
||||
---
|
||||
|
||||
<div id="site">{site}</div>
|
|
@ -2,7 +2,7 @@ import { execa } from 'execa';
|
|||
import { polyfill } from '@astrojs/webapi';
|
||||
import fs from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { resolveConfig, loadConfig } from '../dist/core/config.js';
|
||||
import { openConfig } from '../dist/core/config.js';
|
||||
import dev from '../dist/core/dev/index.js';
|
||||
import build from '../dist/core/build/index.js';
|
||||
import preview from '../dist/core/preview/index.js';
|
||||
|
@ -35,11 +35,15 @@ polyfill(globalThis, {
|
|||
* @property {() => Promise<void>} clean
|
||||
* @property {() => Promise<App>} loadTestAdapterApp
|
||||
* @property {() => Promise<void>} onNextChange
|
||||
*
|
||||
* @typedef {Object} FixtureOpts
|
||||
* @property {'dev'|'build'|undefined} cmd
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load Astro fixture
|
||||
* @param {AstroConfig} inlineConfig Astro config partial (note: must specify `root`)
|
||||
* @param {FixtureOpts|undefined} opts Additional options for fixture
|
||||
* @returns {Promise<Fixture>} The fixture. Has the following properties:
|
||||
* .config - Returns the final config. Will be automatically passed to the methods below:
|
||||
*
|
||||
|
@ -57,7 +61,7 @@ polyfill(globalThis, {
|
|||
* Clean-up
|
||||
* .clean() - Async. Removes the project’s dist folder.
|
||||
*/
|
||||
export async function loadFixture(inlineConfig) {
|
||||
export async function loadFixture(inlineConfig, opts) {
|
||||
if (!inlineConfig || !inlineConfig.root)
|
||||
throw new Error("Must provide { root: './fixtures/...' }");
|
||||
|
||||
|
@ -72,7 +76,7 @@ export async function loadFixture(inlineConfig) {
|
|||
}
|
||||
}
|
||||
// Load the config.
|
||||
let config = await loadConfig({ cwd: fileURLToPath(cwd) });
|
||||
let { astroConfig: config } = await openConfig({ cwd: fileURLToPath(cwd), cmd: opts?.cmd });
|
||||
config = merge(config, { ...inlineConfig, root: cwd });
|
||||
|
||||
// Note: the inline config doesn't run through config validation where these normalizations usually occur
|
||||
|
|
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
|
@ -1347,6 +1347,18 @@ importers:
|
|||
devDependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/config-env-1:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/config-env-2:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/config-host:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { fileURLToPath } from 'url';
|
||||
import v8 from 'v8';
|
||||
import dev from '../../packages/astro/dist/core/dev/index.js';
|
||||
import { loadConfig } from '../../packages/astro/dist/core/config.js';
|
||||
import { openConfig } from '../../packages/astro/dist/core/config.js';
|
||||
import prettyBytes from 'pretty-bytes';
|
||||
|
||||
if (!global.gc) {
|
||||
|
@ -14,7 +14,7 @@ const isCI = process.argv.includes('--ci');
|
|||
/** URL directory containing the entire project. */
|
||||
const projDir = new URL('./project/', import.meta.url);
|
||||
|
||||
let config = await loadConfig({
|
||||
let { astroConfig: config } = await openConfig({
|
||||
cwd: fileURLToPath(projDir),
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue