mirror of
https://github.com/toddbluhm/env-cmd.git
synced 2025-12-08 18:23:33 +00:00
Merge pull request #410 from toddbluhm/feat-recursive-var-expansion
feat: Add --recursive flag to enable env-var nesting
This commit is contained in:
commit
3db3c8f005
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,15 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## 10.1.1 - In Development
|
||||
## Landed in master
|
||||
|
||||
- **Upgrade**: Upgraded dependency `commander` to `5.x`
|
||||
- **Upgrade**: Upgraded devDependencies `ts-standard`, `sinon`
|
||||
- **Upgrade**: Upgraded dependency `commander` to `13.x`
|
||||
- **Upgrade**: Upgraded dependency `cross-spawn` to `7.x`
|
||||
- **Upgrade**: Upgraded all 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
|
||||
|
||||
- **Feature**: Added support for expanding vars using the `-x` flag.
|
||||
Note: only supports `$var` syntax
|
||||
- **Feature**: Added support for `--silent` flag that ignores env-cmd errors and missing files and
|
||||
- **Feature**: Added support for `--silent` flag that ignores env-cmd errors and missing files and
|
||||
only terminates on caught signals
|
||||
- **Feature**: Added a new `--verbose` flag that prints additional debugging info to `console.info`
|
||||
- **Upgrade**: Upgraded dependency `commander` to `4.x`
|
||||
|
||||
13
README.md
13
README.md
@ -59,15 +59,16 @@ Usage: env-cmd [options] -- <command> [...args]
|
||||
Options:
|
||||
-v, --version output the version number
|
||||
-e, --environments [envs...] The rc file environment(s) to use
|
||||
-f, --file [path] Custom env file path or .rc file path if '-e' used (default path: ./.env or
|
||||
./.env-cmdrc.(js|cjs|mjs|json))
|
||||
-x, --expand-envs Replace $var in args and command with environment variables
|
||||
-f, --file [path] Custom env file path or .rc file path if '-e' used (default path: ./.env or ./.env-cmdrc.(js|cjs|mjs|json))
|
||||
-x, --expand-envs Replace $var and ${var} in args and command with environment variables
|
||||
--recursive Replace $var and ${var} in env file with the referenced environment variable
|
||||
--fallback Fallback to default env file path, if custom env file path not found
|
||||
--no-override Do not override existing environment variables
|
||||
--silent Ignore any env-cmd errors and only fail on executed program failure.
|
||||
--use-shell Execute the command in a new shell with the given environment
|
||||
--verbose Print helpful debugging information
|
||||
-h, --help display help for command
|
||||
|
||||
```
|
||||
|
||||
## 🔬 Advanced Usage
|
||||
@ -129,14 +130,14 @@ commands together that share the same environment variables.
|
||||
```
|
||||
|
||||
### Asynchronous env file support
|
||||
|
||||
|
||||
EnvCmd supports reading from asynchronous `.env` files. Instead of using a `.env` file, pass in a `.js`
|
||||
file that exports either an object or a `Promise` resolving to an object (`{ ENV_VAR_NAME: value, ... }`). Asynchronous `.rc`
|
||||
files are also supported using `.js` file extension and resolving to an object with top level environment
|
||||
names (`{ production: { ENV_VAR_NAME: value, ... } }`).
|
||||
|
||||
|
||||
**Terminal**
|
||||
|
||||
|
||||
```sh
|
||||
./node_modules/.bin/env-cmd -f ./async-file.js -- node index.js
|
||||
```
|
||||
|
||||
7
dist/env-cmd.js
vendored
7
dist/env-cmd.js
vendored
@ -29,6 +29,13 @@ 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)) {
|
||||
if (env[key] !== undefined) {
|
||||
env[key] = expandEnvs(env[key], env);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.expandEnvs === true) {
|
||||
command = expandEnvs(command, env);
|
||||
commandArgs = commandArgs.map(arg => expandEnvs(arg, env));
|
||||
|
||||
2
dist/expand-envs.d.ts
vendored
2
dist/expand-envs.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
import type { Environment } from './types.ts';
|
||||
/**
|
||||
* expandEnvs Replaces $var in args and command with environment variables
|
||||
* expandEnvs Replaces $var and ${var} in args and command with environment variables
|
||||
* if the environment variable doesn't exist, it leaves it as is.
|
||||
*/
|
||||
export declare function expandEnvs(str: string, envs: Environment): string;
|
||||
|
||||
7
dist/expand-envs.js
vendored
7
dist/expand-envs.js
vendored
@ -1,11 +1,10 @@
|
||||
/**
|
||||
* expandEnvs Replaces $var in args and command with environment variables
|
||||
* expandEnvs Replaces $var and ${var} in args and command with environment variables
|
||||
* if the environment variable doesn't exist, it leaves it as is.
|
||||
*/
|
||||
export function expandEnvs(str, envs) {
|
||||
return str.replace(/(?<!\\)\$[a-zA-Z0-9_]+/g, (varName) => {
|
||||
const varValue = envs[varName.slice(1)];
|
||||
// const test = 42;
|
||||
return str.replace(/(?<!\\)\$(\{\w+\}|\w+)?/g, (varName) => {
|
||||
const varValue = envs[varName.startsWith('${') ? varName.slice(2, varName.length - 1) : varName.slice(1)];
|
||||
return varValue ?? varName;
|
||||
});
|
||||
}
|
||||
|
||||
8
dist/parse-args.js
vendored
8
dist/parse-args.js
vendored
@ -28,6 +28,10 @@ export function parseArgs(args) {
|
||||
if (parsedCmdOptions.expandEnvs === true) {
|
||||
expandEnvs = true;
|
||||
}
|
||||
let recursive = false;
|
||||
if (parsedCmdOptions.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,7 +88,8 @@ 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('--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.')
|
||||
|
||||
2
dist/types.d.ts
vendored
2
dist/types.d.ts
vendored
@ -4,6 +4,7 @@ export type RCEnvironment = Partial<Record<string, Environment>>;
|
||||
export type CommanderOptions = Command<[], {
|
||||
environments?: true | string[];
|
||||
expandEnvs?: boolean;
|
||||
recursive?: boolean;
|
||||
fallback?: boolean;
|
||||
file?: true | string;
|
||||
override?: boolean;
|
||||
@ -29,6 +30,7 @@ export interface EnvCmdOptions extends GetEnvVarOptions {
|
||||
commandArgs: string[];
|
||||
options?: {
|
||||
expandEnvs?: boolean;
|
||||
recursive?: boolean;
|
||||
noOverride?: boolean;
|
||||
silent?: boolean;
|
||||
useShell?: boolean;
|
||||
|
||||
@ -38,11 +38,12 @@
|
||||
],
|
||||
"author": "Todd Bluhm",
|
||||
"contributors": [
|
||||
"Anton Versal <ant.ver@gmail.com>",
|
||||
"Eric Lanehart <eric@pushred.co>",
|
||||
"Jon Scheiding <jonscheiding@gmail.com>",
|
||||
"serapath (Alexander Praetorius) <dev@serapath.de>",
|
||||
"Kyle Hensel <me@kyle.kiwi>",
|
||||
"Anton Versal <ant.ver@gmail.com>"
|
||||
"Nicholas Krul <nicholas.krul@gmail.com>",
|
||||
"serapath (Alexander Praetorius) <dev@serapath.de>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
||||
@ -40,6 +40,14 @@ export async function EnvCmd(
|
||||
env = Object.assign({}, processLib.env, env)
|
||||
}
|
||||
|
||||
if (options.recursive === true) {
|
||||
for (const key of Object.keys(env)) {
|
||||
if (env[key] !== undefined) {
|
||||
env[key] = expandEnvs(env[key], env)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.expandEnvs === true) {
|
||||
command = expandEnvs(command, env)
|
||||
commandArgs = commandArgs.map(arg => expandEnvs(arg, env))
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import type { Environment } from './types.ts'
|
||||
|
||||
/**
|
||||
* expandEnvs Replaces $var in args and command with environment variables
|
||||
* expandEnvs Replaces $var and ${var} in args and command with environment variables
|
||||
* if the environment variable doesn't exist, it leaves it as is.
|
||||
*/
|
||||
export function expandEnvs(str: string, envs: Environment): string {
|
||||
return str.replace(/(?<!\\)\$[a-zA-Z0-9_]+/g, (varName) => {
|
||||
const varValue = envs[varName.slice(1)]
|
||||
// const test = 42;
|
||||
export function expandEnvs (str: string, envs: Environment): string {
|
||||
return str.replace(/(?<!\\)\$(\{\w+\}|\w+)?/g, (varName) => {
|
||||
const varValue = envs[varName.startsWith('${') ? varName.slice(2, varName.length - 1) : varName.slice(1)]
|
||||
return varValue ?? varName
|
||||
})
|
||||
}
|
||||
|
||||
@ -33,6 +33,10 @@ export function parseArgs(args: string[]): EnvCmdOptions {
|
||||
if (parsedCmdOptions.expandEnvs === true) {
|
||||
expandEnvs = true
|
||||
}
|
||||
let recursive = false
|
||||
if (parsedCmdOptions.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,
|
||||
@ -95,7 +100,8 @@ export function parseArgsUsingCommander(args: string[]): CommanderOptions {
|
||||
.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('--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.')
|
||||
|
||||
@ -8,6 +8,7 @@ export type RCEnvironment = Partial<Record<string, Environment>>
|
||||
export type CommanderOptions = Command<[], {
|
||||
environments?: true | string[]
|
||||
expandEnvs?: boolean // Default: false
|
||||
recursive?: boolean // Default: false
|
||||
fallback?: boolean // Default false
|
||||
file?: true | string
|
||||
override?: boolean // Default: false
|
||||
@ -37,6 +38,7 @@ export interface EnvCmdOptions extends GetEnvVarOptions {
|
||||
commandArgs: string[]
|
||||
options?: {
|
||||
expandEnvs?: boolean
|
||||
recursive?: boolean
|
||||
noOverride?: boolean
|
||||
silent?: boolean
|
||||
useShell?: boolean
|
||||
|
||||
@ -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}' })
|
||||
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
|
||||
|
||||
@ -8,11 +8,22 @@ describe('expandEnvs', (): void => {
|
||||
dollar: 'money',
|
||||
PING: 'PONG',
|
||||
IP1: '127.0.0.1',
|
||||
THANKSFORALLTHEFISH: 42,
|
||||
BRINGATOWEL: true,
|
||||
THANKSFORALLTHEFISH: '42',
|
||||
BRINGATOWEL: 'true',
|
||||
}
|
||||
const args = ['notvar', '$dollar', '\\$notvar', '-4', '$PING', '$IP1', '\\$IP1', '$NONEXIST']
|
||||
const argsExpanded = ['notvar', 'money', '\\$notvar', '-4', 'PONG', '127.0.0.1', '\\$IP1', '$NONEXIST']
|
||||
|
||||
const args = [
|
||||
'notvar', '$dollar', '\\$notvar', '-4',
|
||||
'$PING', '$IP1', '\\$IP1', '$NONEXIST',
|
||||
'${PING}', '${NONEXIST}', '\\${PING}',
|
||||
'$PING}', '${PING2'
|
||||
]
|
||||
const argsExpanded = [
|
||||
'notvar', 'money', '\\$notvar', '-4',
|
||||
'PONG', '127.0.0.1', '\\$IP1', '$NONEXIST',
|
||||
'PONG', '${NONEXIST}', '\\${PING}',
|
||||
'PONG}', '${PING2'
|
||||
]
|
||||
|
||||
it('should replace environment variables in args', (): void => {
|
||||
const res = args.map(arg => expandEnvs(arg, envs))
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user