@shons/next-configify
Version:
NestJS Config on Next Steroids
115 lines (102 loc) • 3.2 kB
text/typescript
import { ConfigfyModuleOptions } from 'src/configuration';
/**
* The variables expansion class.
*
* This is a slightly modified version of the dotenv-expand lib
* due to the lack of support to variables containing '.' and '-'.
*
* Once the dotev-expand lib adds support to '.' and '-' this class can be removed.
*/
export class Variables {
/**
* Escaped \$ matcher
*/
private static readonly ESCAPE_PATTERN: RegExp = /\\\$/g;
/**
* Unescaped $ matcher
*/
private static readonly UNESCAPED_PATTERN: RegExp = /(?!(?<=\\))\$/g;
/**
* (
* (?!(?<=\\))\$ // only match dollar signs that are not escaped
* {? // optional opening curly brace
* ([\w]+) // match the variable name
* ([\w-.]+) // match the variable name
* (?::-([^}\\]*))? // match an optional default value
* }? // optional closing curly brace
* )
*/
private static readonly EXPANSION_GROUP_PATTERN: RegExp =
/((?!(?<=\\))\${?([\w-.]+)(?::-([^}\\]*))?}?)/;
/**
* Expands the variables of the given object.
*
* @param {Record<string, unknown>} record The object
* @returns {Record<string, unknown>} The expanded object
*/
static expand(record: Record<string, unknown>): Record<string, unknown> {
const expanded = {};
for (const key in record) {
const value = record[key];
const interpolated = this.interpolate(value, record);
expanded[key] =
typeof interpolated === 'string'
? this.escapeSequences(interpolated)
: interpolated;
}
return expanded;
}
/**
* Replaces ${variable} with actual values recursively.
*
* @param value
* @param record
* @returns
*/
private static interpolate(
value: unknown,
record: Record<string, unknown>,
): unknown {
if (typeof value !== 'string') return value;
const lastUnescapedDollarSignIndex = this.lastIndexOf(
value,
this.UNESCAPED_PATTERN,
);
if (lastUnescapedDollarSignIndex === -1) return value;
const rightMostGroup = value.slice(lastUnescapedDollarSignIndex);
const match = rightMostGroup.match(this.EXPANSION_GROUP_PATTERN);
if (match != null) {
let [, group, variableName, defaultValue] = match;
variableName =
variableName.indexOf('-') !== -1
? variableName.replaceAll('-', '_')
: variableName;
variableName = variableName.replaceAll('.', '_').toUpperCase();
return this.interpolate(
value.replace(group, defaultValue || `${record[variableName]}` || ''),
record,
);
}
return value;
}
/**
* Replaces escaped \$ signs with $
*
* @param value
* @returns
*/
private static escapeSequences(value: string): string {
return value.replace(this.ESCAPE_PATTERN, '$');
}
/**
* Finds the last index of the matching regex.
*
* @param value
* @param regex
* @returns
*/
private static lastIndexOf(value: string, regex: RegExp): number {
const matches = Array.from(value.matchAll(regex));
return matches.length > 0 ? matches.slice(-1)[0].index : -1;
}
}