envoky
Version:
Validate required environment variables before Node.js app starts.
132 lines (130 loc) • 5.06 kB
JavaScript
;
// 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;
}