Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(next): make astro:env stable #11679

Merged
merged 27 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f108a14
chore: add todos
florian-lefebvre Aug 12, 2024
31a3b3a
feat: work on types and codegen
florian-lefebvre Aug 12, 2024
da6b613
feat: work on todos
florian-lefebvre Aug 12, 2024
18978c7
feat: update adapters
florian-lefebvre Aug 12, 2024
d835645
chore: update tests
florian-lefebvre Aug 12, 2024
5bee7ff
fix: do not use fs mock
florian-lefebvre Aug 12, 2024
d878774
feat: work on jsdoc
florian-lefebvre Aug 12, 2024
b3bc57b
fix: feature validation test
florian-lefebvre Aug 13, 2024
2ed705c
chore: changeset
florian-lefebvre Aug 13, 2024
9d6dd21
chore: remove setup chunk
florian-lefebvre Aug 13, 2024
0962b38
feat: do not use TLA
florian-lefebvre Aug 13, 2024
ff30bfb
Merge branch 'next' into feat/unflag-astro-env
florian-lefebvre Aug 13, 2024
74be1bd
fix: only create env reference if needed
florian-lefebvre Aug 13, 2024
9727e9f
Merge branch 'feat/unflag-astro-env' of https://github.com/withastro/…
florian-lefebvre Aug 13, 2024
23ea790
Merge remote-tracking branch 'origin/next' into feat/unflag-astro-env
florian-lefebvre Aug 14, 2024
a497e44
feat: misc
florian-lefebvre Aug 14, 2024
24e9150
feat: bump peer deps
florian-lefebvre Aug 14, 2024
e181f3f
feat: address reviews
florian-lefebvre Aug 15, 2024
5c97a92
feat: update changeset
florian-lefebvre Aug 15, 2024
acce9b8
Merge branch 'next' into feat/unflag-astro-env
florian-lefebvre Aug 16, 2024
7940a64
Merge branch 'next' into feat/unflag-astro-env
florian-lefebvre Aug 19, 2024
03abff1
feat: address reviews
florian-lefebvre Aug 20, 2024
5bde388
feat: update errors
florian-lefebvre Aug 20, 2024
8e87103
Merge branch 'next' into feat/unflag-astro-env
florian-lefebvre Aug 20, 2024
f7a156d
fix: fixture
florian-lefebvre Aug 20, 2024
24c5720
Sarah edit of changeset
sarah11918 Aug 20, 2024
fbb460e
tiny changeset edit
sarah11918 Aug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .changeset/eighty-boxes-applaud.md
florian-lefebvre marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
'astro': major
---

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.

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.

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.

```js
import { defineConfig, envField } from 'astro/config'

export default defineConfig({
env: {
schema: {
API_URL: envField.string({ context: "client", access: "public", optional: true }),
PORT: envField.number({ context: "server", access: "public", default: 4321 }),
API_SECRET: envField.string({ context: "server", access: "secret" }),
}
}
})
```

You can import and use your defined variables from the appropriate `/client` or `/server` module:

```astro
---
import { API_URL } from "astro:env/client"
import { API_SECRET_TOKEN } from "astro:env/server"

const data = await fetch(`${API_URL}/users`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_SECRET_TOKEN}`
},
})
---

<script>
import { API_URL } from "astro:env/client"

