Skip to content

Commit ea71b90

Browse files
feat(next): make astro:env stable (#11679)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
1 parent 4cd6c43 commit ea71b90

File tree

32 files changed

+210
-282
lines changed

32 files changed

+210
-282
lines changed

.changeset/eighty-boxes-applaud.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
'astro': major
3+
---
4+
5+
The `astro:env` feature introduced behind a flag in [v4.10.0](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md#x4100) is no longer experimental and is available for general use. If you have been waiting for stabilization before using `astro:env`, you can now do so.
6+
7+
This feature lets you configure a type-safe schema for your environment variables, and indicate whether they should be available on the server or the client.
8+
9+
To configure a schema, add the `env` option to your Astro config and define your client and server variables. If you were previously using this feature, please remove the experimental flag from your Astro config and move your entire `env` configuration unchanged to a top-level option.
10+
11+
```js
12+
import { defineConfig, envField } from 'astro/config'
13+
14+
export default defineConfig({
15+
env: {
16+
schema: {
17+
API_URL: envField.string({ context: "client", access: "public", optional: true }),
18+
PORT: envField.number({ context: "server", access: "public", default: 4321 }),
19+
API_SECRET: envField.string({ context: "server", access: "secret" }),
20+
}
21+
}
22+
})
23+
```
24+
25+
You can import and use your defined variables from the appropriate `/client` or `/server` module:
26+
27+
```astro
28+
---
29+
import { API_URL } from "astro:env/client"
30+
import { API_SECRET_TOKEN } from "astro:env/server"
31+
32+
const data = await fetch(`${API_URL}/users`, {
33+
method: "GET",
34+
headers: {
35+
"Content-Type": "application/json",
36+
"Authorization": `Bearer ${API_SECRET_TOKEN}`
37+
},
38+
})
39+
---
40+
41+
<script>
42+
import { API_URL } from "astro:env/client"
43+
44+
fetch(`${API_URL}/ping`)
45+
</script>
46+
```
47+
48+
Please see our [guide to using environment variables](https://docs.astro.build/en/guides/environment-variables/#astroenv) for more about this feature.

.changeset/selfish-impalas-grin.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@astrojs/vercel': major
3+
'@astrojs/node': major
4+
---
5+
6+
Adds stable support for `astro:env`

packages/astro/client.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/// <reference types="vite/types/import-meta.d.ts" />
22
/// <reference path="./types/content.d.ts" />
33
/// <reference path="./types/actions.d.ts" />
4+
/// <reference path="./types/env.d.ts" />
45

56
interface ImportMetaEnv {
67
/**

packages/astro/src/container/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ function createManifest(
143143
i18n: manifest?.i18n,
144144
checkOrigin: false,
145145
middleware: manifest?.middleware ?? middleware ?? defaultMiddleware,
146-
experimentalEnvGetSecretEnabled: false,
146+
envGetSecretEnabled: false,
147147
key: createKey(),
148148
};
149149
}

packages/astro/src/core/app/types.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ export type SSRManifest = {
6969
i18n: SSRManifestI18n | undefined;
7070
middleware: MiddlewareHandler;
7171
checkOrigin: boolean;
72-
// TODO: remove experimental prefix
73-
experimentalEnvGetSecretEnabled: boolean;
72+
envGetSecretEnabled: boolean;
7473
};
7574

7675
export type SSRManifestI18n = {

packages/astro/src/core/base-pipeline.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export abstract class Pipeline {
6868
}
6969
// In SSR, getSecret should fail by default. Setting it here will run before the
7070
// adapter override.
71-
if (callSetGetEnv && manifest.experimentalEnvGetSecretEnabled) {
71+
if (callSetGetEnv && manifest.envGetSecretEnabled) {
7272
setGetEnv(() => {
7373
throw new AstroError(AstroErrorData.EnvUnsupportedGetSecret);
7474
}, true);

packages/astro/src/core/build/generate.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,6 @@ function createBuildManifest(
530530
middleware,
531531
checkOrigin: settings.config.security?.checkOrigin ?? false,
532532
key,
533-
experimentalEnvGetSecretEnabled: false,
533+
envGetSecretEnabled: false,
534534
};
535535
}

packages/astro/src/core/build/plugins/plugin-chunks.ts

-5
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ export function vitePluginChunks(): VitePlugin {
1616
if (id.includes('astro/dist/runtime')) {
1717
return 'astro';
1818
}
19-
// Place `astro/env/setup` import in its own chunk to prevent Rollup's TLA bug
20-
// https://github.com/rollup/rollup/issues/4708
21-
if (id.includes('astro/dist/env/setup')) {
22-
return 'astro/env-setup';
23-
}
2419
},
2520
});
2621
},

packages/astro/src/core/build/plugins/plugin-manifest.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,7 @@ function buildManifest(
281281
checkOrigin: settings.config.security?.checkOrigin ?? false,
282282
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
283283
key: encodedKey,
284-
experimentalEnvGetSecretEnabled:
285-
settings.config.experimental.env !== undefined &&
284+
envGetSecretEnabled:
286285
(settings.adapter?.supportedAstroFeatures.envGetSecret ?? 'unsupported') !== 'unsupported',
287286
};
288287
}

packages/astro/src/core/config/schema.ts

+12-13
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export const ASTRO_CONFIG_DEFAULTS = {
8282
legacy: {},
8383
redirects: {},
8484
security: {},
85+
env: {
86+
schema: {},
87+
validateSecrets: false,
88+
},
8589
experimental: {
8690
actions: false,
8791
directRenderScript: false,
@@ -90,9 +94,6 @@ export const ASTRO_CONFIG_DEFAULTS = {
9094
globalRoutePriority: false,
9195
serverIslands: false,
9296
contentIntellisense: false,
93-
env: {
94-
validateSecrets: false,
95-
},
9697
contentLayer: false,
9798
},
9899
} satisfies AstroUserConfig & { server: { open: boolean } };
@@ -503,6 +504,14 @@ export const AstroConfigSchema = z.object({
503504
})
504505
.optional()
505506
.default(ASTRO_CONFIG_DEFAULTS.security),
507+
env: z
508+
.object({
509+
schema: EnvSchema.optional().default(ASTRO_CONFIG_DEFAULTS.env.schema),
510+
validateSecrets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.env.validateSecrets),
511+
})
512+
.strict()
513+
.optional()
514+
.default(ASTRO_CONFIG_DEFAULTS.env),
506515
experimental: z
507516
.object({
508517
actions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.actions),
@@ -522,16 +531,6 @@ export const AstroConfigSchema = z.object({
522531
.boolean()
523532
.optional()
524533
.default(ASTRO_CONFIG_DEFAULTS.experimental.globalRoutePriority),
525-
env: z
526-
.object({
527-
schema: EnvSchema.optional(),
528-
validateSecrets: z
529-
.boolean()
530-
.optional()
531-
.default(ASTRO_CONFIG_DEFAULTS.experimental.env.validateSecrets),
532-
})
533-
.strict()
534-
.optional(),
535534
serverIslands: z
536535
.boolean()
537536
.optional()

packages/astro/src/core/create-vite.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export async function createVite(
132132
// the build to run very slow as the filewatcher is triggered often.
133133
mode !== 'build' && vitePluginAstroServer({ settings, logger, fs }),
134134
envVitePlugin({ settings, logger }),
135-
astroEnv({ settings, mode, fs, sync }),
135+
astroEnv({ settings, mode, sync }),
136136
markdownVitePlugin({ settings, logger }),
137137
htmlVitePlugin(),
138138
astroPostprocessVitePlugin(),

packages/astro/src/core/errors/errors-data.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1235,15 +1235,15 @@ export const RouteNotFound = {
12351235
/**
12361236
* @docs
12371237
* @description
1238-
* Some environment variables do not match the data type and/or properties defined in `experimental.env.schema`.
1238+
* Some environment variables do not match the data type and/or properties defined in `env.schema`.
12391239
* @message
1240-
* The following environment variables defined in `experimental.env.schema` are invalid.
1240+
* The following environment variables defined in `env.schema` are invalid.
12411241
*/
12421242
export const EnvInvalidVariables = {
12431243
name: 'EnvInvalidVariables',
12441244
title: 'Invalid Environment Variables',
12451245
message: (errors: Array<string>) =>
1246-
`The following environment variables defined in \`experimental.env.schema\` are invalid:\n\n${errors.map((err) => `- ${err}`).join('\n')}\n`,
1246+
`The following environment variables defined in \`env.schema\` are invalid:\n\n${errors.map((err) => `- ${err}`).join('\n')}\n`,
12471247
} satisfies ErrorData;
12481248

12491249
/**

packages/astro/src/core/sync/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export async function syncInternal({
133133
content: '',
134134
});
135135
}
136-
syncAstroEnv(settings, fs);
136+
syncAstroEnv(settings);
137137

138138
await writeFiles(settings, fs, logger);
139139
logger.info('types', `Generated ${dim(getTimeStat(timerStart, performance.now()))}`);

packages/astro/src/env/constants.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ export const VIRTUAL_MODULES_IDS = {
55
};
66
export const VIRTUAL_MODULES_IDS_VALUES = new Set(Object.values(VIRTUAL_MODULES_IDS));
77

8-
export const ENV_TYPES_FILE = 'env.d.ts';
8+
export const ENV_TYPES_FILE = 'astro/env.d.ts';
99

1010
const PKG_BASE = new URL('../../', import.meta.url);
11-
export const MODULE_TEMPLATE_URL = new URL('templates/env/module.mjs', PKG_BASE);
12-
export const TYPES_TEMPLATE_URL = new URL('templates/env/types.d.ts', PKG_BASE);
11+
export const MODULE_TEMPLATE_URL = new URL('templates/env.mjs', PKG_BASE);

packages/astro/src/env/sync.ts

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
1-
import fsMod from 'node:fs';
21
import type { AstroSettings } from '../types/astro.js';
3-
import { TYPES_TEMPLATE_URL } from './constants.js';
2+
import { ENV_TYPES_FILE } from './constants.js';
43
import { getEnvFieldType } from './validators.js';
54

6-
export function syncAstroEnv(settings: AstroSettings, fs = fsMod): void {
7-
if (!settings.config.experimental.env) {
8-
return;
9-
}
10-
11-
const schema = settings.config.experimental.env.schema ?? {};
12-
5+
export function syncAstroEnv(settings: AstroSettings): void {
136
let client = '';
147
let server = '';
158

16-
for (const [key, options] of Object.entries(schema)) {
17-
const str = `export const ${key}: ${getEnvFieldType(options)}; \n`;
9+
for (const [key, options] of Object.entries(settings.config.env.schema)) {
10+
const str = ` export const ${key}: ${getEnvFieldType(options)}; \n`;
1811
if (options.context === 'client') {
1912
client += str;
2013
} else {
2114
server += str;
2215
}
2316
}
2417

25-
const template = fs.readFileSync(TYPES_TEMPLATE_URL, 'utf-8');
26-
const content = template.replace('// @@CLIENT@@', client).replace('// @@SERVER@@', server);
18+
let content = '';
19+
if (client !== '') {
20+
content = `declare module 'astro:env/client' {
21+
${client}}`;
22+
}
23+
if (server !== '') {
24+
content += `declare module 'astro:env/server' {
25+
${server}}`;
26+
}
2727

28-
settings.injectedTypes.push({
29-
filename: 'astro/env.d.ts',
30-
content,
31-
});
28+
if (content !== '') {
29+
settings.injectedTypes.push({
30+
filename: ENV_TYPES_FILE,
31+
content,
32+
});
33+
}
3234
}

packages/astro/src/env/vite-plugin-env.ts

+7-23
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type fsMod from 'node:fs';
1+
import { readFileSync } from 'node:fs';
22
import { fileURLToPath } from 'node:url';
33
import { type Plugin, loadEnv } from 'vite';
44
import { AstroError, AstroErrorData } from '../core/errors/index.js';
@@ -12,29 +12,14 @@ import { type InvalidVariable, invalidVariablesToError } from './errors.js';
1212
import type { EnvSchema } from './schema.js';
1313
import { getEnvFieldType, validateEnvVariable } from './validators.js';
1414

15-
// TODO: reminders for when astro:env comes out of experimental
16-
// Types should always be generated (like in types/content.d.ts). That means the client module will be empty
17-
// and server will only contain getSecret for unknown variables. Then, specifying a schema should only add
18-
// variables as needed. For secret variables, it will only require specifying SecretValues and it should get
19-
// merged with the static types/content.d.ts
20-
21-
interface AstroEnvVirtualModPluginParams {
15+
interface AstroEnvPluginParams {
2216
settings: AstroSettings;
2317
mode: 'dev' | 'build' | string;
24-
fs: typeof fsMod;
2518
sync: boolean;
2619
}
2720

28-
export function astroEnv({
29-
settings,
30-
mode,
31-
fs,
32-
sync,
33-
}: AstroEnvVirtualModPluginParams): Plugin | undefined {
34-
if (!settings.config.experimental.env) {
35-
return;
36-
}
37-
const schema = settings.config.experimental.env.schema ?? {};
21+
export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin {
22+
const { schema, validateSecrets } = settings.config.env;
3823

3924
let templates: { client: string; server: string; internal: string } | null = null;
4025

@@ -56,12 +41,12 @@ export function astroEnv({
5641
const validatedVariables = validatePublicVariables({
5742
schema,
5843
loadedEnv,
59-
validateSecrets: settings.config.experimental.env?.validateSecrets ?? false,
44+
validateSecrets,
6045
sync,
6146
});
6247

6348
templates = {
64-
...getTemplates(schema, fs, validatedVariables),
49+
...getTemplates(schema, validatedVariables),
6550
internal: `export const schema = ${JSON.stringify(schema)};`,
6651
};
6752
},
@@ -140,11 +125,10 @@ function validatePublicVariables({
140125

141126
function getTemplates(
142127
schema: EnvSchema,
143-
fs: typeof fsMod,
144128
validatedVariables: ReturnType<typeof validatePublicVariables>,
145129
) {
146130
let client = '';
147-
let server = fs.readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
131+
let server = readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
148132
let onSetGetEnv = '';
149133

150134
for (const { key, value, context } of validatedVariables) {

packages/astro/src/integrations/features-validation.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export function validateSupportedFeatures(
8787
adapterName,
8888
logger,
8989
'astro:env getSecret',
90-
() => config?.experimental?.env !== undefined,
90+
() => Object.keys(config?.env?.schema ?? {}).length !== 0,
9191
);
9292

9393
return validationResult;

0 commit comments

Comments
 (0)