diff --git a/.changeset/chilly-jokes-fold.md b/.changeset/chilly-jokes-fold.md new file mode 100644 index 0000000000..3410cdeb95 --- /dev/null +++ b/.changeset/chilly-jokes-fold.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a case where invalid `astro:env` variables at runtime would not throw correctly diff --git a/packages/astro/dev-only.d.ts b/packages/astro/dev-only.d.ts new file mode 100644 index 0000000000..5a5420a95c --- /dev/null +++ b/packages/astro/dev-only.d.ts @@ -0,0 +1,5 @@ +// IMPORTANT: do not publish this file! It's only intended for development within the monorepo + +declare module 'virtual:astro:env/internal' { + export const schema: import('./src/env/schema.js').EnvSchema; +} diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index 886fd296e4..8b586e6d2c 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -1235,6 +1235,8 @@ export const RouteNotFound = { * @docs * @description * Some environment variables do not match the data type and/or properties defined in `experimental.env.schema`. + * @message + * The following environment variables defined in `experimental.env.schema` are invalid. */ export const EnvInvalidVariables = { name: 'EnvInvalidVariables', @@ -1243,18 +1245,6 @@ export const EnvInvalidVariables = { `The following environment variables defined in \`experimental.env.schema\` are invalid:\n\n${errors.map((err) => `- ${err}`).join('\n')}\n`, } satisfies ErrorData; -/** - * @docs - * @description - * An environment variable does not match the data type and/or properties defined in `experimental.env.schema`. - */ -export const EnvInvalidVariable = { - name: 'EnvInvalidVariable', - title: 'Invalid Environment Variable', - message: (key: string, type: string) => - `The following environment variable does not match the data type and/or properties defined in \`experimental.env.schema\`: ${key} is not of type ${type}`, -} satisfies ErrorData; - /** * @docs * @description diff --git a/packages/astro/src/env/errors.ts b/packages/astro/src/env/errors.ts new file mode 100644 index 0000000000..6fcbd5b3dd --- /dev/null +++ b/packages/astro/src/env/errors.ts @@ -0,0 +1,22 @@ +import type { ValidationResultErrors } from './validators.js'; + +export interface InvalidVariable { + key: string; + type: string; + errors: ValidationResultErrors; +} + +export function invalidVariablesToError(invalid: Array) { + const _errors: Array = []; + for (const { key, type, errors } of invalid) { + if (errors[0] === 'missing') { + _errors.push(`${key} is missing`); + } else if (errors[0] === 'type') { + _errors.push(`${key}'s type is invalid, expected: ${type}`); + } else { + // constraints + _errors.push(`The following constraints for ${key} are not met: ${errors.join(', ')}`); + } + } + return _errors; +} diff --git a/packages/astro/src/env/runtime.ts b/packages/astro/src/env/runtime.ts index 317e9110fe..57729cdb8b 100644 --- a/packages/astro/src/env/runtime.ts +++ b/packages/astro/src/env/runtime.ts @@ -1,5 +1,7 @@ import { AstroError, AstroErrorData } from '../core/errors/index.js'; -export { validateEnvVariable } from './validators.js'; +import { invalidVariablesToError } from './errors.js'; +import type { ValidationResultInvalid } from './validators.js'; +export { validateEnvVariable, getEnvFieldType } from './validators.js'; export type GetEnv = (key: string) => string | undefined; @@ -21,11 +23,15 @@ export function getEnv(...args: Parameters) { return _getEnv(...args); } -export function createInvalidVariableError( - ...args: Parameters +export function createInvalidVariablesError( + key: string, + type: string, + result: ValidationResultInvalid ) { return new AstroError({ - ...AstroErrorData.EnvInvalidVariable, - message: AstroErrorData.EnvInvalidVariable.message(...args), + ...AstroErrorData.EnvInvalidVariables, + message: AstroErrorData.EnvInvalidVariables.message( + invalidVariablesToError([{ key, type, errors: result.errors }]) + ), }); } diff --git a/packages/astro/src/env/validators.ts b/packages/astro/src/env/validators.ts index 4e5d342875..8776793cb0 100644 --- a/packages/astro/src/env/validators.ts +++ b/packages/astro/src/env/validators.ts @@ -2,16 +2,15 @@ import type { EnumSchema, EnvFieldType, NumberSchema, StringSchema } from './sch export type ValidationResultValue = EnvFieldType['default']; export type ValidationResultErrors = ['missing'] | ['type'] | Array; - -type ValidationResult = - | { - ok: true; - value: ValidationResultValue; - } - | { - ok: false; - errors: ValidationResultErrors; - }; +interface ValidationResultValid { + ok: true; + value: ValidationResultValue; +} +export interface ValidationResultInvalid { + ok: false; + errors: ValidationResultErrors; +} +type ValidationResult = ValidationResultValid | ValidationResultInvalid; export function getEnvFieldType(options: EnvFieldType) { const optional = options.optional ? (options.default !== undefined ? false : true) : false; diff --git a/packages/astro/src/env/vite-plugin-env.ts b/packages/astro/src/env/vite-plugin-env.ts index 3f1ca2b6bd..a922a92123 100644 --- a/packages/astro/src/env/vite-plugin-env.ts +++ b/packages/astro/src/env/vite-plugin-env.ts @@ -9,7 +9,8 @@ import { VIRTUAL_MODULES_IDS_VALUES, } from './constants.js'; import type { EnvSchema } from './schema.js'; -import { type ValidationResultErrors, getEnvFieldType, validateEnvVariable } from './validators.js'; +import { getEnvFieldType, validateEnvVariable } from './validators.js'; +import { invalidVariablesToError, type InvalidVariable } from './errors.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 @@ -105,7 +106,7 @@ function validatePublicVariables({ validateSecrets: boolean; }) { const valid: Array<{ key: string; value: any; type: string; context: 'server' | 'client' }> = []; - const invalid: Array<{ key: string; type: string; errors: ValidationResultErrors }> = []; + const invalid: Array = []; for (const [key, options] of Object.entries(schema)) { const variable = loadedEnv[key] === '' ? undefined : loadedEnv[key]; @@ -125,20 +126,9 @@ function validatePublicVariables({ } if (invalid.length > 0) { - const _errors: Array = []; - for (const { key, type, errors } of invalid) { - if (errors[0] === 'missing') { - _errors.push(`${key} is missing`); - } else if (errors[0] === 'type') { - _errors.push(`${key}'s type is invalid, expected: ${type}`); - } else { - // constraints - _errors.push(`The following constraints for ${key} are not met: ${errors.join(', ')}`); - } - } throw new AstroError({ ...AstroErrorData.EnvInvalidVariables, - message: AstroErrorData.EnvInvalidVariables.message(_errors), + message: AstroErrorData.EnvInvalidVariables.message(invalidVariablesToError(invalid)), }); } diff --git a/packages/astro/templates/env/module.mjs b/packages/astro/templates/env/module.mjs index 952dadbba1..08c0a0a7a5 100644 --- a/packages/astro/templates/env/module.mjs +++ b/packages/astro/templates/env/module.mjs @@ -1,9 +1,11 @@ +// @ts-check import { schema } from 'virtual:astro:env/internal'; import { - createInvalidVariableError, + createInvalidVariablesError, getEnv, setOnSetGetEnv, validateEnvVariable, + getEnvFieldType, } from 'astro/env/runtime'; export const getSecret = (key) => { @@ -19,7 +21,8 @@ const _internalGetSecret = (key) => { if (result.ok) { return result.value; } - throw createInvalidVariableError(key, result.type); + const type = getEnvFieldType(options); + throw createInvalidVariablesError(key, type, result); }; setOnSetGetEnv((reset) => {