UNPKG

envoky

Version:

Validate required environment variables before Node.js app starts.

132 lines (130 loc) 5.06 kB
"use strict"; // envoky: Validate required environment variables before app start // Usage: // import { validateEnv } from './index.js'; // validateEnv(['DB_HOST', { key: 'PORT', defaultValue: '3000' }]); // This will load .env, check for the listed variables, and exit with an error if any are missing. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.reloadEnv = reloadEnv; exports.validateEnv = validateEnv; exports.generateExample = generateExample; const dotenv_1 = __importDefault(require("dotenv")); const chalk_1 = __importDefault(require("chalk")); const boxen_1 = __importDefault(require("boxen")); /** * Reload environment variables from .env file at runtime. * This will re-parse the .env file and update process.env. * Usage: reloadEnv(); */ function reloadEnv() { const result = dotenv_1.default.config(); if (result.parsed) { for (const [key, value] of Object.entries(result.parsed)) { process.env[key] = value; } } } async function validateEnv(vars) { if (process.env.NODE_ENV !== 'test') { dotenv_1.default.config(); } const missing = []; const invalid = []; const validatedEnv = {}; for (const v of vars) { const variable = typeof v === 'string' ? { key: v } : v; const { key, defaultValue, validate, allowEmpty, type = 'string', when } = variable; // Conditional validation check if (when && process.env[when.key] !== when.value) { continue; // Skip validation if condition not met } let value = process.env[key]; if (value === undefined) { if (defaultValue !== undefined) { value = defaultValue; } else { missing.push(key); continue; } } if (allowEmpty === false && value === '') { invalid.push({ key, message: 'Value cannot be empty' }); continue; } // Type validation and coercion let coercedValue = value; switch (type) { case 'number': coercedValue = Number(value); if (isNaN(coercedValue)) { invalid.push({ key, message: `Must be a number (received: "${value}")` }); continue; } break; case 'boolean': if (value === 'true' || value === true) { coercedValue = true; } else if (value === 'false' || value === false) { coercedValue = false; } else { invalid.push({ key, message: `Must be a boolean (true/false) (received: "${value}")` }); continue; } break; case 'email': // Simple email regex if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) { invalid.push({ key, message: `Must be a valid email address (received: "${value}")` }); continue; } break; } if (validate) { const result = await Promise.resolve(validate(coercedValue)); if (result !== true) { const message = typeof result === 'string' ? result : `Invalid value: "${coercedValue}"`; invalid.push({ key, message }); continue; } } validatedEnv[key] = coercedValue; } if (missing.length || invalid.length) { let errorMsg = ''; if (missing.length) { const msg = `Missing required environment variables:\n\n${missing.map(v => '- ' + v).join('\n')}`; const envTemplate = missing.map(v => `${v}=`).join('\n'); errorMsg += chalk_1.default.red(msg) + '\n\n' + chalk_1.default.yellow('Add the following to your .env file:') + '\n' + chalk_1.default.cyan(envTemplate); } if (invalid.length) { if (missing.length) errorMsg += '\n\n'; const msg = `Invalid environment variables:\n\n${invalid.map(v => `- ${v.key}: ${v.message}`).join('\n')}`; errorMsg += chalk_1.default.red(msg); } const boxMsg = (0, boxen_1.default)(errorMsg, { padding: 1, borderColor: 'red', borderStyle: 'round' }); throw new Error(boxMsg); } return validatedEnv; } function generateExample(vars) { let content = `# .env.example # This file contains all the environment variables needed by the application. `; for (const v of vars) { const variable = typeof v === 'string' ? { key: v } : v; const { key, defaultValue } = variable; content += `${key}=${defaultValue !== undefined ? defaultValue : ''}\n`; } return content; }