fetch(`${API_URL}/ping`)
</script>
```

Please see our [guide to using environment variables](https://docs.astro.build/en/guides/environment-variables/#astroenv) for more about this feature.
6 changes: 6 additions & 0 deletions .changeset/selfish-impalas-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astrojs/vercel': major
'@astrojs/node': major
---

Adds stable support for `astro:env`
1 change: 1 addition & 0 deletions packages/astro/client.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="vite/types/import-meta.d.ts" />
/// <reference path="./types/content.d.ts" />
/// <reference path="./types/actions.d.ts" />
/// <reference path="./types/env.d.ts" />

interface ImportMetaEnv {
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ function createManifest(
i18n: manifest?.i18n,
checkOrigin: false,
middleware: manifest?.middleware ?? middleware ?? defaultMiddleware,
experimentalEnvGetSecretEnabled: false,
envGetSecretEnabled: false,
key: createKey(),
};
}
Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ export type SSRManifest = {
i18n: SSRManifestI18n | undefined;
middleware: MiddlewareHandler;
checkOrigin: boolean;
// TODO: remove experimental prefix
experimentalEnvGetSecretEnabled: boolean;
envGetSecretEnabled: boolean;
};

export type SSRManifestI18n = {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/base-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export abstract class Pipeline {
}
// In SSR, getSecret should fail by default. Setting it here will run before the
// adapter override.
if (callSetGetEnv && manifest.experimentalEnvGetSecretEnabled) {
if (callSetGetEnv && manifest.envGetSecretEnabled) {
setGetEnv(() => {
throw new AstroError(AstroErrorData.EnvUnsupportedGetSecret);
}, true);
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/build/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,6 @@ function createBuildManifest(
middleware,
checkOrigin: settings.config.security?.checkOrigin ?? false,
key,
experimentalEnvGetSecretEnabled: false,
envGetSecretEnabled: false,
};
}
5 changes: 0 additions & 5 deletions packages/astro/src/core/build/plugins/plugin-chunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ export function vitePluginChunks(): VitePlugin {
if (id.includes('astro/dist/runtime')) {
return 'astro';
}
// Place `astro/env/setup` import in its own chunk to prevent Rollup's TLA bug
// https://github.com/rollup/rollup/issues/4708
if (id.includes('astro/dist/env/setup')) {
florian-lefebvre marked this conversation as resolved.
Show resolved Hide resolved
return 'astro/env-setup';
}
},
});
},
Expand Down
3 changes: 1 addition & 2 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,7 @@ function buildManifest(
checkOrigin: settings.config.security?.checkOrigin ?? false,
serverIslandNameMap: Array.from(settings.serverIslandNameMap),
key: encodedKey,
experimentalEnvGetSecretEnabled:
settings.config.experimental.env !== undefined &&
envGetSecretEnabled:
(settings.adapter?.supportedAstroFeatures.envGetSecret ?? 'unsupported') !== 'unsupported',
};
}
25 changes: 12 additions & 13 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export const ASTRO_CONFIG_DEFAULTS = {
legacy: {},
redirects: {},
security: {},
env: {
schema: {},
validateSecrets: false,
},
experimental: {
actions: false,
directRenderScript: false,
Expand All @@ -90,9 +94,6 @@ export const ASTRO_CONFIG_DEFAULTS = {
globalRoutePriority: false,
serverIslands: false,
contentIntellisense: false,
env: {
validateSecrets: false,
},
contentLayer: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };
Expand Down Expand Up @@ -507,6 +508,14 @@ export const AstroConfigSchema = z.object({
})
.optional()
.default(ASTRO_CONFIG_DEFAULTS.security),
env: z
.object({
schema: EnvSchema.optional().default(ASTRO_CONFIG_DEFAULTS.env.schema),
validateSecrets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.env.validateSecrets),
})
.strict()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.env),
experimental: z
.object({
actions: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.actions),
Expand All @@ -526,16 +535,6 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.globalRoutePriority),
env: z
.object({
schema: EnvSchema.optional(),
validateSecrets: z
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.env.validateSecrets),
})
.strict()
.optional(),
serverIslands: z
.boolean()
.optional()
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export async function createVite(
// the build to run very slow as the filewatcher is triggered often.
mode !== 'build' && vitePluginAstroServer({ settings, logger, fs }),
envVitePlugin({ settings, logger }),
astroEnv({ settings, mode, fs, sync }),
astroEnv({ settings, mode, sync }),
markdownVitePlugin({ settings, logger }),
htmlVitePlugin(),
astroPostprocessVitePlugin(),
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1235,15 +1235,15 @@ export const RouteNotFound = {
/**
* @docs
* @description
* Some environment variables do not match the data type and/or properties defined in `experimental.env.schema`.
* Some environment variables do not match the data type and/or properties defined in `env.schema`.
* @message
* The following environment variables defined in `experimental.env.schema` are invalid.
* The following environment variables defined in `env.schema` are invalid.
*/
export const EnvInvalidVariables = {
name: 'EnvInvalidVariables',
title: 'Invalid Environment Variables',
message: (errors: Array<string>) =>
`The following environment variables defined in \`experimental.env.schema\` are invalid:\n\n${errors.map((err) => `- ${err}`).join('\n')}\n`,
`The following environment variables defined in \`env.schema\` are invalid:\n\n${errors.map((err) => `- ${err}`).join('\n')}\n`,
} satisfies ErrorData;

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/core/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export async function syncInternal({
content: '',
});
}
syncAstroEnv(settings, fs);
syncAstroEnv(settings);

await writeFiles(settings, fs, logger);
logger.info('types', `Generated ${dim(getTimeStat(timerStart, performance.now()))}`);
Expand Down
5 changes: 2 additions & 3 deletions packages/astro/src/env/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ export const VIRTUAL_MODULES_IDS = {
};
export const VIRTUAL_MODULES_IDS_VALUES = new Set(Object.values(VIRTUAL_MODULES_IDS));

