165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
/**
|
|
* 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<string, any>) {
|
|
return yup.object(shape);
|
|
}
|
|
|
|
/**
|
|
* Validates environment variables against a Yup schema
|
|
*/
|
|
export function validateEnv(schema: yup.ObjectSchema<any>, 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<string, string | undefined>,
|
|
validators: Record<string, any>
|
|
): any {
|
|
const schema = createEnvSchema(validators);
|
|
return validateEnv(schema, env);
|
|
}
|