mirror of
https://github.com/withastro/astro.git
synced 2025-01-13 22:11:20 -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 }),
|
astroContentListenPlugin({ settings, logger, fs }),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ settings, logger, mode }
|
{ settings, logger, mode, sync: false }
|
||||||
);
|
);
|
||||||
await runHookConfigDone({ settings, logger });
|
await runHookConfigDone({ settings, logger });
|
||||||
return mergeConfig(viteConfig, userViteConfig);
|
return mergeConfig(viteConfig, userViteConfig);
|
||||||
|
|
|
@ -139,7 +139,7 @@ class AstroBuilder {
|
||||||
middlewareMode: true,
|
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 });
|
await runHookConfigDone({ settings: this.settings, logger: logger });
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ interface CreateViteOptions {
|
||||||
// will be undefined when using `getViteConfig`
|
// will be undefined when using `getViteConfig`
|
||||||
command?: 'dev' | 'build';
|
command?: 'dev' | 'build';
|
||||||
fs?: typeof nodeFs;
|
fs?: typeof nodeFs;
|
||||||
|
sync: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALWAYS_NOEXTERNAL = [
|
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. */
|
/** Return a base vite config as a common starting point for all Vite commands. */
|
||||||
export async function createVite(
|
export async function createVite(
|
||||||
commandConfig: vite.InlineConfig,
|
commandConfig: vite.InlineConfig,
|
||||||
{ settings, logger, mode, command, fs = nodeFs }: CreateViteOptions
|
{ settings, logger, mode, command, fs = nodeFs, sync }: CreateViteOptions
|
||||||
): Promise<vite.InlineConfig> {
|
): Promise<vite.InlineConfig> {
|
||||||
const astroPkgsConfig = await crawlFrameworkPkgs({
|
const astroPkgsConfig = await crawlFrameworkPkgs({
|
||||||
root: fileURLToPath(settings.config.root),
|
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.
|
// the build to run very slow as the filewatcher is triggered often.
|
||||||
mode !== 'build' && vitePluginAstroServer({ settings, logger, fs }),
|
mode !== 'build' && vitePluginAstroServer({ settings, logger, fs }),
|
||||||
envVitePlugin({ settings }),
|
envVitePlugin({ settings }),
|
||||||
astroEnv({ settings, mode, fs }),
|
astroEnv({ settings, mode, fs, sync }),
|
||||||
markdownVitePlugin({ settings, logger }),
|
markdownVitePlugin({ settings, logger }),
|
||||||
htmlVitePlugin(),
|
htmlVitePlugin(),
|
||||||
mdxVitePlugin(),
|
mdxVitePlugin(),
|
||||||
|
|
|
@ -74,7 +74,7 @@ export async function createContainer({
|
||||||
include: rendererClientEntries,
|
include: rendererClientEntries,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ settings, logger, mode: 'dev', command: 'dev', fs }
|
{ settings, logger, mode: 'dev', command: 'dev', fs, sync: false }
|
||||||
);
|
);
|
||||||
await runHookConfigDone({ settings, logger });
|
await runHookConfigDone({ settings, logger });
|
||||||
const viteServer = await vite.createServer(viteConfig);
|
const viteServer = await vite.createServer(viteConfig);
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {
|
||||||
import type { Logger } from '../logger/core.js';
|
import type { Logger } from '../logger/core.js';
|
||||||
import { formatErrorMessage } from '../messages.js';
|
import { formatErrorMessage } from '../messages.js';
|
||||||
import { ensureProcessNodeEnv } from '../util.js';
|
import { ensureProcessNodeEnv } from '../util.js';
|
||||||
|
import { syncAstroEnv } from '../../env/sync.js';
|
||||||
|
|
||||||
export type ProcessExit = 0 | 1;
|
export type ProcessExit = 0 | 1;
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ export default async function sync(
|
||||||
await dbPackage?.typegen?.(astroConfig);
|
await dbPackage?.typegen?.(astroConfig);
|
||||||
const exitCode = await syncContentCollections(settings, { ...options, logger });
|
const exitCode = await syncContentCollections(settings, { ...options, logger });
|
||||||
if (exitCode !== 0) return exitCode;
|
if (exitCode !== 0) return exitCode;
|
||||||
|
syncAstroEnv(settings, options?.fs);
|
||||||
|
|
||||||
logger.info(null, `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
|
logger.info(null, `Types generated ${dim(getTimeStat(timerStart, performance.now()))}`);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -123,7 +125,7 @@ export async function syncContentCollections(
|
||||||
ssr: { external: [] },
|
ssr: { external: [] },
|
||||||
logLevel: 'silent',
|
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 type fsMod from 'node:fs';
|
||||||
import { fileURLToPath } from 'node:url';
|
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 type { AstroSettings } from '../@types/astro.js';
|
||||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||||
import {
|
import {
|
||||||
ENV_TYPES_FILE,
|
|
||||||
MODULE_TEMPLATE_URL,
|
MODULE_TEMPLATE_URL,
|
||||||
TYPES_TEMPLATE_URL,
|
|
||||||
VIRTUAL_MODULES_IDS,
|
VIRTUAL_MODULES_IDS,
|
||||||
VIRTUAL_MODULES_IDS_VALUES,
|
VIRTUAL_MODULES_IDS_VALUES,
|
||||||
} from './constants.js';
|
} from './constants.js';
|
||||||
import type { EnvSchema } from './schema.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
|
// 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
|
// 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;
|
settings: AstroSettings;
|
||||||
mode: 'dev' | 'build' | string;
|
mode: 'dev' | 'build' | string;
|
||||||
fs: typeof fsMod;
|
fs: typeof fsMod;
|
||||||
|
sync: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function astroEnv({
|
export function astroEnv({
|
||||||
settings,
|
settings,
|
||||||
mode,
|
mode,
|
||||||
fs,
|
fs,
|
||||||
|
sync,
|
||||||
}: AstroEnvVirtualModPluginParams): Plugin | undefined {
|
}: AstroEnvVirtualModPluginParams): Plugin | undefined {
|
||||||
if (!settings.config.experimental.env) {
|
if (!settings.config.experimental.env || sync) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const schema = settings.config.experimental.env.schema ?? {};
|
const schema = settings.config.experimental.env.schema ?? {};
|
||||||
|
@ -54,23 +54,10 @@ export function astroEnv({
|
||||||
|
|
||||||
const validatedVariables = validatePublicVariables({ schema, loadedEnv });
|
const validatedVariables = validatePublicVariables({ schema, loadedEnv });
|
||||||
|
|
||||||
const clientTemplates = getClientTemplates({ validatedVariables });
|
|
||||||
const serverTemplates = getServerTemplates({ validatedVariables, schema, fs });
|
|
||||||
|
|
||||||
templates = {
|
templates = {
|
||||||
client: clientTemplates.module,
|
...getTemplates(schema, fs, validatedVariables),
|
||||||
server: serverTemplates.module,
|
|
||||||
internal: `export const schema = ${JSON.stringify(schema)};`,
|
internal: `export const schema = ${JSON.stringify(schema)};`,
|
||||||
};
|
};
|
||||||
generateDts({
|
|
||||||
settings,
|
|
||||||
fs,
|
|
||||||
content: getDts({
|
|
||||||
fs,
|
|
||||||
client: clientTemplates.types,
|
|
||||||
server: serverTemplates.types,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
buildEnd() {
|
buildEnd() {
|
||||||
templates = null;
|
templates = null;
|
||||||
|
@ -104,19 +91,6 @@ function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
|
||||||
return `\0${id}`;
|
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({
|
function validatePublicVariables({
|
||||||
schema,
|
schema,
|
||||||
loadedEnv,
|
loadedEnv,
|
||||||
|
@ -152,55 +126,22 @@ function validatePublicVariables({
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDts({
|
function getTemplates(
|
||||||
client,
|
schema: EnvSchema,
|
||||||
server,
|
fs: typeof fsMod,
|
||||||
fs,
|
validatedVariables: ReturnType<typeof validatePublicVariables>
|
||||||
}: {
|
) {
|
||||||
client: string;
|
let client = '';
|
||||||
server: string;
|
let server = fs.readFileSync(MODULE_TEMPLATE_URL, 'utf-8');
|
||||||
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 = '';
|
|
||||||
let onSetGetEnv = '';
|
let onSetGetEnv = '';
|
||||||
|
|
||||||
for (const { key, type, value } of validatedVariables.filter((e) => e.context === 'server')) {
|
for (const { key, value, context } of validatedVariables) {
|
||||||
module += `export const ${key} = ${JSON.stringify(value)};`;
|
const str = `export const ${key} = ${JSON.stringify(value)};`;
|
||||||
types += `export const ${key}: ${type}; \n`;
|
if (context === 'client') {
|
||||||
|
client += str;
|
||||||
|
} else {
|
||||||
|
server += str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, options] of Object.entries(schema)) {
|
for (const [key, options] of Object.entries(schema)) {
|
||||||
|
@ -208,15 +149,14 @@ function getServerTemplates({
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
types += `export const ${key}: ${getEnvFieldType(options)}; \n`;
|
server += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
||||||
module += `export let ${key} = _internalGetSecret(${JSON.stringify(key)});\n`;
|
|
||||||
onSetGetEnv += `${key} = reset ? undefined : _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 {
|
return {
|
||||||
module,
|
client,
|
||||||
types,
|
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 */
|
/** @param {string} path */
|
||||||
thenFileShouldExist(path) {
|
thenFileShouldExist(path) {
|
||||||
|
@ -164,6 +167,17 @@ describe('astro sync', () => {
|
||||||
`/// <reference path="../.astro/env.d.ts" />`
|
`/// <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', () => {
|
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"
|
||||||
|
}
|
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
|
@ -2121,6 +2121,12 @@ importers:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../..
|
version: link:../../..
|
||||||
|
|
||||||
|
packages/astro/test/fixtures/astro-env-required-public:
|
||||||
|
dependencies:
|
||||||
|
astro:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../..
|
||||||
|
|
||||||
packages/astro/test/fixtures/astro-env-server-fail:
|
packages/astro/test/fixtures/astro-env-server-fail:
|
||||||
dependencies:
|
dependencies:
|
||||||
astro:
|
astro:
|
||||||
|
|
Loading…
Add table
Reference in a new issue