deno.land / std@0.166.0 / dotenv / mod.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license./** * Load environment variables from `.env` files. * * @module */
import { difference, removeEmptyValues } from "./util.ts";
export interface DotenvConfig { [key: string]: string;}
type StringList = Array<string> | undefined;
export interface ConfigOptions { path?: string; export?: boolean; safe?: boolean; example?: string; allowEmptyValues?: boolean; defaults?: string; restrictEnvAccessTo?: StringList;}
type LineParseResult = { key: string; unquoted: string; interpolated: string; notInterpolated: string;};
type CharactersMap = { [key: string]: string };
const RE_KeyValue = /^\s*(?:export\s+)?(?<key>[a-zA-Z_]+[a-zA-Z0-9_]*?)\s*=[\ \t]*('\n?(?<notInterpolated>(.|\n)*?)\n?'|"\n?(?<interpolated>(.|\n)*?)\n?"|(?<unquoted>[^\n#]*)) *#*.*$/gm;
const RE_ExpandValue = /(\${(?<inBrackets>.+?)(\:-(?<inBracketsDefault>.+))?}|(?<!\\)\$(?<notInBrackets>\w+)(\:-(?<notInBracketsDefault>.+))?)/g;
export function parse( rawDotenv: string, restrictEnvAccessTo: StringList = [],): DotenvConfig { const env: DotenvConfig = {};
let match; const keysForExpandCheck = [];
while ((match = RE_KeyValue.exec(rawDotenv)) != null) { const { key, interpolated, notInterpolated, unquoted } = match ?.groups as LineParseResult;
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, ...readEnv(restrictEnvAccessTo) }; keysForExpandCheck.forEach((key) => { env[key] = expand(env[key], variablesMap); });
return env;}
const defaultConfigOptions = { path: `.env`, export: false, safe: false, example: `.env.example`, allowEmptyValues: false, defaults: `.env.defaults`, restrictEnvAccessTo: [],};
export function configSync(options: ConfigOptions = {}): DotenvConfig { const o: Required<ConfigOptions> = { ...defaultConfigOptions, ...options };
const conf = parseFile(o.path, o.restrictEnvAccessTo);
if (o.defaults) { const confDefaults = parseFile(o.defaults, o.restrictEnvAccessTo); for (const key in confDefaults) { if (!(key in conf)) { conf[key] = confDefaults[key]; } } }
if (o.safe) { const confExample = parseFile(o.example, o.restrictEnvAccessTo); assertSafe(conf, confExample, o.allowEmptyValues, o.restrictEnvAccessTo); }
if (o.export) { for (const key in conf) { if (Deno.env.get(key) !== undefined) continue; Deno.env.set(key, conf[key]); } }
return conf;}
export async function config( options: ConfigOptions = {},): Promise<DotenvConfig> { const o: Required<ConfigOptions> = { ...defaultConfigOptions, ...options };
const conf = await parseFileAsync(o.path, o.restrictEnvAccessTo);
if (o.defaults) { const confDefaults = await parseFileAsync( o.defaults, o.restrictEnvAccessTo, ); for (const key in confDefaults) { if (!(key in conf)) { conf[key] = confDefaults[key]; } } }
if (o.safe) { const confExample = await parseFileAsync(o.example, o.restrictEnvAccessTo); assertSafe(conf, confExample, o.allowEmptyValues, o.restrictEnvAccessTo); }
if (o.export) { for (const key in conf) { if (Deno.env.get(key) !== undefined) continue; Deno.env.set(key, conf[key]); } }
return conf;}
function parseFile(filepath: string, restrictEnvAccessTo: StringList = []) { try { return parse( new TextDecoder("utf-8").decode(Deno.readFileSync(filepath)), restrictEnvAccessTo, ); } catch (e) { if (e instanceof Deno.errors.NotFound) return {}; throw e; }}
async function parseFileAsync( filepath: string, restrictEnvAccessTo: StringList = [],) { try { return parse( new TextDecoder("utf-8").decode(await Deno.readFile(filepath)), restrictEnvAccessTo, ); } catch (e) { if (e instanceof Deno.errors.NotFound) return {}; throw e; }}
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 assertSafe( conf: DotenvConfig, confExample: DotenvConfig, allowEmptyValues: boolean, restrictEnvAccessTo: StringList = [],) { const currentEnv = readEnv(restrictEnvAccessTo);
// Not all the variables have to be defined in .env, they can be supplied externally const confWithEnv = Object.assign({}, currentEnv, conf);
const missing = difference( Object.keys(confExample), // If allowEmptyValues is false, filter out empty values from configuration Object.keys( allowEmptyValues ? confWithEnv : removeEmptyValues(confWithEnv), ), );
if (missing.length > 0) { const errorMessages = [ `The following variables were defined in the example file but are not present in the environment:\n ${ missing.join( ", ", ) }`, `Make sure to add them to your env file.`, !allowEmptyValues && `If you expect any of these variables to be empty, you can set the allowEmptyValues option to true.`, ];
throw new MissingEnvVarsError( errorMessages.filter(Boolean).join("\n\n"), missing, ); }}
// a guarded env access, that reads only a subset from the Deno.env object,// if `restrictEnvAccessTo` property is passed.function readEnv( restrictEnvAccessTo: StringList,) { if ( restrictEnvAccessTo && Array.isArray(restrictEnvAccessTo) && restrictEnvAccessTo.length > 0 ) { return restrictEnvAccessTo.reduce( (accessedEnvVars: DotenvConfig, envVarName: string): DotenvConfig => { if (Deno.env.get(envVarName)) { accessedEnvVars[envVarName] = Deno.env.get(envVarName) as string; } return accessedEnvVars; }, {}, ); }
return Deno.env.toObject();}
export class MissingEnvVarsError extends Error { missing: string[]; constructor(message: string, missing: string[]) { super(message); this.name = "MissingEnvVarsError"; this.missing = missing; Object.setPrototypeOf(this, new.target.prototype); }}
function expand(str: string, variablesMap: { [key: string]: string }): string { if (RE_ExpandValue.test(str)) { return expand( str.replace(RE_ExpandValue, function (...params) { const { inBrackets, inBracketsDefault, notInBrackets, notInBracketsDefault, } = params[params.length - 1]; const expandValue = inBrackets || notInBrackets; const defaultValue = inBracketsDefault || notInBracketsDefault;
return variablesMap[expandValue] || expand(defaultValue, variablesMap); }), variablesMap, ); } else { return str; }}
/** * @param object object to be stringified * @returns string of object * ```ts * import { stringify } from "https://deno.land/std@$STD_VERSION/dotenv/mod.ts"; * * const object = { GREETING: "hello world" }; * const string = stringify(object); * ``` */export function stringify(object: DotenvConfig) { const lines: string[] = []; for (const [key, value] of Object.entries(object)) { let quote;
let escapedValue = value ?? ""; if (key.startsWith("#")) { console.warn( `key starts with a '#' indicates a comment and is ignored: '${key}'`, ); continue; } else if (escapedValue.includes("\n")) { // escape inner new lines escapedValue = escapedValue.replaceAll("\n", "\\n"); quote = `"`; } else if (escapedValue.match(/\W/)) { quote = "'"; }
if (quote) { // escape inner quotes escapedValue = escapedValue.replaceAll(quote, `\\${quote}`); escapedValue = `${quote}${escapedValue}${quote}`; } const line = `${key}=${escapedValue}`; lines.push(line); } return lines.join("\n");}
Version Info