UNPKG

@adonisjs/env

Version:

Environment variable manager for Node.js

235 lines (234 loc) 7.42 kB
import { n as debug_default, t as EnvLoader } from "./loader-KsjAiZ3e.js"; import "node:module"; import { parseEnv } from "node:util"; import { readFile } from "node:fs/promises"; import { Exception, RuntimeException, createError } from "@poppinss/utils/exception"; import { Secret } from "@poppinss/utils"; import { schema } from "@poppinss/validator-lite"; var __defProp = Object.defineProperty; var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" }); return target; }; var errors_exports = /* @__PURE__ */ __exportAll({ E_IDENTIFIER_ALREADY_DEFINED: () => E_IDENTIFIER_ALREADY_DEFINED, E_INVALID_ENV_VARIABLES: () => E_INVALID_ENV_VARIABLES }); const E_INVALID_ENV_VARIABLES = class EnvValidationException extends Exception { static message = "Validation failed for one or more environment variables"; static code = "E_INVALID_ENV_VARIABLES"; help = ""; }; const E_IDENTIFIER_ALREADY_DEFINED = createError("The identifier \"%s\" is already defined", "E_IDENTIFIER_ALREADY_DEFINED", 500); var EnvParser = class EnvParser { #envContents; #appRoot; #preferProcessEnv = true; static #identifiers = { async file(value, key, appRoot) { const filePath = new URL(value, appRoot); try { return await readFile(filePath, "utf-8"); } catch (error) { if (error.code === "ENOENT") throw new RuntimeException(`Cannot process "${key}" env variable. Unable to locate file "${filePath}"`, { cause: error }); throw error; } } }; constructor(envContents, appRoot, options) { if (options?.ignoreProcessEnv) this.#preferProcessEnv = false; this.#envContents = envContents; this.#appRoot = appRoot; } static identifier(name, callback) { EnvParser.defineIdentifier(name, callback); } static defineIdentifier(name, callback) { if (this.#identifiers[name]) throw new E_IDENTIFIER_ALREADY_DEFINED([name]); this.#identifiers[name] = callback; } static defineIdentifierIfMissing(name, callback) { if (typeof this.#identifiers[name] === "undefined") this.#identifiers[name] = callback; } static removeIdentifier(name) { delete this.#identifiers[name]; } #getValue(key, parsed) { if (this.#preferProcessEnv && process.env[key]) return process.env[key]; if (parsed[key]) return this.#interpolate(parsed[key], parsed); return process.env[key] || ""; } #interpolateMustache(token, parsed) { const closingBrace = token.indexOf("}"); if (closingBrace === -1) return token; const varReference = token.slice(1, closingBrace).trim(); return `${this.#getValue(varReference, parsed)}${token.slice(closingBrace + 1)}`; } #interpolateVariable(token, parsed) { return token.replace(/[a-zA-Z0-9_]+/, (key) => { return this.#getValue(key, parsed); }); } #interpolate(value, parsed) { const tokens = value.split("$"); let newValue = ""; let skipNextToken = true; tokens.forEach((token) => { if (token === "\\") { newValue += "$"; skipNextToken = true; return; } if (skipNextToken) { newValue += token.replace(/\\$/, "$"); if (token.endsWith("\\")) return; } else { if (token.startsWith("{")) { newValue += this.#interpolateMustache(token, parsed); return; } newValue += this.#interpolateVariable(token, parsed); } skipNextToken = false; }); return newValue; } async parse() { const envCollection = parseEnv(this.#envContents.trim()); const identifiers = Object.keys(EnvParser.#identifiers); let result = {}; $keyLoop: for (const key in envCollection) { const value = this.#getValue(key, envCollection); if (value.includes(":")) { for (const identifier of identifiers) { if (value.startsWith(`${identifier}:`)) { result[key] = await EnvParser.#identifiers[identifier](value.substring(identifier.length + 1), key, this.#appRoot); continue $keyLoop; } if (value.startsWith(`${identifier}\\:`)) { result[key] = identifier + value.substring(identifier.length + 1); continue $keyLoop; } } result[key] = value; } else result[key] = value; } return result; } }; var EnvValidator = class { #schema; #error; constructor(schema) { this.#schema = schema; this.#error = new E_INVALID_ENV_VARIABLES(); } validate(values) { const help = []; const validated = Object.keys(this.#schema).reduce((result, key) => { const value = process.env[key] || values[key]; try { result[key] = this.#schema[key](key, value); } catch (error) { help.push(`- ${error.message}`); } return result; }, { ...values }); if (help.length) { this.#error.help = help.join("\n"); throw this.#error; } return validated; } }; var EnvProcessor = class { #appRoot; constructor(appRoot) { this.#appRoot = appRoot; } async #processContents(envContents, store) { if (!envContents.trim()) return store; const values = await new EnvParser(envContents, this.#appRoot).parse(); Object.keys(values).forEach((key) => { let value = process.env[key]; if (value === void 0) { value = values[key]; process.env[key] = values[key]; } if (key in store === false) store[key] = value; }); return store; } async #loadAndProcessDotFiles() { const envFiles = await new EnvLoader(this.#appRoot).load(); if (debug_default.enabled) debug_default("processing .env files (priority from top to bottom) %O", envFiles.map((file) => file.path)); const envValues = {}; await Promise.all(envFiles.map(({ contents }) => this.#processContents(contents, envValues))); return envValues; } async process() { return this.#loadAndProcessDotFiles(); } }; function secret(options) { return function validate(key, value) { if (!value) throw new Error(options?.message ?? `Missing environment variable "${key}"`); return new Secret(value); }; } secret.optional = function optionalString() { return function validate(_, value) { if (!value) return; return new Secret(value); }; }; secret.optionalWhen = function optionalWhenString(condition, options) { return function validate(key, value) { if (typeof condition === "function" ? condition(key, value) : condition) return secret.optional()(key, value); return secret(options)(key, value); }; }; const schema$1 = { ...schema, secret }; var Env = class Env { #values; constructor(values) { this.#values = values; } static async create(appRoot, schema) { const values = await new EnvProcessor(appRoot).process(); return new Env(this.rules(schema).validate(values)); } static identifier(name, callback) { return EnvParser.defineIdentifier(name, callback); } static defineIdentifier(name, callback) { EnvParser.defineIdentifier(name, callback); } static defineIdentifierIfMissing(name, callback) { EnvParser.defineIdentifierIfMissing(name, callback); } static removeIdentifier(name) { EnvParser.removeIdentifier(name); } static schema = schema$1; static rules(schema) { return new EnvValidator(schema); } get(key, defaultValue) { if (this.#values[key] !== void 0) return this.#values[key]; const envValue = process.env[key]; if (envValue) return envValue; return defaultValue; } set(key, value) { this.#values[key] = value; process.env[key] = value; } }; export { Env, EnvLoader, EnvParser, EnvProcessor, errors_exports as errors };