0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

fix(astro): astro sync and astro:env (#11326)

This commit is contained in:
Florian Lefebvre 2024-06-25 14:51:55 +02:00 committed by GitHub
parent 4c4741b42d
commit 41121fbe00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 113 additions and 91 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes a case where running `astro sync` when using the experimental `astro:env` feature would fail if environment variables were missing

View file

@ -48,7 +48,7 @@ export function getViteConfig(
astroContentListenPlugin({ settings, logger, fs }),
],
},
{ settings, logger, mode }
{ settings, logger, mode, sync: false }
);
await runHookConfigDone({ settings, logger });
return mergeConfig(viteConfig, userViteConfig);

View file

@ -139,7 +139,7 @@ class AstroBuilder {
middlewareMode: true,
},
},
{ settings: this.settings, logger: this.logger, mode: 'build', command: 'build' }
{ settings: this.settings, logger: this.logger, mode: 'build', command: 'build', sync: false }
);
await runHookConfigDone({ settings: this.settings, logger: logger });

View file

@ -47,6 +47,7 @@ interface CreateViteOptions {
// will be undefined when using `getViteConfig`
command?: 'dev' | 'build';
fs?: typeof nodeFs;
sync: boolean;
}
const ALWAYS_NOEXTERNAL = [
@ -74,7 +75,7 @@ const ONLY_DEV_EXTERNAL = [
/** Return a base vite config as a common starting point for all Vite commands. */
export async function createVite(
commandConfig: vite.InlineConfig,
{ settings, logger, mode, command, fs = nodeFs }: CreateViteOptions
{ settings, logger, mode, command, fs = nodeFs, sync }: CreateViteOptions
): Promise<vite.InlineConfig> {
const astroPkgsConfig = await crawlFrameworkPkgs({
root: fileURLToPath(settings.config.root),
@ -137,7 +138,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 }),
astroEnv({ settings, mode, fs }),
astroEnv({ settings, mode, fs, sync }),
markdownVitePlugin({ settings, logger }),
htmlVitePlugin(),
mdxVitePlugin(),

View file

@ -74,7 +74,7 @@ export async function createContainer({
include: rendererClientEntries,
},
},
{ settings, logger, mode: 'dev', command: 'dev', fs }
{ settings, logger, mode: 'dev', command: 'dev', fs, sync: false }
);
await runHookConfigDone({ settings, logger });
const viteServer = await vite.createServer(viteConfig);

View file

@ -27,6 +27,7 @@ import {
import type { Logger } from '../logger/core.js';
import { formatErrorMessage } from '../messages.js';
import { ensureProcessNodeEnv } from '../util.js';
import { syncAstroEnv } from '../../env/sync.js';
export type ProcessExit = 0 | 1;
@ -83,6 +84,7 @@ export default async function sync(
await dbPackage?.typegen?.(astroConfig);
const exitCode = await syncContentCollections(settings, { ...options, logger });
if (exitCode !== 0) return exitCode;
syncAstroEnv(settings, options?.fs);
logger.info(null, `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
return 0;
@ -123,7 +125,7 @@ export async function syncContentCollections(
ssr: { external: [] },
logLevel: 'silent',
},
{ settings, logger, mode: 'build', command: 'build', fs }
{ settings, logger, mode: 'build', command: 'build', fs, sync: true }
)
);

30
packages/astro/src/env/sync.ts vendored Normal file
View file

@ -0,0 +1,30 @@
import type { AstroSettings } from '../@types/astro.js';
import fsMod from 'node:fs';
import { getEnvFieldType } from './validators.js';
import { ENV_TYPES_FILE, TYPES_TEMPLATE_URL } from './constants.js';
export function syncAstroEnv(settings: AstroSettings, fs = fsMod) {
if (!settings.config.experimental.env) {
return;
}
const schema = settings.config.experimental.env.schema ?? {};
let client = '';
let server = '';
for (const [key, options] of Object.entries(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 dts = template.replace('// @@CLIENT@@', client).replace('// @@SERVER@@', server);
fs.mkdirSync(settings.dotAstroDir, { recursive: true });
fs.writeFileSync(new URL(ENV_TYPES_FILE, settings.dotAstroDir), dts, 'utf-8');
}

View file

@ -1,17 +1,15 @@
import type fsMod from 'node:fs';
import { fileURLToPath } from 'node:url';
import { type Plugin, loadEnv } from 'vite';
import { loadEnv, type Plugin } from 'vite';
import type { AstroSettings } from '../@types/astro.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import {
ENV_TYPES_FILE,
MODULE_TEMPLATE_URL,
TYPES_TEMPLATE_URL,
VIRTUAL_MODULES_IDS,
VIRTUAL_MODULES_IDS_VALUES,
} from './constants.js';
import type { EnvSchema } from './schema.js';
import { getEnvFieldType, validateEnvVariable } from './validators.js';
import { 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
@ -23,14 +21,16 @@ interface AstroEnvVirtualModPluginParams {
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) {
if (!settings.config.experimental.env || sync) {
return;
}
const schema = settings.config.experimental.env.schema ?? {};
@ -54,23 +54,10 @@ export function astroEnv({
const validatedVariables = validatePublicVariables({ schema, loadedEnv });
const clientTemplates = getClientTemplates({ validatedVariables });
const serverTemplates = getServerTemplates({ validatedVariables, schema, fs });
templates = {
client: clientTemplates.module,
server: serverTemplates.module,
...getTemplates(schema, fs, validatedVariables),
internal: `export const schema = ${JSON.stringify(schema)};`,
};
generateDts({
settings,
fs,
content: getDts({
fs,
client: clientTemplates.types,
server: serverTemplates.types,
}),
});
},
buildEnd() {
templates = null;
@ -104,19 +91,6 @@ function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
return `\0${id}`;
}
function generateDts({
content,
settings,
fs,
}: {
content: string;
settings: AstroSettings;
fs: typeof fsMod;
}) {
fs.mkdirSync(settings.dotAstroDir, { recursive: true });
fs.writeFileSync(new URL(ENV_TYPES_FILE, settings.dotAstroDir), content, 'utf-8');
}
function validatePublicVariables({
schema,
loadedEnv,
@ -152,55 +126,22 @@ function validatePublicVariables({
return valid;
}
function getDts({
client,
server,
fs,
}: {
client: string;
server: string;
fs: typeof fsMod;
}) {
const template = fs.readFileSync(TYPES_TEMPLATE_URL, 'utf-8');
return template.replace('// @@CLIENT@@', client).replace('// @@SERVER@@', server);
}
function getClientTemplates({
validatedVariables,
}: {
validatedVariables: ReturnType<typeof validatePublicVariables>;
}) {
let module = '';
let types = '';
for (const { key, type, value } of validatedVariables.filter((e) => e.context === 'client')) {
module += `export const ${key} = ${JSON.stringify(value)};`;
types += `export const ${key}: ${type}; \n`;
}
return {
module,
types,
};
}
function getServerTemplates({
validatedVariables,
schema,
fs,
}: {
validatedVariables: ReturnType<typeof validatePublicVariables>;
schema: EnvSchema;
fs: typeof fsMod;
}) {
let module = fs.readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
let types = '';
function getTemplates(
schema: EnvSchema,
fs: typeof fsMod,
validatedVariables: ReturnType<typeof validatePublicVariables>
) {
let client = '';
let server = fs.readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
let onSetGetEnv = '';
for (const { key, type, value } of validatedVariables.filter((e) => e.context === 'server')) {
module += `export const ${key} = ${JSON.stringify(value)};`;
types += `export const ${key}: ${type}; \n`;
for (const { key, value, context } of validatedVariables) {
const str = `export const ${key} = ${JSON.stringify(value)};`;
if (context === 'client') {
client += str;
} else {
server += str;
}
}
for (const [key, options] of Object.entries(schema)) {
@ -208,15 +149,14 @@ function getServerTemplates({
continue;
}
types += `export const ${key}: ${getEnvFieldType(options)}; \n`;
module += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
server += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
onSetGetEnv += `${key} = reset ? undefined : _internalGetSecret(${JSON.stringify(key)});\n`;
}
module = module.replace('// @@ON_SET_GET_ENV@@', onSetGetEnv);
server = server.replace('// @@ON_SET_GET_ENV@@', onSetGetEnv);
return {
module,
types,
client,
server,
};
}

View file

@ -47,7 +47,10 @@ const createFixture = () => {
},
};
await astroFixture.sync({}, { fs: fsMock });
const code = await astroFixture.sync({}, { fs: fsMock });
if (code !== 0) {
throw new Error(`Process error code ${code}`);
}
},
/** @param {string} path */
thenFileShouldExist(path) {
@ -164,6 +167,17 @@ describe('astro sync', () => {
`/// <reference path="../.astro/env.d.ts" />`
);
});
it('Does not throw if a public variable is required', async () => {
let error = null;
try {
await fixture.whenSyncing('./fixtures/astro-env-required-public/');
} catch (e) {
error = e;
}
assert.equal(error, null, 'Syncing should not throw astro:env validation errors');
});
});
describe('Astro Actions', () => {

View file

@ -0,0 +1,13 @@
import { defineConfig, envField } from 'astro/config';
// https://astro.build/config
export default defineConfig({
experimental: {
env: {
schema: {
FOO: envField.string({ context: "client", access: "public" }),
BAR: envField.number({ context: "server", access: "public" }),
}
}
}
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/astro-env-required-public",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/base"
}

View file

@ -2121,6 +2121,12 @@ importers:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/astro-env-required-public:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/astro-env-server-fail:
dependencies:
astro: