/** * Environment validation utilities using Yup */ import { existsSync } from 'fs'; import { join } from 'path'; import { config } from 'dotenv'; import * as yup from 'yup'; // Function to find and load environment variables function loadEnvFiles() { const cwd = process.cwd(); const possiblePaths = [ // Current working directory join(cwd, '.env'), join(cwd, '.env.local'), // Root of the workspace (common pattern) join(cwd, '../../.env'), join(cwd, '../../../.env'), // Config library directory join(__dirname, '../.env'), join(__dirname, '../../.env'), join(__dirname, '../../../.env'), ]; // Try to load each possible .env file for (const envPath of possiblePaths) { if (existsSync(envPath)) { console.log(`📄 Loading environment from: ${envPath}`); config({ path: envPath }); break; // Use the first .env file found } } // Also try to load environment-specific files const environment = process.env.NODE_ENV || 'development'; const envSpecificPaths = [ join(cwd, `.env.${environment}`), join(cwd, `.env.${environment}.local`), ]; for (const envPath of envSpecificPaths) { if (existsSync(envPath)) { console.log(`📄 Loading ${environment} environment from: ${envPath}`); config({ path: envPath, override: false }); // Don't override existing vars } } } // Load environment variables loadEnvFiles(); /** * Creates a Yup schema for environment variable validation */ export function createEnvSchema(shape: Record) { return yup.object(shape); } /** * Validates environment variables against a Yup schema */ export function validateEnv(schema: yup.ObjectSchema, env = process.env): any { try { const result = schema.validateSync(env, { abortEarly: false }); return result; } catch (error) { if (error instanceof yup.ValidationError) { console.error('❌ Invalid environment variables:'); error.inner.forEach(err => { console.error(` ${err.path}: ${err.message}`); }); } throw new Error('Environment validation failed'); } } /** * Manually load environment variables from a specific path */ export function loadEnv(path?: string) { if (path) { console.log(`📄 Manually loading environment from: ${path}`); config({ path }); } else { loadEnvFiles(); } } /** * Helper functions for common validation patterns */ export const envValidators = { // String with default str: (defaultValue?: string, description?: string) => yup.string().default(defaultValue || ''), // String with choices (enum) strWithChoices: (choices: string[], defaultValue?: string, description?: string) => yup .string() .oneOf(choices) .default(defaultValue || choices[0]), // Required string requiredStr: (description?: string) => yup.string().required('Required'), // Port number port: (defaultValue?: number, description?: string) => yup .number() .integer() .min(1) .max(65535) .transform((val, originalVal) => { if (typeof originalVal === 'string') { return parseInt(originalVal, 10); } return val; }) .default(defaultValue || 3000), // Number with default num: (defaultValue?: number, description?: string) => yup .number() .transform((val, originalVal) => { if (typeof originalVal === 'string') { return parseFloat(originalVal); } return val; }) .default(defaultValue || 0), // Boolean with default bool: (defaultValue?: boolean, description?: string) => yup .boolean() .transform((val, originalVal) => { if (typeof originalVal === 'string') { return originalVal === 'true' || originalVal === '1'; } return val; }) .default(defaultValue || false), // URL validation url: (defaultValue?: string, description?: string) => yup .string() .url() .default(defaultValue || 'http://localhost'), // Email validation email: (description?: string) => yup.string().email(), }; /** * Legacy compatibility - creates a cleanEnv-like function */ export function cleanEnv( env: Record, validators: Record ): any { const schema = createEnvSchema(validators); return validateEnv(schema, env); }