New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@app-config/extensions

Package Overview
Dependencies
Maintainers
2
Versions
48
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@app-config/extensions - npm Package Compare versions

Comparing version 2.1.5 to 2.1.6

8

dist/es/index.d.ts

@@ -5,6 +5,8 @@ import { ParsingExtension } from '@app-config/core';

export declare function markAllValuesAsSecret(): ParsingExtension;
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
export declare function unescape$Directives(): ParsingExtension;
/** Uses another file as overriding values, layering them on top of current file */
export declare function overrideDirective(): ParsingExtension;
/** Uses another file as a "base", and extends on top of it */
export declare function extendsDirective(): ParsingExtension;
/** Uses another file as overriding values, layering them on top of current file */
export declare function overrideDirective(): ParsingExtension;
/** Lookup a property in the same file, and "copy" it */

@@ -16,5 +18,3 @@ export declare function extendsSelfDirective(): ParsingExtension;

export declare function timestampDirective(dateSource?: () => Date): ParsingExtension;
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
export declare function unescape$Directives(): ParsingExtension;
/** Substitues environment variables found in strings (similar to bash variable substitution) */
export declare function environmentVariableSubstitution(aliases?: EnvironmentAliases, environmentOverride?: string, environmentSourceNames?: string[] | string): ParsingExtension;
import { join, dirname, resolve, isAbsolute } from 'path';
import { isObject } from '@app-config/utils';
import { forKey, validateOptions } from '@app-config/extension-utils';
import { ParsedValue, AppConfigError, NotFoundError, FailedToSelectSubObject, } from '@app-config/core';

@@ -10,5 +10,12 @@ import { currentEnvironment, defaultAliases, FileSource, } from '@app-config/node';

}
/** Uses another file as a "base", and extends on top of it */
export function extendsDirective() {
return fileReferenceDirective('$extends', { shouldMerge: true });
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
export function unescape$Directives() {
return (value, [_, key]) => {
if (typeof key === 'string' && key.startsWith('$$')) {
return async (parse) => {
return parse(value, { rewriteKey: key.slice(1), fromEscapedDirective: true });
};
}
return false;
};
}

@@ -19,23 +26,19 @@ /** Uses another file as overriding values, layering them on top of current file */

}
/** Uses another file as a "base", and extends on top of it */
export function extendsDirective() {
return fileReferenceDirective('$extends', { shouldMerge: true });
}
/** Lookup a property in the same file, and "copy" it */
export function extendsSelfDirective() {
return (value, [_, key]) => {
if (key !== '$extendsSelf')
return false;
return async (parse, _, __, ___, root) => {
const selector = (await parse(value)).toJSON();
if (typeof selector !== 'string') {
throw new AppConfigError(`$extendsSelf was provided a non-string value`);
}
// we temporarily use a ParsedValue literal so that we get the same property lookup semantics
const selected = ParsedValue.literal(root).property(selector.split('.'));
if (selected === undefined) {
throw new AppConfigError(`$extendsSelf selector was not found (${selector})`);
}
if (selected.asObject() !== undefined) {
return parse(selected.toJSON(), { shouldMerge: true });
}
return parse(selected.toJSON(), { shouldFlatten: true });
};
};
return forKey('$extendsSelf', validateOptions((SchemaBuilder) => SchemaBuilder.stringSchema(), (value) => async (parse, _, __, ___, root) => {
// we temporarily use a ParsedValue literal so that we get the same property lookup semantics
const selected = ParsedValue.literal(root).property(value.split('.'));
if (selected === undefined) {
throw new AppConfigError(`$extendsSelf selector was not found (${value})`);
}
if (selected.asObject() !== undefined) {
return parse(selected.toJSON(), { shouldMerge: true });
}
return parse(selected.toJSON(), { shouldFlatten: true });
}));
}

@@ -46,209 +49,165 @@ /** Looks up an environment-specific value ($env) */

