UNPKG

envox

Version:

Fast and flexible environment variable parser with detailed error reporting.

257 lines (248 loc) 7.83 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { fromObject: () => fromObject, isEnvFile: () => isEnvFile, parseEnv: () => parseEnv }); module.exports = __toCommonJS(index_exports); // src/errors.ts var EnvoxError = class extends Error { constructor(line, content, message) { super(message); this.line = line; this.content = content; this.name = "EnvoxError"; } }; // src/constants.ts var REGEX = { VAR_EXPANSION: /\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*?)(?=[^A-Za-z0-9]|$)/g, VALID_KEY: /^[A-Za-z_][A-Za-z0-9_]*$/, NEED_QUOTES: /[\s"'$\\]/, ENV_VAR_LIKE: /^[A-Za-z_][A-Za-z0-9_]*$/ }; // src/expansions.ts function expandValue(value, lookup) { return value.replace(REGEX.VAR_EXPANSION, (_match, braced, simple) => { const varName = braced || simple; return lookup(varName) || ""; }); } // src/helpers.ts function isEnvFile(content) { const lines = content.split("\n").filter((line) => line.trim()); if (lines.length === 0) return false; let validLines = 0; let totalLines = 0; for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith("#")) continue; totalLines++; if (isLikeEnvVar(trimmed)) { validLines++; } } return totalLines > 0 && validLines / totalLines >= 0.5; } function isLikeEnvVar(line) { const cleaned = line.replace(/^\s*export\s+/, ""); const equalsIndex = cleaned.indexOf("="); if (equalsIndex === -1) return false; const key = cleaned.substring(0, equalsIndex).trim(); return REGEX.ENV_VAR_LIKE.test(key); } function toObject(variables) { const obj = {}; for (const variable of variables) { obj[variable.key] = variable.value; } return obj; } function fromObject(obj, { includeExport = false } = {}) { const prefix = includeExport ? "export " : ""; return Object.entries(obj).map(([key, value]) => { const quotedValue = REGEX.NEED_QUOTES.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value; return `${prefix}${key}=${quotedValue}`; }).join("\n"); } // src/schema.ts function runSchema(schema, data) { try { const validation = schema["~standard"].validate(data); if (validation instanceof Promise) { throw new TypeError("Schema validation must be synchronous"); } if ("issues" in validation) { const errors = validation.issues?.map((issue) => ({ line: 0, message: issue.message, content: issue.path ? `Path: ${formatPath(issue.path)}` : "Schema validation failed" })) || []; return { success: false, errors }; } return { success: true, data: validation.value }; } catch (error) { return { success: false, errors: [ { line: 0, message: error instanceof Error ? error.message : "Schema validation failed", content: "Schema validation error" } ] }; } } function formatPath(path) { return path.map((segment) => { if (typeof segment === "object" && "key" in segment) { return String(segment.key); } return String(segment); }).join("."); } // src/utils.ts function parseLine(line, lineNumber, envMap, options) { const trimmedLine = line.trim(); if (!trimmedLine) return null; if (options.allowComments && trimmedLine.startsWith("#")) return null; let processedLine = trimmedLine; if (options.allowExport && trimmedLine.startsWith("export ")) { processedLine = trimmedLine.substring(7); } const equalsIndex = processedLine.indexOf("="); if (equalsIndex === -1) { if (!options.allowEmpty) { throw new EnvoxError(lineNumber, line, "Missing equals sign"); } return null; } const key = processedLine.substring(0, equalsIndex).trim(); let value = processedLine.substring(equalsIndex + 1); if (!isValidKey(key)) { throw new EnvoxError( lineNumber, line, `Invalid environment variable key: ${key}` ); } value = processValue(value, envMap, options); return { key, value, line: lineNumber }; } function processValue(value, envMap, options) { let processedValue = value; processedValue = handleQuotes(processedValue); if (options.trimValues) { processedValue = processedValue.trim(); } if (options.expandVariables) { processedValue = expandValue(processedValue, (k) => envMap.get(k)); } return processedValue; } function handleQuotes(value) { const trimmed = value.trim(); if (trimmed.startsWith('"') && trimmed.endsWith('"')) { return trimmed.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\"); } if (trimmed.startsWith("'") && trimmed.endsWith("'")) { return trimmed.slice(1, -1); } return value; } function isValidKey(key) { return REGEX.VALID_KEY.test(key) && !key.includes(" "); } // src/index.ts var defaultOptions = { allowEmpty: true, allowComments: true, allowExport: true, trimValues: true, expandVariables: false }; function parseEnv(source, options = {}) { const mergedOptions = { ...defaultOptions, ...options }; const variables = []; const errors = []; const envMap = /* @__PURE__ */ new Map(); if (typeof source === "string") { const lines = source.split("\n"); for (const [index, line] of lines.entries()) { const lineNumber = index + 1; if (line === void 0) continue; try { const result = parseLine(line, lineNumber, envMap, mergedOptions); if (result) { variables.push(result); envMap.set(result.key, result.value); } } catch (error) { if (error instanceof EnvoxError) { errors.push({ line: error.line, message: error.message, content: error.content }); } else { errors.push({ line: lineNumber, message: error instanceof Error ? error.message : "Unknown error", content: line }); } } } } else if (typeof source === "object" && source !== null) { const tempMap = /* @__PURE__ */ new Map(); for (const [key, value] of Object.entries(source)) { if (isValidKey(key) && value !== void 0) { tempMap.set(key, value); } } for (const [key, value] of tempMap.entries()) { const expandedValue = mergedOptions.expandVariables ? expandValue(value, (k) => tempMap.get(k)) : value; envMap.set(key, expandedValue); variables.push({ key, value: expandedValue, line: 0 }); } } if (errors.length > 0) { return { ok: false, errors }; } const obj = toObject(variables); if (mergedOptions.schema) { const validationResult = runSchema(mergedOptions.schema, obj); if (validationResult.success) { return { ok: true, data: validationResult.data, vars: variables }; } else { return { ok: false, errors: validationResult.errors }; } } return { ok: true, data: obj, vars: variables }; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { fromObject, isEnvFile, parseEnv });