File
envFilePath
(Optional)
|
Type
|
string
|
envValues
(Optional)
|
Type
|
string[]
|
import { Tree } from '@nx/devkit';
/**
* A a key-value object to be easily processed
* by other functions.
*
* {a: 1, b:"value"}
*/
export interface EnvOptionObject {
BFF_BASE_URL?: string;
PERSIST_ACL_POLICY_URL?: string;
HEADER_LOGO_LINK_URL?: string;
[key: string]: unknown;
}
export interface ReadEnvOptions {
envFilePath?: string;
envValues?: string[];
}
/**
* Combines a given config of environment details with a separate list of env values
* given through the CLI. CLI values will override static config values.
* CLI values need to have the following format.
*
* The constructed env object returned by this function will then look like this:
* ```
* {
* KEY_A: 'value'
* KEY_B: 'some other value if `KEY_B` is present in process.env'
* }
* ```
*/
export function readEnv(tree: Tree, options: ReadEnvOptions): EnvOptionObject {
const envFromFile = readEnvFromFile(tree, options.envFilePath);
const envFromCli = parseEnvOptions(options.envValues ?? []);
// resulting env object with defaults
const env = {
BFF_BASE_URL: '',
PERSIST_ACL_POLICY_URL: '',
TRACKING_APP_NAME: '',
TRACKING_ADOBE_URL: '',
HEADER_LOGO_LINK_URL: '',
...envFromFile,
...envFromCli
};
return env;
}
/**
* Read and parse env options from a plain text file
* in the format:
* A=1
* B=value
*/
function readEnvFromFile(tree: Tree, envPath?: string): EnvOptionObject {
if (envPath) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const envFileContent = tree.read(envPath)!.toString();
return parseEnvFile(envFileContent);
}
return {};
}
/**
* Cherry picked from dotenv
* via https://github.com/motdotla/dotenv/blob/7301ac9be0b2c766f865bbe24280bf82586d25aa/lib/main.js
*/
function parseEnvFile(content: string): EnvOptionObject {
const NEWLINE = '\n';
const RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*(.*)?\s*$/;
const RE_NEWLINES = /\\n/g;
const NEWLINES_MATCH = /\n|\r|\r\n/;
const obj: Record<string, string> = {};
content.split(NEWLINES_MATCH).forEach(function (line) {
// matching "KEY' and 'VAL' in 'KEY=VAL'
const keyValueArr = line.match(RE_INI_KEY_VAL);
if (keyValueArr != null) {
const key = keyValueArr[1];
// default undefined or missing values to empty string
let val = keyValueArr[2] || '';
const end = val.length - 1;
const isDoubleQuoted = val[0] === '"' && val[end] === '"';
const isSingleQuoted = val[0] === "'" && val[end] === "'";
// if single or double quoted, remove quotes
if (isSingleQuoted || isDoubleQuoted) {
val = val.substring(1, end);
// if double quoted, expand newlines
if (isDoubleQuoted) {
val = val.replace(RE_NEWLINES, NEWLINE);
}
} else {
// remove surrounding whitespace
val = val.trim();
}
obj[key] = val;
}
});
return obj;
}
/**
* Parse options given through the CLI.
* Pass in a key only to retrieve the value from the process environment (via process.env)
* or pass in a `key=value` combination similar to the file format to provide an explicit value.
*
* The result is a key-value object
* {a: 1, b:"value"}
*
*/
function parseEnvOptions(overrides: string[]): EnvOptionObject {
const overrideMap = overrides.reduce((accu: Record<string, string | undefined>, item) => {
const parts = item.split('=');
const [key, value] = parts;
if (key && value) {
accu[key] = value;
} else if (parts.length === 1 && key && process.env[key]) {
const envValue = process.env[key];
accu[key] = envValue;
} else {
accu[key] = '';
}
return accu;
}, {});
return overrideMap;
}