const metadata = { shouldOverride: true };
return (value, [_, key]) => {
if (key === '$env') {
return (parse) => {
if (!isObject(value)) {
throw new AppConfigError('An $env directive was used with a non-object value');
}
if (!environment) {
if (value.default)
return parse(value.default, metadata);
throw new AppConfigError(`An $env directive was used, but current environment (eg. NODE_ENV) is undefined`);
}
for (const [envName, envValue] of Object.entries(value)) {
if (envName === environment || aliases[envName] === environment) {
return parse(envValue, metadata);
}
}
if ('default' in value) {
return parse(value.default, metadata);
}
const found = Object.keys(value).join(', ');
throw new AppConfigError(`An $env directive was used, but none matched the current environment (wanted ${environment}, saw [${found}])`);
};
return forKey('$env', validateOptions((SchemaBuilder) => SchemaBuilder.emptySchema().addAdditionalProperties(), (value) => (parse) => {
if (!environment) {
if ('default' in value) {
return parse(value.default, metadata);
}
throw new AppConfigError(`An $env directive was used, but current environment (eg. NODE_ENV) is undefined`);
}
return false;
};
for (const [envName, envValue] of Object.entries(value)) {
if (envName === environment || aliases[envName] === environment) {
return parse(envValue, metadata);
}
}
if ('default' in value) {
return parse(value.default, metadata);
}
const found = Object.keys(value).join(', ');
throw new AppConfigError(`An $env directive was used, but none matched the current environment (wanted ${environment}, saw [${found}])`);
}, { lazy: true }));
}
/** Provides the current timestamp using { $timestamp: true } */
export function timestampDirective(dateSource = () => new Date()) {
return (value, [_, key]) => {
if (key === '$timestamp') {
return async (parse) => {
let formatted;
const date = dateSource();
if (value === true) {
formatted = date.toISOString();
}
else if (value && typeof value === 'object' && !Array.isArray(value)) {
const { locale, ...options } = value;
if (typeof locale !== 'string') {
throw new AppConfigError('$timestamp was provided a non-string locale');
}
formatted = date.toLocaleDateString(locale, options);
}
else {
throw new AppConfigError('$timestamp was provided an invalid option');
}
return parse(formatted, { shouldFlatten: true });
};
return forKey('$timestamp', validateOptions((SchemaBuilder) => SchemaBuilder.oneOf(SchemaBuilder.booleanSchema(), SchemaBuilder.emptySchema()
.addString('day', {}, false)
.addString('month', {}, false)
.addString('year', {}, false)
.addString('weekday', {}, false)
.addString('locale', {}, false)
.addString('timeZone', {}, false)
.addString('timeZoneName', {}, false)), (value) => (parse) => {
let formatted;
const date = dateSource();
if (value === true) {
formatted = date.toISOString();
}
return false;
};
}
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
export function unescape$Directives() {
return (value, [_, key]) => {
if (typeof key === 'string' && key.startsWith('$$')) {
return async (parse) => {
return parse(value, { rewriteKey: key.slice(1), fromEscapedDirective: true });
};
else if (typeof value === 'object') {
const { locale, ...options } = value;
formatted = date.toLocaleDateString(locale, options);
}
return false;
};
else {
throw new AppConfigError('$timestamp was provided an invalid option');
}
return parse(formatted, { shouldFlatten: true });
}));
}
/** Substitues environment variables found in strings (similar to bash variable substitution) */
export function environmentVariableSubstitution(aliases = defaultAliases, environmentOverride, environmentSourceNames) {
const performAllSubstitutions = (text) => {
let output = text;
/* eslint-disable-next-line no-constant-condition */
while (true) {
// this regex matches:
// $FOO
// ${FOO}
// ${FOO:-fallback}
// ${FOO:-${FALLBACK}}
//
// var name is group 1 || 2
// fallback value is group 3
// https://regex101.com/r/6ZMmx7/3
const match = /\$(?:([a-zA-Z_]\w+)|(?:{([a-zA-Z_]\w+)(?::- *(.*?) *)?}))/g.exec(output);
if (!match)
break;
const fullMatch = match[0];
const varName = match[1] || match[2];
const fallback = match[3];
if (varName) {
const env = process.env[varName];
if (env !== undefined) {
output = output.replace(fullMatch, env);
}
else if (fallback !== undefined) {
// we'll recurse again, so that ${FOO:-${FALLBACK}} -> ${FALLBACK} -> value
output = performAllSubstitutions(output.replace(fullMatch, fallback));
}
else if (varName === 'APP_CONFIG_ENV') {
const envType = environmentOverride ?? currentEnvironment(aliases, environmentSourceNames);
if (!envType) {
throw new AppConfigError(`Could not find environment variable ${varName}`);
}
// there's a special case for APP_CONFIG_ENV, which is always the envType
output = output.replace(fullMatch, envType);
}
else {
throw new AppConfigError(`Could not find environment variable ${varName}`);
}
const envType = environmentOverride ?? currentEnvironment(aliases, environmentSourceNames);
return forKey(['$substitute', '$subs'], validateOptions((SchemaBuilder) => SchemaBuilder.oneOf(SchemaBuilder.stringSchema(), SchemaBuilder.emptySchema().addString('$name').addString('$fallback', {}, false)), (value) => (parse) => {
if (typeof value === 'object') {
const { $name: variableName, $fallback: fallback } = value;
const resolvedValue = process.env[variableName];
if (fallback !== undefined) {
return parse(resolvedValue || fallback, { shouldFlatten: true });
}
if (!resolvedValue) {
throw new AppConfigError(`$substitute could not find ${variableName} environment variable`);
}
return parse(resolvedValue, { shouldFlatten: true });
}
logger.verbose(`Performed $substitute for "${text}" -> "${output}"`);
return output;
};
return (value, [_, key]) => {
if (key === '$subsitute')
logger.warn('Noticed a typo! Key of $subsitute was found.');
if (key === '$substitute' || key === '$subs') {
return (parse) => {
if (isObject(value)) {
if (!value.$name) {
throw new AppConfigError('$substitute was provided an object without $name');
}
if (typeof value.$name !== 'string') {
throw new AppConfigError('$substitute was provided an object without a string $name');
}
const variableName = value.$name;
const fallback = value.$fallback;
const resolvedValue = process.env[variableName];
if (fallback !== undefined) {
return parse(resolvedValue || fallback, { shouldFlatten: true });
}
if (!resolvedValue) {
throw new AppConfigError(`$substitute could not find ${variableName} environment variable`);
}
return parse(resolvedValue, { shouldFlatten: true });
}
if (typeof value !== 'string') {
throw new AppConfigError('$substitute expects a string value');
}
return parse(performAllSubstitutions(value), { shouldFlatten: true });
};
}
return false;
};
return parse(performAllSubstitutions(value, envType), { shouldFlatten: true });
}));
}
// common logic for $extends and $override
function fileReferenceDirective(keyName, meta) {
return (value, [_, key]) => {
if (key !== keyName)
return false;
return async (parse, _, context, extensions) => {
const retrieveFile = async (filepath, subselector, isOptional = false) => {
let resolvedPath = filepath;
// resolve filepaths that are relative to the current FileSource
if (!isAbsolute(filepath) && context instanceof FileSource) {
resolvedPath = join(dirname(context.filePath), filepath);
if (resolve(context.filePath) === resolvedPath) {
throw new AppConfigError(`A ${keyName} directive resolved to it's own file (${resolvedPath}). Please use $extendsSelf instead.`);
}
return forKey(keyName, validateOptions((SchemaBuilder) => {
const reference = SchemaBuilder.oneOf(SchemaBuilder.stringSchema(), SchemaBuilder.emptySchema()
.addString('path')
.addBoolean('optional', {}, false)
.addString('select', {}, false));
return SchemaBuilder.oneOf(reference, SchemaBuilder.arraySchema(reference));
}, (value) => async (parse, _, context, extensions) => {
const retrieveFile = async (filepath, subselector, isOptional = false) => {
let resolvedPath = filepath;
// resolve filepaths that are relative to the current FileSource
if (!isAbsolute(filepath) && context instanceof FileSource) {
resolvedPath = join(dirname(context.filePath), filepath);
if (resolve(context.filePath) === resolvedPath) {
throw new AppConfigError(`A ${keyName} directive resolved to it's own file (${resolvedPath}). Please use $extendsSelf instead.`);
}
logger.verbose(`Loading file for ${keyName}: ${resolvedPath}`);
const source = new FileSource(resolvedPath);
const parsed = await source.read(extensions).catch((error) => {
if (error instanceof NotFoundError && isOptional) {
return ParsedValue.literal({});
}
throw error;
});
if (subselector) {
const found = parsed.property(subselector.split('.'));
if (!found) {
throw new FailedToSelectSubObject(`Failed to select ${subselector} in ${resolvedPath}`);
}
return found;
}
logger.verbose(`Loading file for ${keyName}: ${resolvedPath}`);
const source = new FileSource(resolvedPath);
const parsed = await source.read(extensions).catch((error) => {
if (error instanceof NotFoundError && isOptional) {
return ParsedValue.literal({});
}
return parsed;
};
const forOptions = async (options) => {
const parsed = (await parse(options)).toJSON();
if (typeof parsed === 'string') {
return retrieveFile(parsed);
throw error;
});
if (subselector) {
const found = parsed.property(subselector.split('.'));
if (!found) {
throw new FailedToSelectSubObject(`Failed to select ${subselector} in ${resolvedPath}`);
}
if (!isObject(parsed)) {
throw new AppConfigError(`${keyName} was provided an invalid option`);
return found;
}
return parsed;
};
let parsed;
if (typeof value === 'string') {
parsed = await retrieveFile(value);
}
else if (Array.isArray(value)) {
parsed = ParsedValue.literal({});
for (const ext of value) {
if (typeof ext === 'string') {
parsed = ParsedValue.merge(parsed, await retrieveFile(ext));
}
const { path, optional, select } = parsed;
if (!path || typeof path !== 'string') {
throw new AppConfigError(`Invalid ${keyName} filepath found`);
else {
const { path, optional, select } = ext;
parsed = ParsedValue.merge(parsed, await retrieveFile(path, select, optional));
}
if (select !== undefined && typeof select !== 'string') {
throw new AppConfigError(`Invalid ${keyName} select found`);
}
}
else {
const { path, optional, select } = value;
parsed = await retrieveFile(path, select, optional);
}
return parsed.assignMeta(meta);
}));
}
function performAllSubstitutions(text, envType) {
let output = text;
/* eslint-disable-next-line no-constant-condition */
while (true) {
// this regex matches:
// $FOO
// ${FOO}
// ${FOO:-fallback}
// ${FOO:-${FALLBACK}}
//
// var name is group 1 || 2
// fallback value is group 3
// https://regex101.com/r/6ZMmx7/3
const match = /\$(?:([a-zA-Z_]\w+)|(?:{([a-zA-Z_]\w+)(?::- *(.*?) *)?}))/g.exec(output);
if (!match)
break;
const fullMatch = match[0];
const varName = match[1] || match[2];
const fallback = match[3];
if (varName) {
const env = process.env[varName];
if (env !== undefined) {
output = output.replace(fullMatch, env);
}
else if (fallback !== undefined) {
// we'll recurse again, so that ${FOO:-${FALLBACK}} -> ${FALLBACK} -> value
output = performAllSubstitutions(output.replace(fullMatch, fallback), envType);
}
else if (varName === 'APP_CONFIG_ENV') {
if (!envType) {
throw new AppConfigError(`Could not find environment variable ${varName}`);
}
if (optional !== undefined && typeof optional !== 'boolean') {
throw new AppConfigError(`Invalid ${keyName} optional found`);
}
return retrieveFile(path, select, optional);
};
let parsed;
if (Array.isArray(value)) {
parsed = ParsedValue.literal({});
for (const ext of value) {
parsed = ParsedValue.merge(parsed, await forOptions(ext));
}
// there's a special case for APP_CONFIG_ENV, which is always the envType
output = output.replace(fullMatch, envType);
}
else {
parsed = await forOptions(value);
throw new AppConfigError(`Could not find environment variable ${varName}`);
}
return parsed.assignMeta(meta);
};
};
}
}
logger.verbose(`Performed $substitute for "${text}" -> "${output}"`);
return output;
}
//# sourceMappingURL=index.js.map

@@ -5,6 +5,8 @@ import { ParsingExtension } from '@app-config/core';

export declare function markAllValuesAsSecret(): ParsingExtension;
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
export declare function unescape$Directives(): ParsingExtension;
/** Uses another file as overriding values, layering them on top of current file */
export declare function overrideDirective(): ParsingExtension;
/** Uses another file as a "base", and extends on top of it */
export declare function extendsDirective(): ParsingExtension;
/** Uses another file as overriding values, layering them on top of current file */
export declare function overrideDirective(): ParsingExtension;
/** Lookup a property in the same file, and "copy" it */

@@ -16,5 +18,3 @@ export declare function extendsSelfDirective(): ParsingExtension;

export declare function timestampDirective(dateSource?: () => Date): ParsingExtension;
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
export declare function unescape$Directives(): ParsingExtension;
/** Substitues environment variables found in strings (similar to bash variable substitution) */
export declare function environmentVariableSubstitution(aliases?: EnvironmentAliases, environmentOverride?: string, environmentSourceNames?: string[] | string): ParsingExtension;

@@ -14,5 +14,5 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.environmentVariableSubstitution = exports.unescape$Directives = exports.timestampDirective = exports.envDirective = exports.extendsSelfDirective = exports.overrideDirective = exports.extendsDirective = exports.markAllValuesAsSecret = void 0;
exports.environmentVariableSubstitution = exports.timestampDirective = exports.envDirective = exports.extendsSelfDirective = exports.extendsDirective = exports.overrideDirective = exports.unescape$Directives = exports.markAllValuesAsSecret = void 0;
const path_1 = require("path");
const utils_1 = require("@app-config/utils");
const extension_utils_1 = require("@app-config/extension-utils");
const core_1 = require("@app-config/core");

@@ -26,7 +26,14 @@ const node_1 = require("@app-config/node");

exports.markAllValuesAsSecret = markAllValuesAsSecret;
/** Uses another file as a "base", and extends on top of it */
function extendsDirective() {
return fileReferenceDirective('$extends', { shouldMerge: true });
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
function unescape$Directives() {
return (value, [_, key]) => {
if (typeof key === 'string' && key.startsWith('$$')) {
return async (parse) => {
return parse(value, { rewriteKey: key.slice(1), fromEscapedDirective: true });
};
}
return false;
};
}
exports.extendsDirective = extendsDirective;
exports.unescape$Directives = unescape$Directives;
/** Uses another file as overriding values, layering them on top of current file */

@@ -37,23 +44,20 @@ function overrideDirective() {

exports.overrideDirective = overrideDirective;
/** Uses another file as a "base", and extends on top of it */
function extendsDirective() {
return fileReferenceDirective('$extends', { shouldMerge: true });
}
exports.extendsDirective = extendsDirective;
/** Lookup a property in the same file, and "copy" it */
function extendsSelfDirective() {
return (value, [_, key]) => {
if (key !== '$extendsSelf')
return false;
return async (parse, _, __, ___, root) => {
const selector = (await parse(value)).toJSON();
if (typeof selector !== 'string') {
throw new core_1.AppConfigError(`$extendsSelf was provided a non-string value`);
}
// we temporarily use a ParsedValue literal so that we get the same property lookup semantics
const selected = core_1.ParsedValue.literal(root).property(selector.split('.'));
if (selected === undefined) {
throw new core_1.AppConfigError(`$extendsSelf selector was not found (${selector})`);
}
if (selected.asObject() !== undefined) {
return parse(selected.toJSON(), { shouldMerge: true });
}
return parse(selected.toJSON(), { shouldFlatten: true });
};
};
return extension_utils_1.forKey('$extendsSelf', extension_utils_1.validateOptions((SchemaBuilder) => SchemaBuilder.stringSchema(), (value) => async (parse, _, __, ___, root) => {
// we temporarily use a ParsedValue literal so that we get the same property lookup semantics
const selected = core_1.ParsedValue.literal(root).property(value.split('.'));
if (selected === undefined) {
throw new core_1.AppConfigError(`$extendsSelf selector was not found (${value})`);
}
if (selected.asObject() !== undefined) {
return parse(selected.toJSON(), { shouldMerge: true });
}
return parse(selected.toJSON(), { shouldFlatten: true });
}));
}

@@ -65,27 +69,20 @@ exports.extendsSelfDirective = extendsSelfDirective;

const metadata = { shouldOverride: true };
return (value, [_, key]) => {
if (key === '$env') {
return (parse) => {
if (!utils_1.isObject(value)) {
throw new core_1.AppConfigError('An $env directive was used with a non-object value');
}
if (!environment) {
if (value.default)
return parse(value.default, metadata);
throw new core_1.AppConfigError(`An $env directive was used, but current environment (eg. NODE_ENV) is undefined`);
}
for (const [envName, envValue] of Object.entries(value)) {
if (envName === environment || aliases[envName] === environment) {
return parse(envValue, metadata);
}
}
if ('default' in value) {
return parse(value.default, metadata);
}
const found = Object.keys(value).join(', ');
throw new core_1.AppConfigError(`An $env directive was used, but none matched the current environment (wanted ${environment}, saw [${found}])`);
};
return extension_utils_1.forKey('$env', extension_utils_1.validateOptions((SchemaBuilder) => SchemaBuilder.emptySchema().addAdditionalProperties(), (value) => (parse) => {
if (!environment) {
if ('default' in value) {
return parse(value.default, metadata);
}
throw new core_1.AppConfigError(`An $env directive was used, but current environment (eg. NODE_ENV) is undefined`);
}
return false;
};
for (const [envName, envValue] of Object.entries(value)) {
if (envName === environment || aliases[envName] === environment) {
return parse(envValue, metadata);
}
}
if ('default' in value) {
return parse(value.default, metadata);
}
const found = Object.keys(value).join(', ');
throw new core_1.AppConfigError(`An $env directive was used, but none matched the current environment (wanted ${environment}, saw [${found}])`);
}, { lazy: true }));
}

@@ -95,116 +92,43 @@ exports.envDirective = envDirective;

function timestampDirective(dateSource = () => new Date()) {
return (value, [_, key]) => {
if (key === '$timestamp') {
return async (parse) => {
let formatted;
const date = dateSource();
if (value === true) {
formatted = date.toISOString();
}
else if (value && typeof value === 'object' && !Array.isArray(value)) {
const { locale } = value, options = __rest(value, ["locale"]);
if (typeof locale !== 'string') {
throw new core_1.AppConfigError('$timestamp was provided a non-string locale');
}
formatted = date.toLocaleDateString(locale, options);
}
else {
throw new core_1.AppConfigError('$timestamp was provided an invalid option');
}
return parse(formatted, { shouldFlatten: true });
};
return extension_utils_1.forKey('$timestamp', extension_utils_1.validateOptions((SchemaBuilder) => SchemaBuilder.oneOf(SchemaBuilder.booleanSchema(), SchemaBuilder.emptySchema()
.addString('day', {}, false)
.addString('month', {}, false)
.addString('year', {}, false)
.addString('weekday', {}, false)
.addString('locale', {}, false)
.addString('timeZone', {}, false)
.addString('timeZoneName', {}, false)), (value) => (parse) => {
let formatted;
const date = dateSource();
if (value === true) {
formatted = date.toISOString();
}
return false;
};
else if (typeof value === 'object') {
const { locale } = value, options = __rest(value, ["locale"]);
formatted = date.toLocaleDateString(locale, options);
}
else {
throw new core_1.AppConfigError('$timestamp was provided an invalid option');
}
return parse(formatted, { shouldFlatten: true });
}));
}
exports.timestampDirective = timestampDirective;
/** When a key $$foo is seen, change it to be $foo and mark with meta property fromEscapedDirective */
function unescape$Directives() {
return (value, [_, key]) => {
if (typeof key === 'string' && key.startsWith('$$')) {
return async (parse) => {
return parse(value, { rewriteKey: key.slice(1), fromEscapedDirective: true });
};
}
return false;
};
}
exports.unescape$Directives = unescape$Directives;
/** Substitues environment variables found in strings (similar to bash variable substitution) */
function environmentVariableSubstitution(aliases = node_1.defaultAliases, environmentOverride, environmentSourceNames) {
const performAllSubstitutions = (text) => {
let output = text;
/* eslint-disable-next-line no-constant-condition */
while (true) {
// this regex matches:
// $FOO
// ${FOO}
// ${FOO:-fallback}
// ${FOO:-${FALLBACK}}
//
// var name is group 1 || 2
// fallback value is group 3
// https://regex101.com/r/6ZMmx7/3
const match = /\$(?:([a-zA-Z_]\w+)|(?:{([a-zA-Z_]\w+)(?::- *(.*?) *)?}))/g.exec(output);
if (!match)
break;
const fullMatch = match[0];
const varName = match[1] || match[2];
const fallback = match[3];
if (varName) {
const env = process.env[varName];
if (env !== undefined) {
output = output.replace(fullMatch, env);
}
else if (fallback !== undefined) {
// we'll recurse again, so that ${FOO:-${FALLBACK}} -> ${FALLBACK} -> value
output = performAllSubstitutions(output.replace(fullMatch, fallback));
}
else if (varName === 'APP_CONFIG_ENV') {
const envType = environmentOverride !== null && environmentOverride !== void 0 ? environmentOverride : node_1.currentEnvironment(aliases, environmentSourceNames);
if (!envType) {
throw new core_1.AppConfigError(`Could not find environment variable ${varName}`);
}
// there's a special case for APP_CONFIG_ENV, which is always the envType
output = output.replace(fullMatch, envType);
}
else {
throw new core_1.AppConfigError(`Could not find environment variable ${varName}`);
}
const envType = environmentOverride !== null && environmentOverride !== void 0 ? environmentOverride : node_1.currentEnvironment(aliases, environmentSourceNames);
return extension_utils_1.forKey(['$substitute', '$subs'], extension_utils_1.validateOptions((SchemaBuilder) => SchemaBuilder.oneOf(SchemaBuilder.stringSchema(), SchemaBuilder.emptySchema().addString('$name').addString('$fallback', {}, false)), (value) => (parse) => {
if (typeof value === 'object') {
const { $name: variableName, $fallback: fallback } = value;
const resolvedValue = process.env[variableName];
if (fallback !== undefined) {
return parse(resolvedValue || fallback, { shouldFlatten: true });
}
if (!resolvedValue) {
throw new core_1.AppConfigError(`$substitute could not find ${variableName} environment variable`);
}
return parse(resolvedValue, { shouldFlatten: true });
}
logging_1.logger.verbose(`Performed $substitute for "${text}" -> "${output}"`);
return output;
};
return (value, [_, key]) => {
if (key === '$subsitute')
logging_1.logger.warn('Noticed a typo! Key of $subsitute was found.');
if (key === '$substitute' || key === '$subs') {
return (parse) => {
if (utils_1.isObject(value)) {
if (!value.$name) {
throw new core_1.AppConfigError('$substitute was provided an object without $name');
}
if (typeof value.$name !== 'string') {
throw new core_1.AppConfigError('$substitute was provided an object without a string $name');
}
const variableName = value.$name;
const fallback = value.$fallback;
const resolvedValue = process.env[variableName];
if (fallback !== undefined) {
return parse(resolvedValue || fallback, { shouldFlatten: true });
}
if (!resolvedValue) {
throw new core_1.AppConfigError(`$substitute could not find ${variableName} environment variable`);
}
return parse(resolvedValue, { shouldFlatten: true });
}
if (typeof value !== 'string') {
throw new core_1.AppConfigError('$substitute expects a string value');
}
return parse(performAllSubstitutions(value), { shouldFlatten: true });
};
}
return false;
};
return parse(performAllSubstitutions(value, envType), { shouldFlatten: true });
}));
}

@@ -214,66 +138,101 @@ exports.environmentVariableSubstitution = environmentVariableSubstitution;

function fileReferenceDirective(keyName, meta) {
return (value, [_, key]) => {
if (key !== keyName)
return false;
return async (parse, _, context, extensions) => {
const retrieveFile = async (filepath, subselector, isOptional = false) => {
let resolvedPath = filepath;
// resolve filepaths that are relative to the current FileSource
if (!path_1.isAbsolute(filepath) && context instanceof node_1.FileSource) {
resolvedPath = path_1.join(path_1.dirname(context.filePath), filepath);
if (path_1.resolve(context.filePath) === resolvedPath) {
throw new core_1.AppConfigError(`A ${keyName} directive resolved to it's own file (${resolvedPath}). Please use $extendsSelf instead.`);
}
return extension_utils_1.forKey(keyName, extension_utils_1.validateOptions((SchemaBuilder) => {
const reference = SchemaBuilder.oneOf(SchemaBuilder.stringSchema(), SchemaBuilder.emptySchema()
.addString('path')
.addBoolean('optional', {}, false)
.addString('select', {}, false));
return SchemaBuilder.oneOf(reference, SchemaBuilder.arraySchema(reference));
}, (value) => async (parse, _, context, extensions) => {
const retrieveFile = async (filepath, subselector, isOptional = false) => {
let resolvedPath = filepath;
// resolve filepaths that are relative to the current FileSource
if (!path_1.isAbsolute(filepath) && context instanceof node_1.FileSource) {
resolvedPath = path_1.join(path_1.dirname(context.filePath), filepath);
if (path_1.resolve(context.filePath) === resolvedPath) {
throw new core_1.AppConfigError(`A ${keyName} directive resolved to it's own file (${resolvedPath}). Please use $extendsSelf instead.`);
}
logging_1.logger.verbose(`Loading file for ${keyName}: ${resolvedPath}`);
const source = new node_1.FileSource(resolvedPath);
const parsed = await source.read(extensions).catch((error) => {
if (error instanceof core_1.NotFoundError && isOptional) {
return core_1.ParsedValue.literal({});
}
throw error;
});
if (subselector) {
const found = parsed.property(subselector.split('.'));
if (!found) {
throw new core_1.FailedToSelectSubObject(`Failed to select ${subselector} in ${resolvedPath}`);
}
return found;
}
logging_1.logger.verbose(`Loading file for ${keyName}: ${resolvedPath}`);
const source = new node_1.FileSource(resolvedPath);
const parsed = await source.read(extensions).catch((error) => {
if (error instanceof core_1.NotFoundError && isOptional) {
return core_1.ParsedValue.literal({});
}
return parsed;
};
const forOptions = async (options) => {
const parsed = (await parse(options)).toJSON();
if (typeof parsed === 'string') {
return retrieveFile(parsed);
throw error;
});
if (subselector) {
const found = parsed.property(subselector.split('.'));
if (!found) {
throw new core_1.FailedToSelectSubObject(`Failed to select ${subselector} in ${resolvedPath}`);
}
if (!utils_1.isObject(parsed)) {
throw new core_1.AppConfigError(`${keyName} was provided an invalid option`);
return found;
}
return parsed;
};
let parsed;
if (typeof value === 'string') {
parsed = await retrieveFile(value);
}
else if (Array.isArray(value)) {
parsed = core_1.ParsedValue.literal({});
for (const ext of value) {
if (typeof ext === 'string') {
parsed = core_1.ParsedValue.merge(parsed, await retrieveFile(ext));
}
const { path, optional, select } = parsed;
if (!path || typeof path !== 'string') {
throw new core_1.AppConfigError(`Invalid ${keyName} filepath found`);
else {
const { path, optional, select } = ext;
parsed = core_1.ParsedValue.merge(parsed, await retrieveFile(path, select, optional));
}
if (select !== undefined && typeof select !== 'string') {
throw new core_1.AppConfigError(`Invalid ${keyName} select found`);
}
}
else {
const { path, optional, select } = value;
parsed = await retrieveFile(path, select, optional);
}
return parsed.assignMeta(meta);
}));
}
function performAllSubstitutions(text, envType) {
let output = text;
/* eslint-disable-next-line no-constant-condition */
while (true) {
// this regex matches:
// $FOO
// ${FOO}
// ${FOO:-fallback}
// ${FOO:-${FALLBACK}}
//
// var name is group 1 || 2
// fallback value is group 3
// https://regex101.com/r/6ZMmx7/3
const match = /\$(?:([a-zA-Z_]\w+)|(?:{([a-zA-Z_]\w+)(?::- *(.*?) *)?}))/g.exec(output);
if (!match)
break;
const fullMatch = match[0];
const varName = match[1] || match[2];
const fallback = match[3];
if (varName) {
const env = process.env[varName];
if (env !== undefined) {
output = output.replace(fullMatch, env);
}
else if (fallback !== undefined) {
// we'll recurse again, so that ${FOO:-${FALLBACK}} -> ${FALLBACK} -> value
output = performAllSubstitutions(output.replace(fullMatch, fallback), envType);
}
else if (varName === 'APP_CONFIG_ENV') {
if (!envType) {
throw new core_1.AppConfigError(`Could not find environment variable ${varName}`);
}
if (optional !== undefined && typeof optional !== 'boolean') {
throw new core_1.AppConfigError(`Invalid ${keyName} optional found`);
}
return retrieveFile(path, select, optional);
};
let parsed;
if (Array.isArray(value)) {
parsed = core_1.ParsedValue.literal({});
for (const ext of value) {
parsed = core_1.ParsedValue.merge(parsed, await forOptions(ext));
}
// there's a special case for APP_CONFIG_ENV, which is always the envType
output = output.replace(fullMatch, envType);
}
else {
parsed = await forOptions(value);
throw new core_1.AppConfigError(`Could not find environment variable ${varName}`);
}
return parsed.assignMeta(meta);
};
};
}
}
logging_1.logger.verbose(`Performed $substitute for "${text}" -> "${output}"`);
return output;
}
//# sourceMappingURL=index.js.map
{
"name": "@app-config/extensions",
"description": "Common parsing extensions for @app-config",
"version": "2.1.5",
"version": "2.1.6",
"license": "MPL-2.0",

@@ -33,9 +33,10 @@ "author": {

"dependencies": {
"@app-config/core": "^2.1.5",
"@app-config/logging": "^2.1.5",
"@app-config/node": "^2.1.5",
"@app-config/utils": "^2.1.5"
"@app-config/core": "^2.1.6",
"@app-config/extension-utils": "^2.1.6",
"@app-config/logging": "^2.1.6",
"@app-config/node": "^2.1.6",
"@app-config/utils": "^2.1.6"
},
"devDependencies": {
"@app-config/test-utils": "^2.1.5"
"@app-config/test-utils": "^2.1.6"
},

@@ -42,0 +43,0 @@ "prettier": "@lcdev/prettier",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc