feat: recursive embedding of env vars in env vars

This commit is contained in:
Nicholas Krul 2021-03-10 14:12:48 +11:00 committed by Todd Bluhm
parent e5d3a27a54
commit 53f4549001
No known key found for this signature in database
GPG Key ID: 9CF312607477B8AB
9 changed files with 63 additions and 1 deletions

View File

@ -5,6 +5,7 @@
- **Upgrade**: Upgraded dependency `commander` to `5.x`
- **Upgrade**: Upgraded devDependencies `ts-standard`, `sinon`
- **Feature**: support both `$var` and `${var}` when expanding vars
- **Feature**: Added support for nested env variables with the `--recursive` flag
## 10.1.0

5
dist/env-cmd.js vendored
View File

@ -29,6 +29,11 @@ export async function EnvCmd({ command, commandArgs, envFile, rc, options = {},
// Add in the system environment variables to our environment list
env = Object.assign({}, processLib.env, env);
}
if (options.recursive === true) {
for (const key of Object.keys(env)) {
env[key] = expand_envs_1.expandEnvs(env[key], env);
}
}
if (options.expandEnvs === true) {
command = expandEnvs(command, env);
commandArgs = commandArgs.map(arg => expandEnvs(arg, env));

9
dist/parse-args.js vendored
View File

@ -28,6 +28,10 @@ export function parseArgs(args) {
if (parsedCmdOptions.expandEnvs === true) {
expandEnvs = true;
}
let recursive = false;
if (program.recursive === true) {
recursive = true;
}
let verbose = false;
if (parsedCmdOptions.verbose === true) {
verbose = true;
@ -65,6 +69,7 @@ export function parseArgs(args) {
rc,
options: {
expandEnvs,
recursive,
noOverride,
silent,
useShell,
@ -83,16 +88,18 @@ export function parseArgsUsingCommander(args) {
.usage('[options] -- <command> [...args]')
.option('-e, --environments [envs...]', 'The rc file environment(s) to use', parseArgList)
.option('-f, --file [path]', 'Custom env file path or .rc file path if \'-e\' used (default path: ./.env or ./.env-cmdrc.(js|cjs|mjs|json))')
.option('-x, --expand-envs', 'Replace $var in args and command with environment variables')
.option('-x, --expand-envs', 'Replace $var and $\\{var\\} in args and command with environment variables')
.option('--fallback', 'Fallback to default env file path, if custom env file path not found')
.option('--no-override', 'Do not override existing environment variables')
.option('--silent', 'Ignore any env-cmd errors and only fail on executed program failure.')
.option('--use-shell', 'Execute the command in a new shell with the given environment')
.option('--verbose', 'Print helpful debugging information')
.option('--recursive', 'Replace $var and $\\{var\\} in env file with the referenced environment variable')
// TODO: Remove -r deprecation error on version >= v12
.addOption(new Option('-r, --rc-file [path]', 'Deprecated Option')
.hideHelp()
.argParser(() => { throw new CommanderError(1, 'deprecated-option', 'The -r flag has been deprecated, use the -f flag instead.'); }))
// ENDTODO
.allowUnknownOption(true)
.allowExcessArguments(true)
.parse(['_', '_', ...args], { from: 'node' });

1
dist/types.d.ts vendored
View File

@ -29,6 +29,7 @@ export interface EnvCmdOptions extends GetEnvVarOptions {
commandArgs: string[];
options?: {
expandEnvs?: boolean;
recursive?: boolean;
noOverride?: boolean;
silent?: boolean;
useShell?: boolean;

View File

@ -40,6 +40,12 @@ export async function EnvCmd(
env = Object.assign({}, processLib.env, env)
}
if (options.recursive === true) {
for (const key of Object.keys(env)) {
env[key] = expandEnvs(env[key], env)
}
}
if (options.expandEnvs === true) {
command = expandEnvs(command, env)
commandArgs = commandArgs.map(arg => expandEnvs(arg, env))

View File

@ -33,6 +33,10 @@ export function parseArgs(args: string[]): EnvCmdOptions {
if (parsedCmdOptions.expandEnvs === true) {
expandEnvs = true
}
let recursive = false
if (program.recursive === true) {
recursive = true
}
let verbose = false
if (parsedCmdOptions.verbose === true) {
verbose = true
@ -75,6 +79,7 @@ export function parseArgs(args: string[]): EnvCmdOptions {
rc,
options: {
expandEnvs,
recursive,
noOverride,
silent,
useShell,
@ -96,6 +101,7 @@ export function parseArgsUsingCommander(args: string[]): CommanderOptions {
.option('-e, --environments [envs...]', 'The rc file environment(s) to use', parseArgList)
.option('-f, --file [path]', 'Custom env file path or .rc file path if \'-e\' used (default path: ./.env or ./.env-cmdrc.(js|cjs|mjs|json))')
.option('-x, --expand-envs', 'Replace $var in args and command with environment variables')
.option('--recursive', 'Replace $var and $\\{var\\} in env file with the referenced environment variable')
.option('--fallback', 'Fallback to default env file path, if custom env file path not found')
.option('--no-override', 'Do not override existing environment variables')
.option('--silent', 'Ignore any env-cmd errors and only fail on executed program failure.')

View File

@ -37,6 +37,7 @@ export interface EnvCmdOptions extends GetEnvVarOptions {
commandArgs: string[]
options?: {
expandEnvs?: boolean
recursive?: boolean
noOverride?: boolean
silent?: boolean
useShell?: boolean

View File

@ -169,6 +169,35 @@ describe('EnvCmd', (): void => {
},
)
it('should spawn process with args expanded if recursive option is true',
async (): Promise<void> => {
getEnvVarsStub.returns({ PING: 'PONG', recursive: 'PING ${PING}' }) /* eslint-disable-line */
await envCmdLib.EnvCmd({
command: 'node',
commandArgs: [],
envFile: {
filePath: './.env',
fallback: true
},
rc: {
environments: ['dev'],
filePath: './.rc'
},
options: {
recursive: true
}
})
const spawnArgs = spawnStub.args[0]
assert.equal(getEnvVarsStub.callCount, 1, 'getEnvVars must be called once')
assert.equal(spawnStub.callCount, 1)
assert.isAtLeast(expandEnvsSpy.callCount, 3, 'total number of env args')
assert.equal(spawnArgs[0], 'node')
assert.equal(spawnArgs[2].env.recursive, 'PING PONG')
}
)
it('should ignore errors if silent flag provided',
async (): Promise<void> => {
delete process.env.BOB

View File

@ -98,6 +98,12 @@ describe('parseArgs', (): void => {
assert.isTrue(res.options!.expandEnvs)
})
it('should parse recursive option', (): void => {
const res = parseArgs(['-f', envFilePath, '--recursive', command, ...commandArgs])
assert.exists(res.envFile)
assert.isTrue(res.options!.recursive)
})
it('should parse silent option', (): void => {
const res = parseArgs(['-f', envFilePath, '--silent', '--', command, ...commandArgs])
assert.exists(res.envFile)