import { z } from 'zod'; const optionalUrl = z .string() .trim() .url() .optional() .or(z.literal('').transform(() => undefined)); const optionalString = z .string() .trim() .min(1) .optional() .or(z.literal('').transform(() => undefined)); const booleanFromEnv = z.preprocess((value) => { if (value === undefined || value === null || value === '') { return undefined; } if (typeof value === 'boolean') { return value; } if (typeof value === 'string') { return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase()); } if (typeof value === 'number') { return value === 1; } return false; }, z.boolean().default(false)); const positiveIntegerFromEnv = (defaultValue: number) => z.preprocess((value) => { if (value === undefined || value === null || value === '') { return undefined; } return value; }, z.coerce.number().int().positive().default(defaultValue)); export const envSchema = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'), PORT: z.coerce.number().int().positive().default(4300), CORS_ORIGIN: optionalString, LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'), DATABASE_URL: optionalString, USE_DATABASE_READS: booleanFromEnv, PRISMA_CONNECT_ON_BOOT: booleanFromEnv, REDIS_URL: optionalString, JENKINS_BASE_URL: optionalUrl, JENKINS_USERNAME: optionalString, JENKINS_API_TOKEN: optionalString, JENKINS_AUTO_SYNC_ENABLED: booleanFromEnv, JENKINS_AUTO_SYNC_INTERVAL_MS: positiveIntegerFromEnv(30000), GITEA_BASE_URL: optionalUrl, GITEA_TOKEN: optionalString, GITEA_WEBHOOK_SECRET: optionalString, DEVOPS_PUBLIC_URL: optionalUrl, NOTIFICATION_PROVIDER: optionalString, NOTIFICATION_WEBHOOK_URL: optionalUrl, FEISHU_WEBHOOK_URL: optionalUrl, WECOM_WEBHOOK_URL: optionalUrl, LLM_BASE_URL: optionalUrl, LLM_API_KEY: optionalString, LLM_MODEL: optionalString, SECRET_ENCRYPTION_KEY: optionalString, }); export type EnvConfig = z.infer; export function validateEnv(config: Record): EnvConfig { const result = envSchema.safeParse(config); if (!result.success) { const details = result.error.issues .map((issue) => `${issue.path.join('.')}: ${issue.message}`) .join('; '); throw new Error(`Invalid environment configuration: ${details}`); } return result.data; }