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:
parent
4c4741b42d
commit
41121fbe00
13 changed files with 113 additions and 91 deletions
5
.changeset/dirty-ducks-play.md
Normal file
5
.changeset/dirty-ducks-play.md
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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 });
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
30
packages/astro/src/env/sync.ts
vendored
Normal 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');
|
||||
}
|
108
packages/astro/src/env/vite-plugin-env.ts
vendored
108
packages/astro/src/env/vite-plugin-env.ts
vendored
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
13
packages/astro/test/fixtures/astro-env-required-public/astro.config.mjs
vendored
Normal file
13
packages/astro/test/fixtures/astro-env-required-public/astro.config.mjs
vendored
Normal 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" }),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
8
packages/astro/test/fixtures/astro-env-required-public/package.json
vendored
Normal file
8
packages/astro/test/fixtures/astro-env-required-public/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/astro-env-required-public",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
3
packages/astro/test/fixtures/astro-env-required-public/tsconfig.json
vendored
Normal file
3
packages/astro/test/fixtures/astro-env-required-public/tsconfig.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/base"
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue