import { z } from 'zod'; /** * Environment variable schema. */ const envSchema = z.object({ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), NEXT_PUBLIC_BASE_URL: z.string().url().default('http://localhost:3000'), // Analytics NEXT_PUBLIC_UMAMI_WEBSITE_ID: z.string().optional(), NEXT_PUBLIC_UMAMI_SCRIPT_URL: z.string().url().default('https://analytics.infra.mintel.me/script.js'), // Error Tracking SENTRY_DSN: z.string().optional(), // Cache REDIS_URL: z.string().optional(), REDIS_KEY_PREFIX: z.string().default('klz:'), // Logging LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'), // Mail MAIL_HOST: z.string().optional(), MAIL_PORT: z.coerce.number().default(587), MAIL_USERNAME: z.string().optional(), MAIL_PASSWORD: z.string().optional(), MAIL_FROM: z.string().optional(), MAIL_RECIPIENTS: z.string().optional().transform(val => val?.split(',').filter(Boolean) || []), }); /** * Helper to get environment variables. */ function getRawEnv() { const isServer = typeof window === 'undefined'; if (!isServer) { // Client-side: only return NEXT_PUBLIC_ variables return { NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID, NEXT_PUBLIC_UMAMI_SCRIPT_URL: process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL, }; } // Server-side: return all variables return { NODE_ENV: process.env.NODE_ENV, NEXT_PUBLIC_BASE_URL: process.env.NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_UMAMI_WEBSITE_ID: process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID, NEXT_PUBLIC_UMAMI_SCRIPT_URL: process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL, SENTRY_DSN: process.env.SENTRY_DSN, REDIS_URL: process.env.REDIS_URL, REDIS_KEY_PREFIX: process.env.REDIS_KEY_PREFIX, LOG_LEVEL: process.env.LOG_LEVEL, MAIL_HOST: process.env.MAIL_HOST, MAIL_PORT: process.env.MAIL_PORT, MAIL_USERNAME: process.env.MAIL_USERNAME, MAIL_PASSWORD: process.env.MAIL_PASSWORD, MAIL_FROM: process.env.MAIL_FROM, MAIL_RECIPIENTS: process.env.MAIL_RECIPIENTS, }; } /** * Validated environment variables. * We use safeParse during build to avoid crashing the build process. */ const isBuildTime = process.env.NEXT_PHASE === 'phase-production-build'; const parsed = isBuildTime ? envSchema.safeParse(getRawEnv()) : { success: true, data: envSchema.parse(getRawEnv()) }; export const env = parsed.success ? parsed.data : envSchema.parse({});