UNPKG

@d3vtool/strict-env

Version:

A utility to automatically load and validate environment variables from `.env` files, ensuring they're properly configured before your system starts.

159 lines (158 loc) 6.63 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Validator = void 0; exports.setup = setup; const node_fs_1 = __importDefault(require("node:fs")); const error_1 = require("./error"); const evalidator_1 = require("./evalidator"); const validator_1 = require("@d3vtool/validator"); Object.defineProperty(exports, "Validator", { enumerable: true, get: function () { return validator_1.Validator; } }); const REF_REGEX = /\$\{(.+?)\}/g; const EXTRACT_LINE_NO_REGEX = /'(.+?)'/; /** * Loads the environment variables to process.env. * * @param {string} filePath - The path to your env file. * @param {string} encoding - The encoding to use when reading the file (default is 'utf8'). * @param {ObjectValidator | Object} validators - An object or object-validator of validator functions to check each key-value pair. */ function loadEnv(filePath, encoding, validators) { const fileData = node_fs_1.default.readFileSync(filePath, { encoding: encoding ?? "utf8" }); const envMap = parseEnv(fileData); if (validators) { (0, evalidator_1.eValidator)(validators, envMap); } for (const key in envMap) { if (envMap[key] === undefined) continue; process.env[key] = envMap[key]; } return envMap; } function parseEnv(fileData) { const envMap = {}; const laterRefStack = []; const lines = fileData.split("\n"); for (let line = 0; line < lines.length; ++line) { if (lines[line].startsWith("#") || lines[line].trim().length === 0) continue; if (!lines[line].includes("=")) { throw new error_1.EnvParseError("Line expected to have key-value pair seperated by '='"); } let envKey = ''; let envValue = ''; const lineStr = lines[line]; let idx = 0; while (idx < lineStr.length && lineStr[idx] !== '=') { envKey += lineStr[idx]; idx++; } if (idx === lineStr.length - 1) { throw new error_1.EnvParseError("Invalid key-value pair."); } idx++; // skip '=' envValue = lineStr.slice(idx).trim(); if (envValue.includes("#")) { let value = ''; const hasQuote = envValue[0] === "'" || envValue[0] === '"'; let idx = 0; if (hasQuote) { while (idx < envValue.length) { if (idx > 0 && envValue[idx] === envValue[0]) { break; } if (idx === envValue.length - 1) { throw new error_1.EnvParseError(`Quotes were never closed on line ${line + 1}.`); } value += envValue[idx++]; } } else { while (idx < envValue.length) { if (envValue[idx] === '#') { break; } value += envValue[idx++]; } } envValue = value; } envKey = envKey?.trim(); envValue = envValue?.trim(); if (envValue?.includes("'")) { envValue = envValue?.replaceAll("'", ""); } else if (envValue?.includes('"')) { envValue = envValue?.replaceAll('"', ""); } if (envValue.includes("${")) { const matchedRefKeys = [...envValue.matchAll(REF_REGEX)]; for (let idx = 0; idx < matchedRefKeys.length; ++idx) { const actualValueToReplace = matchedRefKeys[idx][0]; const refKey = matchedRefKeys[idx][1]; const referedValue = envMap[refKey]; if (referedValue === undefined) { laterRefStack.push({ envKey, refKey, actualValueToReplace, errorMsg: `At Line '${line + 1}' invalid reference key found "${refKey}".` }); continue; } envValue = envValue.replaceAll(actualValueToReplace, referedValue); } } if (envKey.length === 0) throw new error_1.EnvParseError(`Environment variable key is missing at line: ${line + 1}`); if (envValue?.length === 0) throw new error_1.EnvParseError(`Environment variable '${envKey}' value is missing at line: ${line + 1}`); envMap[envKey] = envValue; } while (laterRefStack.length !== 0) { const refContext = laterRefStack.pop(); if (!refContext) { continue; } const envValue = envMap[refContext.refKey]; if (!envValue) { throw new error_1.EnvParseError(refContext.errorMsg); } if (envValue === refContext.actualValueToReplace) { const lineNo = refContext?.errorMsg?.match(EXTRACT_LINE_NO_REGEX)?.[1]; throw new error_1.EnvParseError(`Circular reference is not allowed on line '${lineNo}'.`); } envMap[refContext.envKey] = envMap[refContext.envKey]?.replaceAll(refContext.actualValueToReplace, envValue); } return envMap; } /** * Sets up environment configuration by loading environment files. * If a specific file is provided in the options, it will load that file. * Otherwise, it will attempt to load from a set of predefined accepted environment files. * * @param {Object} options - Configuration options for setting up the environment. * @param {string} [options.file] - The specific environment file to load. If not provided, the function will attempt to load from a list of accepted files. * @param {BufferEncoding} [options.encoding="utf8"] - The encoding used to read the file. Default is "utf8". * @param {Object | ObjectValidator } [options.validators] - A list of validator object key-value to apply to the environment variables once loaded. Optional. * * @returns {void} This function does not return anything. */ function setup(options) { if (options && options?.file) { return loadEnv(options?.file, options.encoding, options?.validators); } else { const defaultDotEnv = ".env"; if (!node_fs_1.default.existsSync(defaultDotEnv)) { throw new Error("Error: .env file not found — unable to load environment variables."); } return loadEnv(defaultDotEnv, "utf8", options?.validators); } }