mirror of
https://github.com/toddbluhm/env-cmd.git
synced 2025-12-08 18:23:33 +00:00
feat: Support env vars expansion in cmd and args
This pull request adds support for expanding environment variable expansion support. Closes #91 Since the user controls the call site of cmd-env, it only supports basic UNIX style $var format. `%var%` (windows) or `${var}` is not supported. However, you can escape variable expansion by escaping the dollar sign like so `\$`. Signed-off-by: omeid matten <public@omeid.me>
This commit is contained in:
parent
bd090e7c6b
commit
8318637c48
@ -61,6 +61,7 @@ Options:
|
|||||||
-f, --file [path] Custom env file path (default path: ./.env)
|
-f, --file [path] Custom env file path (default path: ./.env)
|
||||||
-r, --rc-file [path] Custom rc file path (default path: ./.env-cmdrc(|.js|.json)
|
-r, --rc-file [path] Custom rc file path (default path: ./.env-cmdrc(|.js|.json)
|
||||||
-e, --environments [env1,env2,...] The rc file environment(s) to use
|
-e, --environments [env1,env2,...] The rc file environment(s) to use
|
||||||
|
-x, --expand-envs Replace $var in args and command with environment variables
|
||||||
--fallback Fallback to default env file path, if custom env file path not found
|
--fallback Fallback to default env file path, if custom env file path not found
|
||||||
--no-override Do not override existing environment variables
|
--no-override Do not override existing environment variables
|
||||||
--use-shell Execute the command in a new shell with the given environment
|
--use-shell Execute the command in a new shell with the given environment
|
||||||
|
|||||||
5
dist/env-cmd.js
vendored
5
dist/env-cmd.js
vendored
@ -4,6 +4,7 @@ const spawn_1 = require("./spawn");
|
|||||||
const signal_termination_1 = require("./signal-termination");
|
const signal_termination_1 = require("./signal-termination");
|
||||||
const parse_args_1 = require("./parse-args");
|
const parse_args_1 = require("./parse-args");
|
||||||
const get_env_vars_1 = require("./get-env-vars");
|
const get_env_vars_1 = require("./get-env-vars");
|
||||||
|
const expand_envs_1 = require("./expand-envs");
|
||||||
/**
|
/**
|
||||||
* Executes env - cmd using command line arguments
|
* Executes env - cmd using command line arguments
|
||||||
* @export
|
* @export
|
||||||
@ -41,6 +42,10 @@ async function EnvCmd({ command, commandArgs, envFile, rc, options = {} }) {
|
|||||||
// Add in the system environment variables to our environment list
|
// Add in the system environment variables to our environment list
|
||||||
env = Object.assign({}, process.env, env);
|
env = Object.assign({}, process.env, env);
|
||||||
}
|
}
|
||||||
|
if (options.expandEnvs === true) {
|
||||||
|
command = expand_envs_1.expandEnvs(command, env);
|
||||||
|
commandArgs = commandArgs.map(arg => expand_envs_1.expandEnvs(arg, env));
|
||||||
|
}
|
||||||
// Execute the command with the given environment variables
|
// Execute the command with the given environment variables
|
||||||
const proc = spawn_1.spawn(command, commandArgs, {
|
const proc = spawn_1.spawn(command, commandArgs, {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
|
|||||||
7
dist/expand-envs.d.ts
vendored
Normal file
7
dist/expand-envs.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* expandEnvs Replaces $var in args and command with environment variables
|
||||||
|
* the environment variable doesn't exist, it leaves it as is.
|
||||||
|
*/
|
||||||
|
export declare function expandEnvs(str: string, envs: {
|
||||||
|
[key: string]: any;
|
||||||
|
}): string;
|
||||||
13
dist/expand-envs.js
vendored
Normal file
13
dist/expand-envs.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
/**
|
||||||
|
* expandEnvs Replaces $var in args and command with environment variables
|
||||||
|
* the environment variable doesn't exist, it leaves it as is.
|
||||||
|
*/
|
||||||
|
function expandEnvs(str, envs) {
|
||||||
|
return str.replace(/(?<!\\)\$[a-zA-Z0-9_]+/g, varName => {
|
||||||
|
const varValue = envs[varName.slice(1)];
|
||||||
|
return varValue === undefined ? varName : varValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
exports.expandEnvs = expandEnvs;
|
||||||
5
dist/parse-args.js
vendored
5
dist/parse-args.js
vendored
@ -14,6 +14,7 @@ function parseArgs(args) {
|
|||||||
program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)));
|
program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)));
|
||||||
const noOverride = !program.override;
|
const noOverride = !program.override;
|
||||||
const useShell = !!program.useShell;
|
const useShell = !!program.useShell;
|
||||||
|
const expandEnvs = !!program.expandEnvs;
|
||||||
const verbose = !!program.verbose;
|
const verbose = !!program.verbose;
|
||||||
let rc;
|
let rc;
|
||||||
if (program.environments !== undefined && program.environments.length !== 0) {
|
if (program.environments !== undefined && program.environments.length !== 0) {
|
||||||
@ -37,7 +38,8 @@ function parseArgs(args) {
|
|||||||
options: {
|
options: {
|
||||||
noOverride,
|
noOverride,
|
||||||
useShell,
|
useShell,
|
||||||
verbose
|
verbose,
|
||||||
|
expandEnvs
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (verbose === true) {
|
if (verbose === true) {
|
||||||
@ -57,6 +59,7 @@ function parseArgsUsingCommander(args) {
|
|||||||
.option('--fallback', 'Fallback to default env file path, if custom env file path not found')
|
.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('--no-override', 'Do not override existing environment variables')
|
||||||
.option('--use-shell', 'Execute the command in a new shell with the given environment')
|
.option('--use-shell', 'Execute the command in a new shell with the given environment')
|
||||||
|
.option('-x, --expand-envs', 'Replace $var in args and command with environment variables')
|
||||||
.option('--verbose', 'Print helpful debugging information')
|
.option('--verbose', 'Print helpful debugging information')
|
||||||
.parse(['_', '_', ...args]);
|
.parse(['_', '_', ...args]);
|
||||||
}
|
}
|
||||||
|
|||||||
1
dist/types.d.ts
vendored
1
dist/types.d.ts
vendored
@ -16,5 +16,6 @@ export interface EnvCmdOptions extends GetEnvVarOptions {
|
|||||||
noOverride?: boolean;
|
noOverride?: boolean;
|
||||||
useShell?: boolean;
|
useShell?: boolean;
|
||||||
verbose?: boolean;
|
verbose?: boolean;
|
||||||
|
expandEnvs?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { EnvCmdOptions } from './types'
|
|||||||
import { TermSignals } from './signal-termination'
|
import { TermSignals } from './signal-termination'
|
||||||
import { parseArgs } from './parse-args'
|
import { parseArgs } from './parse-args'
|
||||||
import { getEnvVars } from './get-env-vars'
|
import { getEnvVars } from './get-env-vars'
|
||||||
|
import { expandEnvs } from './expand-envs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes env - cmd using command line arguments
|
* Executes env - cmd using command line arguments
|
||||||
@ -43,6 +44,11 @@ export async function EnvCmd (
|
|||||||
env = Object.assign({}, process.env, env)
|
env = Object.assign({}, process.env, env)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.expandEnvs === true) {
|
||||||
|
command = expandEnvs(command, env)
|
||||||
|
commandArgs = commandArgs.map(arg => expandEnvs(arg, env))
|
||||||
|
}
|
||||||
|
|
||||||
// Execute the command with the given environment variables
|
// Execute the command with the given environment variables
|
||||||
const proc = spawn(command, commandArgs, {
|
const proc = spawn(command, commandArgs, {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
|
|||||||
11
src/expand-envs.ts
Normal file
11
src/expand-envs.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* expandEnvs Replaces $var in args and command with environment variables
|
||||||
|
* the environment variable doesn't exist, it leaves it as is.
|
||||||
|
*/
|
||||||
|
export function expandEnvs (str: string, envs: { [key: string]: any }): string {
|
||||||
|
return str.replace(/(?<!\\)\$[a-zA-Z0-9_]+/g, varName => {
|
||||||
|
const varValue = envs[varName.slice(1)]
|
||||||
|
return varValue === undefined ? varName : varValue
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -15,6 +15,7 @@ export function parseArgs (args: string[]): EnvCmdOptions {
|
|||||||
program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)))
|
program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)))
|
||||||
const noOverride = !(program.override as boolean)
|
const noOverride = !(program.override as boolean)
|
||||||
const useShell = !!(program.useShell as boolean)
|
const useShell = !!(program.useShell as boolean)
|
||||||
|
const expandEnvs = !!(program.expandEnvs as boolean)
|
||||||
const verbose = !!(program.verbose as boolean)
|
const verbose = !!(program.verbose as boolean)
|
||||||
|
|
||||||
let rc: any
|
let rc: any
|
||||||
@ -41,7 +42,8 @@ export function parseArgs (args: string[]): EnvCmdOptions {
|
|||||||
options: {
|
options: {
|
||||||
noOverride,
|
noOverride,
|
||||||
useShell,
|
useShell,
|
||||||
verbose
|
verbose,
|
||||||
|
expandEnvs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (verbose === true) {
|
if (verbose === true) {
|
||||||
@ -61,6 +63,7 @@ export function parseArgsUsingCommander (args: string[]): Command {
|
|||||||
.option('--fallback', 'Fallback to default env file path, if custom env file path not found')
|
.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('--no-override', 'Do not override existing environment variables')
|
||||||
.option('--use-shell', 'Execute the command in a new shell with the given environment')
|
.option('--use-shell', 'Execute the command in a new shell with the given environment')
|
||||||
|
.option('-x, --expand-envs', 'Replace $var in args and command with environment variables')
|
||||||
.option('--verbose', 'Print helpful debugging information')
|
.option('--verbose', 'Print helpful debugging information')
|
||||||
.parse(['_', '_', ...args])
|
.parse(['_', '_', ...args])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,5 +17,6 @@ export interface EnvCmdOptions extends GetEnvVarOptions {
|
|||||||
noOverride?: boolean
|
noOverride?: boolean
|
||||||
useShell?: boolean
|
useShell?: boolean
|
||||||
verbose?: boolean
|
verbose?: boolean
|
||||||
|
expandEnvs?: boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import * as sinon from 'sinon'
|
|||||||
import { assert } from 'chai'
|
import { assert } from 'chai'
|
||||||
import * as parseArgsLib from '../src/parse-args'
|
import * as parseArgsLib from '../src/parse-args'
|
||||||
import * as getEnvVarsLib from '../src/get-env-vars'
|
import * as getEnvVarsLib from '../src/get-env-vars'
|
||||||
|
import * as expandEnvsLib from '../src/expand-envs'
|
||||||
import * as spawnLib from '../src/spawn'
|
import * as spawnLib from '../src/spawn'
|
||||||
import * as envCmdLib from '../src/env-cmd'
|
import * as envCmdLib from '../src/env-cmd'
|
||||||
|
|
||||||
@ -9,7 +10,6 @@ describe('CLI', (): void => {
|
|||||||
let parseArgsStub: sinon.SinonStub<any, any>
|
let parseArgsStub: sinon.SinonStub<any, any>
|
||||||
let envCmdStub: sinon.SinonStub<any, any>
|
let envCmdStub: sinon.SinonStub<any, any>
|
||||||
let processExitStub: sinon.SinonStub<any, any>
|
let processExitStub: sinon.SinonStub<any, any>
|
||||||
|
|
||||||
before((): void => {
|
before((): void => {
|
||||||
parseArgsStub = sinon.stub(parseArgsLib, 'parseArgs')
|
parseArgsStub = sinon.stub(parseArgsLib, 'parseArgs')
|
||||||
envCmdStub = sinon.stub(envCmdLib, 'EnvCmd')
|
envCmdStub = sinon.stub(envCmdLib, 'EnvCmd')
|
||||||
@ -47,10 +47,12 @@ describe('CLI', (): void => {
|
|||||||
describe('EnvCmd', (): void => {
|
describe('EnvCmd', (): void => {
|
||||||
let getEnvVarsStub: sinon.SinonStub<any, any>
|
let getEnvVarsStub: sinon.SinonStub<any, any>
|
||||||
let spawnStub: sinon.SinonStub<any, any>
|
let spawnStub: sinon.SinonStub<any, any>
|
||||||
|
let expandEnvsSpy: sinon.SinonSpy<any, any>
|
||||||
before((): void => {
|
before((): void => {
|
||||||
getEnvVarsStub = sinon.stub(getEnvVarsLib, 'getEnvVars')
|
getEnvVarsStub = sinon.stub(getEnvVarsLib, 'getEnvVars')
|
||||||
spawnStub = sinon.stub(spawnLib, 'spawn')
|
spawnStub = sinon.stub(spawnLib, 'spawn')
|
||||||
spawnStub.returns({ on: (): void => {}, kill: (): void => {} })
|
spawnStub.returns({ on: (): void => {}, kill: (): void => {} })
|
||||||
|
expandEnvsSpy = sinon.spy(expandEnvsLib, 'expandEnvs')
|
||||||
})
|
})
|
||||||
|
|
||||||
after((): void => {
|
after((): void => {
|
||||||
@ -150,4 +152,34 @@ describe('EnvCmd', (): void => {
|
|||||||
assert.equal(spawnStub.args[0][2].shell, true)
|
assert.equal(spawnStub.args[0][2].shell, true)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it('should should spawn process with command and args expanded if expandEnvs option is true',
|
||||||
|
async (): Promise<void> => {
|
||||||
|
getEnvVarsStub.returns({ PING: 'PONG', CMD: 'node' })
|
||||||
|
await envCmdLib.EnvCmd({
|
||||||
|
command: '$CMD',
|
||||||
|
commandArgs: ['$PING', '\\$IP'],
|
||||||
|
envFile: {
|
||||||
|
filePath: './.env',
|
||||||
|
fallback: true
|
||||||
|
},
|
||||||
|
rc: {
|
||||||
|
environments: ['dev'],
|
||||||
|
filePath: './.rc'
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
expandEnvs: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const spawnArgs = spawnStub.args[0]
|
||||||
|
|
||||||
|
assert.equal(getEnvVarsStub.callCount, 1, 'getEnvVars must be called once')
|
||||||
|
assert.equal(spawnStub.callCount, 1)
|
||||||
|
assert.equal(expandEnvsSpy.callCount, 3, 'command + number of args')
|
||||||
|
assert.equal(spawnArgs[0], 'node')
|
||||||
|
assert.sameOrderedMembers(spawnArgs[1], ['PONG', '\\$IP'])
|
||||||
|
assert.equal(spawnArgs[2].env.PING, 'PONG')
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
19
test/expand-envs.spec.ts
Normal file
19
test/expand-envs.spec.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/* eslint @typescript-eslint/no-non-null-assertion: 0 */
|
||||||
|
import { assert } from 'chai'
|
||||||
|
import { expandEnvs } from '../src/expand-envs'
|
||||||
|
|
||||||
|
describe('expandEnvs', (): void => {
|
||||||
|
const envs = {
|
||||||
|
notvar: 'this is not used',
|
||||||
|
dollar: 'money',
|
||||||
|
PING: 'PONG',
|
||||||
|
IP1: '127.0.0.1'
|
||||||
|
}
|
||||||
|
const args = ['notvar', '$dollar', '\\$notvar', '-4', '$PING', '$IP1', '\\$IP1', '$NONEXIST']
|
||||||
|
const argsExpanded = ['notvar', 'money', '\\$notvar', '-4', 'PONG', '127.0.0.1', '\\$IP1', '$NONEXIST']
|
||||||
|
|
||||||
|
it('should replace environment variables in args', (): void => {
|
||||||
|
const res = args.map(arg => expandEnvs(arg, envs))
|
||||||
|
assert.sameOrderedMembers(res, argsExpanded)
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user