// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. type LineParseResult = { key: string; unquoted: string; interpolated: string; notInterpolated: string; }; type CharactersMap = { [key: string]: string }; const RE_KEY_VALUE = /^\s*(?:export\s+)?(?[^\s=#]+?)\s*=[\ \t]*('\r?\n?(?(.|\r\n|\n)*?)\r?\n?'|"\r?\n?(?(.|\r\n|\n)*?)\r?\n?"|(?[^\r\n#]*)) *#*.*$/gm; const RE_VALID_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/; const RE_EXPAND_VALUE = /(\${(?.+?)(\:-(?.+))?}|(?\w+)(\:-(?.+))?)/g; function expandCharacters(str: string): string { const charactersMap: CharactersMap = { "\\n": "\n", "\\r": "\r", "\\t": "\t", }; return str.replace( /\\([nrt])/g, ($1: keyof CharactersMap): string => charactersMap[$1] ?? "", ); } function expand(str: string, variablesMap: { [key: string]: string }): string { if (RE_EXPAND_VALUE.test(str)) { return expand( str.replace(RE_EXPAND_VALUE, function (...params) { const { inBrackets, inBracketsDefault, notInBrackets, notInBracketsDefault, } = params[params.length - 1]; const expandValue = inBrackets || notInBrackets; const defaultValue = inBracketsDefault || notInBracketsDefault; let value: string | undefined = variablesMap[expandValue]; if (value === undefined) { value = Deno.env.get(expandValue); } return value === undefined ? expand(defaultValue, variablesMap) : value; }), variablesMap, ); } else { return str; } } /** * Parse `.env` file output in an object. * * Note: The key needs to match the pattern /^[a-zA-Z_][a-zA-Z0-9_]*$/. * * @example Usage * ```ts * import { parse } from "@std/dotenv/parse"; * import { assertEquals } from "@std/assert"; * * const env = parse("GREETING=hello world"); * assertEquals(env, { GREETING: "hello world" }); * ``` * * @param text The text to parse. * @returns The parsed object. */ export function parse(text: string): Record { const env: Record = {}; let match; const keysForExpandCheck = []; while ((match = RE_KEY_VALUE.exec(text)) !== null) { const { key, interpolated, notInterpolated, unquoted } = match ?.groups as LineParseResult; if (!RE_VALID_KEY.test(key)) { // deno-lint-ignore no-console console.warn( `Ignored the key "${key}" as it is not a valid identifier: The key need to match the pattern /^[a-zA-Z_][a-zA-Z0-9_]*$/.`, ); continue; } if (unquoted) { keysForExpandCheck.push(key); } env[key] = typeof notInterpolated === "string" ? notInterpolated : typeof interpolated === "string" ? expandCharacters(interpolated) : unquoted.trim(); } //https://github.com/motdotla/dotenv-expand/blob/ed5fea5bf517a09fd743ce2c63150e88c8a5f6d1/lib/main.js#L23 const variablesMap = { ...env }; keysForExpandCheck.forEach((key) => { env[key] = expand(env[key]!, variablesMap); }); return env; }