diff --git a/README.md b/README.md index 5db9714..21ebfb7 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ Options: -f, --file [path] Custom env file path (default path: ./.env) -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 + -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 --no-override Do not override existing environment variables --use-shell Execute the command in a new shell with the given environment diff --git a/dist/env-cmd.js b/dist/env-cmd.js index ea5162d..de93308 100644 --- a/dist/env-cmd.js +++ b/dist/env-cmd.js @@ -4,6 +4,7 @@ const spawn_1 = require("./spawn"); const signal_termination_1 = require("./signal-termination"); const parse_args_1 = require("./parse-args"); const get_env_vars_1 = require("./get-env-vars"); +const expand_envs_1 = require("./expand-envs"); /** * Executes env - cmd using command line arguments * @export @@ -41,6 +42,10 @@ async function EnvCmd({ command, commandArgs, envFile, rc, options = {} }) { // Add in the system environment variables to our environment list 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 const proc = spawn_1.spawn(command, commandArgs, { stdio: 'inherit', diff --git a/dist/expand-envs.d.ts b/dist/expand-envs.d.ts new file mode 100644 index 0000000..cd06ffa --- /dev/null +++ b/dist/expand-envs.d.ts @@ -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; diff --git a/dist/expand-envs.js b/dist/expand-envs.js new file mode 100644 index 0000000..b46324a --- /dev/null +++ b/dist/expand-envs.js @@ -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(/(? { + const varValue = envs[varName.slice(1)]; + return varValue === undefined ? varName : varValue; + }); +} +exports.expandEnvs = expandEnvs; diff --git a/dist/parse-args.js b/dist/parse-args.js index ba3630a..6bdc484 100644 --- a/dist/parse-args.js +++ b/dist/parse-args.js @@ -14,6 +14,7 @@ function parseArgs(args) { program = parseArgsUsingCommander(args.slice(0, args.indexOf(command))); const noOverride = !program.override; const useShell = !!program.useShell; + const expandEnvs = !!program.expandEnvs; const verbose = !!program.verbose; let rc; if (program.environments !== undefined && program.environments.length !== 0) { @@ -37,7 +38,8 @@ function parseArgs(args) { options: { noOverride, useShell, - verbose + verbose, + expandEnvs } }; 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('--no-override', 'Do not override existing environment variables') .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') .parse(['_', '_', ...args]); } diff --git a/dist/types.d.ts b/dist/types.d.ts index 5bc5646..a1ded91 100644 --- a/dist/types.d.ts +++ b/dist/types.d.ts @@ -16,5 +16,6 @@ export interface EnvCmdOptions extends GetEnvVarOptions { noOverride?: boolean; useShell?: boolean; verbose?: boolean; + expandEnvs?: boolean; }; } diff --git a/src/env-cmd.ts b/src/env-cmd.ts index 4f4b83f..513fdda 100644 --- a/src/env-cmd.ts +++ b/src/env-cmd.ts @@ -3,6 +3,7 @@ import { EnvCmdOptions } from './types' import { TermSignals } from './signal-termination' import { parseArgs } from './parse-args' import { getEnvVars } from './get-env-vars' +import { expandEnvs } from './expand-envs' /** * Executes env - cmd using command line arguments @@ -43,6 +44,11 @@ export async function EnvCmd ( 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 const proc = spawn(command, commandArgs, { stdio: 'inherit', diff --git a/src/expand-envs.ts b/src/expand-envs.ts new file mode 100644 index 0000000..cd77317 --- /dev/null +++ b/src/expand-envs.ts @@ -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(/(? { + const varValue = envs[varName.slice(1)] + return varValue === undefined ? varName : varValue + }) +} diff --git a/src/parse-args.ts b/src/parse-args.ts index 166dfbf..24d3189 100644 --- a/src/parse-args.ts +++ b/src/parse-args.ts @@ -15,6 +15,7 @@ export function parseArgs (args: string[]): EnvCmdOptions { program = parseArgsUsingCommander(args.slice(0, args.indexOf(command))) const noOverride = !(program.override as boolean) const useShell = !!(program.useShell as boolean) + const expandEnvs = !!(program.expandEnvs as boolean) const verbose = !!(program.verbose as boolean) let rc: any @@ -41,7 +42,8 @@ export function parseArgs (args: string[]): EnvCmdOptions { options: { noOverride, useShell, - verbose + verbose, + expandEnvs } } 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('--no-override', 'Do not override existing environment variables') .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') .parse(['_', '_', ...args]) } diff --git a/src/types.ts b/src/types.ts index f223490..4c8be42 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,5 +17,6 @@ export interface EnvCmdOptions extends GetEnvVarOptions { noOverride?: boolean useShell?: boolean verbose?: boolean + expandEnvs?: boolean } } diff --git a/test/env-cmd.spec.ts b/test/env-cmd.spec.ts index 9a463c4..a588e5d 100644 --- a/test/env-cmd.spec.ts +++ b/test/env-cmd.spec.ts @@ -2,6 +2,7 @@ import * as sinon from 'sinon' import { assert } from 'chai' import * as parseArgsLib from '../src/parse-args' import * as getEnvVarsLib from '../src/get-env-vars' +import * as expandEnvsLib from '../src/expand-envs' import * as spawnLib from '../src/spawn' import * as envCmdLib from '../src/env-cmd' @@ -9,7 +10,6 @@ describe('CLI', (): void => { let parseArgsStub: sinon.SinonStub let envCmdStub: sinon.SinonStub let processExitStub: sinon.SinonStub - before((): void => { parseArgsStub = sinon.stub(parseArgsLib, 'parseArgs') envCmdStub = sinon.stub(envCmdLib, 'EnvCmd') @@ -47,10 +47,12 @@ describe('CLI', (): void => { describe('EnvCmd', (): void => { let getEnvVarsStub: sinon.SinonStub let spawnStub: sinon.SinonStub + let expandEnvsSpy: sinon.SinonSpy before((): void => { getEnvVarsStub = sinon.stub(getEnvVarsLib, 'getEnvVars') spawnStub = sinon.stub(spawnLib, 'spawn') spawnStub.returns({ on: (): void => {}, kill: (): void => {} }) + expandEnvsSpy = sinon.spy(expandEnvsLib, 'expandEnvs') }) after((): void => { @@ -150,4 +152,34 @@ describe('EnvCmd', (): void => { 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 => { + 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') + } + ) }) diff --git a/test/expand-envs.spec.ts b/test/expand-envs.spec.ts new file mode 100644 index 0000000..e998409 --- /dev/null +++ b/test/expand-envs.spec.ts @@ -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) + }) +})