export const ENV_TYPES_FILE = 'env.d.ts';
export const ENV_TYPES_FILE = 'astro/env.d.ts';

const PKG_BASE = new URL('../../', import.meta.url);
export const MODULE_TEMPLATE_URL = new URL('templates/env/module.mjs', PKG_BASE);
export const TYPES_TEMPLATE_URL = new URL('templates/env/types.d.ts', PKG_BASE);
export const MODULE_TEMPLATE_URL = new URL('templates/env.mjs', PKG_BASE);
36 changes: 19 additions & 17 deletions packages/astro/src/env/sync.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import fsMod from 'node:fs';
import type { AstroSettings } from '../types/astro.js';
import { TYPES_TEMPLATE_URL } from './constants.js';
import { ENV_TYPES_FILE } from './constants.js';
import { getEnvFieldType } from './validators.js';

export function syncAstroEnv(settings: AstroSettings, fs = fsMod): void {
if (!settings.config.experimental.env) {
return;
}

const schema = settings.config.experimental.env.schema ?? {};

export function syncAstroEnv(settings: AstroSettings): void {
let client = '';
let server = '';

for (const [key, options] of Object.entries(schema)) {
const str = `export const ${key}: ${getEnvFieldType(options)}; \n`;
for (const [key, options] of Object.entries(settings.config.env.schema)) {
const str = ` export const ${key}: ${getEnvFieldType(options)}; \n`;
if (options.context === 'client') {
client += str;
} else {
server += str;
}
}

const template = fs.readFileSync(TYPES_TEMPLATE_URL, 'utf-8');
const content = template.replace('// @@CLIENT@@', client).replace('// @@SERVER@@', server);
let content = '';
if (client !== '') {
content = `declare module 'astro:env/client' {
${client}}`;
}
if (server !== '') {
content += `declare module 'astro:env/server' {
${server}}`;
}

settings.injectedTypes.push({
filename: 'astro/env.d.ts',
content,
});
if (content !== '') {
settings.injectedTypes.push({
filename: ENV_TYPES_FILE,
content,
});
}
}
30 changes: 7 additions & 23 deletions packages/astro/src/env/vite-plugin-env.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type fsMod from 'node:fs';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { type Plugin, loadEnv } from 'vite';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
Expand All @@ -12,29 +12,14 @@ import { type InvalidVariable, invalidVariablesToError } from './errors.js';
import type { EnvSchema } from './schema.js';
import { getEnvFieldType, validateEnvVariable } from './validators.js';

// TODO: reminders for when astro:env comes out of experimental
// Types should always be generated (like in types/content.d.ts). That means the client module will be empty
// and server will only contain getSecret for unknown variables. Then, specifying a schema should only add
// variables as needed. For secret variables, it will only require specifying SecretValues and it should get
// merged with the static types/content.d.ts

interface AstroEnvVirtualModPluginParams {
interface AstroEnvPluginParams {
settings: AstroSettings;
mode: 'dev' | 'build' | string;
fs: typeof fsMod;
sync: boolean;
}

export function astroEnv({
settings,
mode,
fs,
sync,
}: AstroEnvVirtualModPluginParams): Plugin | undefined {
if (!settings.config.experimental.env) {
return;
}
const schema = settings.config.experimental.env.schema ?? {};
export function astroEnv({ settings, mode, sync }: AstroEnvPluginParams): Plugin {
const { schema, validateSecrets } = settings.config.env;

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

Expand All @@ -56,12 +41,12 @@ export function astroEnv({
const validatedVariables = validatePublicVariables({
schema,
loadedEnv,
validateSecrets: settings.config.experimental.env?.validateSecrets ?? false,
validateSecrets,
sync,
});

templates = {
...getTemplates(schema, fs, validatedVariables),
...getTemplates(schema, validatedVariables),
internal: `export const schema = ${JSON.stringify(schema)};`,
};
},
Expand Down Expand Up @@ -140,11 +125,10 @@ function validatePublicVariables({

function getTemplates(
schema: EnvSchema,
fs: typeof fsMod,
validatedVariables: ReturnType<typeof validatePublicVariables>,
) {
let client = '';
let server = fs.readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
let server = readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
let onSetGetEnv = '';

for (const { key, value, context } of validatedVariables) {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/integrations/features-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function validateSupportedFeatures(
adapterName,
logger,
'astro:env getSecret',
() => config?.experimental?.env !== undefined,
() => Object.keys(config?.env?.schema ?? {}).length !== 0,
);

return validationResult;
Expand Down
Loading
Loading