fix(commander)!: updated code for new version

This commit is contained in:
Todd Bluhm 2024-12-03 05:38:40 -09:00
parent 29c824674b
commit 9942f3c448
No known key found for this signature in database
GPG Key ID: 9CF312607477B8AB
8 changed files with 105 additions and 95 deletions

View File

@ -30,7 +30,7 @@ ENV3=THE FISH
```json
{
"scripts": {
"test": "env-cmd mocha -R spec"
"test": "env-cmd -- mocha -R spec"
}
}
```
@ -38,7 +38,7 @@ ENV3=THE FISH
**Terminal**
```sh
./node_modules/.bin/env-cmd node index.js
./node_modules/.bin/env-cmd -- node index.js
```
### Using custom env file path
@ -48,13 +48,13 @@ To use a custom env filename or path, pass the `-f` flag. This is a major breaki
**Terminal**
```sh
./node_modules/.bin/env-cmd -f ./custom/path/.env node index.js
./node_modules/.bin/env-cmd -f ./custom/path/.env -- node index.js
```
## 📜 Help
```text
Usage: _ [options] <command> [...args]
Usage: env-cmd [options] -- <command> [...args]
Options:
-v, --version output the version number
@ -101,10 +101,10 @@ are found.
**Terminal**
```sh
./node_modules/.bin/env-cmd -e production node index.js
./node_modules/.bin/env-cmd -e production -- node index.js
# Or for multiple environments (where `production` vars override `test` vars,
# but both are included)
./node_modules/.bin/env-cmd -e test,production node index.js
./node_modules/.bin/env-cmd -e test,production -- node index.js
```
### `--no-override` option
@ -125,7 +125,7 @@ commands together that share the same environment variables.
**Terminal**
```sh
./node_modules/.bin/env-cmd -f ./test/.env --use-shell "npm run lint && npm test"
./node_modules/.bin/env-cmd -f ./test/.env --use-shell -- "npm run lint && npm test"
```
### Asynchronous env file support
@ -138,7 +138,7 @@ commands together that share the same environment variables.
**Terminal**
```sh
./node_modules/.bin/env-cmd -f ./async-file.js node index.js
./node_modules/.bin/env-cmd -f ./async-file.js -- node index.js
```
### `-x` expands vars in arguments
@ -152,14 +152,14 @@ to provide arguments to a command that are based on environment variable values
```sh
# $VAR will be expanded into the env value it contains at runtime
./node_modules/.bin/env-cmd -x node index.js --arg=\$VAR
./node_modules/.bin/env-cmd -x -- node index.js --arg=\$VAR
```
or in `package.json` (use `\\` to insert a literal backslash)
```json
{
"script": {
"start": "env-cmd -x node index.js --arg=\\$VAR"
"start": "env-cmd -x -- node index.js --arg=\\$VAR"
}
}
```
@ -252,11 +252,6 @@ usually just easier to have a file with all the vars in them, especially for dev
[`cross-env`](https://github.com/kentcdodds/cross-env) - Cross platform setting of environment scripts
## 🎊 Special Thanks
Special thanks to [`cross-env`](https://github.com/kentcdodds/cross-env) for inspiration (uses the
same `cross-spawn` lib underneath too).
## 📋 Contributing Guide
I welcome all pull requests. Please make sure you add appropriate test cases for any features

53
dist/parse-args.js vendored
View File

@ -1,4 +1,4 @@
import * as commander from 'commander';
import { Command } from '@commander-js/extra-typings';
import { parseArgList } from './utils.js';
import { default as packageJson } from '../package.json' with { type: 'json' };
/**
@ -7,48 +7,55 @@ import { default as packageJson } from '../package.json' with { type: 'json' };
export function parseArgs(args) {
// Run the initial arguments through commander in order to determine
// which value in the args array is the `command` to execute
let program = parseArgsUsingCommander(args);
const program = parseArgsUsingCommander(args);
const command = program.args[0];
// Grab all arguments after the `command` in the args array
const commandArgs = args.splice(args.indexOf(command) + 1);
// Reprocess the args with the command and command arguments removed
program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)));
// program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)))
const parsedCmdOptions = program.opts();
// Set values for provided options
let noOverride = false;
// In commander `no-` negates the original value `override`
if (program.override === false) {
if (parsedCmdOptions.override === false) {
noOverride = true;
}
let useShell = false;
if (program.useShell === true) {
if (parsedCmdOptions.useShell === true) {
useShell = true;
}
let expandEnvs = false;
if (program.expandEnvs === true) {
if (parsedCmdOptions.expandEnvs === true) {
expandEnvs = true;
}
let verbose = false;
if (program.verbose === true) {
if (parsedCmdOptions.verbose === true) {
verbose = true;
}
let silent = false;
if (program.silent === true) {
if (parsedCmdOptions.silent === true) {
silent = true;
}
let rc;
if (program.environments !== undefined
&& Array.isArray(program.environments)
&& program.environments.length !== 0) {
if (parsedCmdOptions.environments !== undefined
&& Array.isArray(parsedCmdOptions.environments)
&& parsedCmdOptions.environments.length !== 0) {
rc = {
environments: program.environments,
filePath: program.rcFile,
environments: parsedCmdOptions.environments,
// if we get a boolean value assume not defined
filePath: parsedCmdOptions.rcFile === true ?
undefined :
parsedCmdOptions.rcFile,
};
}
let envFile;
if (program.file !== undefined) {
if (parsedCmdOptions.file !== undefined) {
envFile = {
filePath: program.file,
fallback: program.fallback,
// if we get a boolean value assume not defined
filePath: parsedCmdOptions.file === true ?
undefined :
parsedCmdOptions.file,
fallback: parsedCmdOptions.fallback,
};
}
const options = {
@ -70,19 +77,19 @@ export function parseArgs(args) {
return options;
}
export function parseArgsUsingCommander(args) {
const program = new commander.Command();
return program
return new Command('env-cmd')
.description('CLI for executing commands using an environment from an env file.')
.version(packageJson.version, '-v, --version')
.usage('[options] <command> [...args]')
.option('-e, --environments [env1,env2,...]', 'The rc file environment(s) to use', parseArgList)
.usage('[options] -- <command> [...args]')
.option('-e, --environments [envs...]', 'The rc file environment(s) to use', parseArgList)
.option('-f, --file [path]', 'Custom env file path (default path: ./.env)')
.option('-r, --rc-file [path]', 'Custom rc file path (default path: ./.env-cmdrc.(js|cjs|mjs|json)')
.option('-x, --expand-envs', 'Replace $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('-r, --rc-file [path]', 'Custom rc file path (default path: ./.env-cmdrc(|.js|.json)')
.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('-x, --expand-envs', 'Replace $var in args and command with environment variables')
.allowUnknownOption(true)
.parse(['_', '_', ...args]);
.parse(['_', '_', ...args], { from: 'node' });
}

20
dist/types.d.ts vendored
View File

@ -1,17 +1,17 @@
import { Command } from 'commander';
import type { Command } from '@commander-js/extra-typings';
export type Environment = Partial<Record<string, string | number | boolean>>;
export type RCEnvironment = Partial<Record<string, Environment>>;
export interface CommanderOptions extends Command {
override?: boolean;
useShell?: boolean;
export type CommanderOptions = Command<[], {
environments?: true | string[];
expandEnvs?: boolean;
verbose?: boolean;
silent?: boolean;
fallback?: boolean;
environments?: string[];
rcFile?: string;
file?: string;
}
file?: true | string;
override?: boolean;
rcFile?: true | string;
silent?: boolean;
useShell?: boolean;
verbose?: boolean;
}>;
export interface RCFileOptions {
environments: string[];
filePath?: string;

View File

@ -49,6 +49,7 @@
},
"homepage": "https://github.com/toddbluhm/env-cmd#readme",
"dependencies": {
"@commander-js/extra-typings": "^12.1.0",
"commander": "^12.1.0",
"cross-spawn": "^7.0.6"
},

View File

@ -1,4 +1,4 @@
import * as commander from 'commander'
import { Command } from '@commander-js/extra-typings'
import type { EnvCmdOptions, CommanderOptions, EnvFileOptions, RCFileOptions } from './types.ts'
import { parseArgList } from './utils.js'
import { default as packageJson } from '../package.json' with { type: 'json' };
@ -9,54 +9,62 @@ import { default as packageJson } from '../package.json' with { type: 'json' };
export function parseArgs(args: string[]): EnvCmdOptions {
// Run the initial arguments through commander in order to determine
// which value in the args array is the `command` to execute
let program = parseArgsUsingCommander(args)
const program = parseArgsUsingCommander(args)
const command = program.args[0]
// Grab all arguments after the `command` in the args array
const commandArgs = args.splice(args.indexOf(command) + 1)
// Reprocess the args with the command and command arguments removed
program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)))
// program = parseArgsUsingCommander(args.slice(0, args.indexOf(command)))
const parsedCmdOptions = program.opts()
// Set values for provided options
let noOverride = false
// In commander `no-` negates the original value `override`
if (program.override === false) {
if (parsedCmdOptions.override === false) {
noOverride = true
}
let useShell = false
if (program.useShell === true) {
if (parsedCmdOptions.useShell === true) {
useShell = true
}
let expandEnvs = false
if (program.expandEnvs === true) {
if (parsedCmdOptions.expandEnvs === true) {
expandEnvs = true
}
let verbose = false
if (program.verbose === true) {
if (parsedCmdOptions.verbose === true) {
verbose = true
}
let silent = false
if (program.silent === true) {
if (parsedCmdOptions.silent === true) {
silent = true
}
let rc: RCFileOptions | undefined
if (
program.environments !== undefined
&& Array.isArray(program.environments)
&& program.environments.length !== 0
parsedCmdOptions.environments !== undefined
&& Array.isArray(parsedCmdOptions.environments)
&& parsedCmdOptions.environments.length !== 0
) {
rc = {
environments: program.environments,
filePath: program.rcFile,
environments: parsedCmdOptions.environments,
// if we get a boolean value assume not defined
filePath: parsedCmdOptions.rcFile === true ?
undefined :
parsedCmdOptions.rcFile,
}
}
let envFile: EnvFileOptions | undefined
if (program.file !== undefined) {
if (parsedCmdOptions.file !== undefined) {
envFile = {
filePath: program.file,
fallback: program.fallback,
// if we get a boolean value assume not defined
filePath: parsedCmdOptions.file === true ?
undefined :
parsedCmdOptions.file,
fallback: parsedCmdOptions.fallback,
}
}
@ -80,19 +88,19 @@ export function parseArgs(args: string[]): EnvCmdOptions {
}
export function parseArgsUsingCommander(args: string[]): CommanderOptions {
const program = new commander.Command() as CommanderOptions
return program
return new Command('env-cmd')
.description('CLI for executing commands using an environment from an env file.')
.version(packageJson.version, '-v, --version')
.usage('[options] <command> [...args]')
.option('-e, --environments [env1,env2,...]', 'The rc file environment(s) to use', parseArgList)
.usage('[options] -- <command> [...args]')
.option('-e, --environments [envs...]', 'The rc file environment(s) to use', parseArgList)
.option('-f, --file [path]', 'Custom env file path (default path: ./.env)')
.option('-r, --rc-file [path]', 'Custom rc file path (default path: ./.env-cmdrc.(js|cjs|mjs|json)')
.option('-x, --expand-envs', 'Replace $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('-r, --rc-file [path]', 'Custom rc file path (default path: ./.env-cmdrc(|.js|.json)')
.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('-x, --expand-envs', 'Replace $var in args and command with environment variables')
.allowUnknownOption(true)
.parse(['_', '_', ...args])
.parse(['_', '_', ...args], { from: 'node' })
}

View File

@ -1,21 +1,21 @@
import { Command } from 'commander'
import type { Command } from '@commander-js/extra-typings'
// Define an export type
export type Environment = Partial<Record<string, string | number | boolean>>
export type RCEnvironment = Partial<Record<string, Environment>>
export interface CommanderOptions extends Command {
override?: boolean // Default: false
useShell?: boolean // Default: false
export type CommanderOptions = Command<[], {
environments?: true | string[]
expandEnvs?: boolean // Default: false
verbose?: boolean // Default: false
silent?: boolean // Default: false
fallback?: boolean // Default false
environments?: string[]
rcFile?: string
file?: string
}
file?: true | string
override?: boolean // Default: false
rcFile?: true | string
silent?: boolean // Default: false
useShell?: boolean // Default: false
verbose?: boolean // Default: false
}>
export interface RCFileOptions {
environments: string[]

View File

@ -25,31 +25,31 @@ describe('parseArgs', (): void => {
})
it('should parse environment value', (): void => {
const res = parseArgs(['-e', environments[0], command])
const res = parseArgs(['-e', environments[0], '--', command])
assert.exists(res.rc)
assert.sameOrderedMembers(res.rc.environments, [environments[0]])
})
it('should parse multiple environment values', (): void => {
const res = parseArgs(['-e', environments.join(','), command])
const res = parseArgs(['-e', environments.join(','), '--', command])
assert.exists(res.rc)
assert.sameOrderedMembers(res.rc.environments, environments)
})
it('should parse command value', (): void => {
const res = parseArgs(['-e', environments[0], command])
const res = parseArgs(['-e', environments[0], '--', command])
assert.equal(res.command, command)
})
it('should parse multiple command arguments', (): void => {
const res = parseArgs(['-e', environments[0], command, ...commandArgs])
const res = parseArgs(['-e', environments[0], '--', command, ...commandArgs])
assert.sameOrderedMembers(res.commandArgs, commandArgs)
})
it('should parse multiple command arguments even if they use the same options flags as env-cmd',
(): void => {
const commandFlags = ['-f', './other-file', '--use-shell', '-r']
const res = parseArgs(['-e', environments[0], command, ...commandFlags])
const res = parseArgs(['-e', environments[0], '--', command, ...commandFlags])
assert.sameOrderedMembers(res.commandArgs, commandFlags)
assert.notOk(res.options!.useShell)
assert.notOk(res.envFile)
@ -57,49 +57,49 @@ describe('parseArgs', (): void => {
)
it('should parse override option', (): void => {
const res = parseArgs(['-e', environments[0], '--no-override', command, ...commandArgs])
const res = parseArgs(['-e', environments[0], '--no-override', '--', command, ...commandArgs])
assert.exists(res.options)
assert.isTrue(res.options.noOverride)
})
it('should parse use shell option', (): void => {
const res = parseArgs(['-e', environments[0], '--use-shell', command, ...commandArgs])
const res = parseArgs(['-e', environments[0], '--use-shell', '--', command, ...commandArgs])
assert.exists(res.options)
assert.isTrue(res.options.useShell)
})
it('should parse rc file path', (): void => {
const res = parseArgs(['-e', environments[0], '-r', rcFilePath, command, ...commandArgs])
const res = parseArgs(['-e', environments[0], '-r', rcFilePath, '--', command, ...commandArgs])
assert.exists(res.rc)
assert.equal(res.rc.filePath, rcFilePath)
})
it('should parse env file path', (): void => {
const res = parseArgs(['-f', envFilePath, command, ...commandArgs])
const res = parseArgs(['-f', envFilePath, '--', command, ...commandArgs])
assert.exists(res.envFile)
assert.equal(res.envFile.filePath, envFilePath)
})
it('should parse fallback option', (): void => {
const res = parseArgs(['-f', envFilePath, '--fallback', command, ...commandArgs])
const res = parseArgs(['-f', envFilePath, '--fallback', '--', command, ...commandArgs])
assert.exists(res.envFile)
assert.isTrue(res.envFile.fallback)
})
it('should print to console.info if --verbose flag is passed', (): void => {
const res = parseArgs(['-f', envFilePath, '--verbose', command, ...commandArgs])
const res = parseArgs(['-f', envFilePath, '--verbose', '--', command, ...commandArgs])
assert.exists(res.options!.verbose)
assert.equal(logInfoStub.callCount, 1)
})
it('should parse expandEnvs option', (): void => {
const res = parseArgs(['-f', envFilePath, '-x', command, ...commandArgs])
const res = parseArgs(['-f', envFilePath, '-x', '--', command, ...commandArgs])
assert.exists(res.envFile)
assert.isTrue(res.options!.expandEnvs)
})
it('should parse silent option', (): void => {
const res = parseArgs(['-f', envFilePath, '--silent', command, ...commandArgs])
const res = parseArgs(['-f', envFilePath, '--silent', '--', command, ...commandArgs])
assert.exists(res.envFile)
assert.isTrue(res.options!.silent)
})

View File

@ -1,6 +1,5 @@
module.exports = new Promise((resolve) => {
setTimeout(() => {
console.log('resolved')
resolve({
development: {
THANKS: 'FOR ALL THE FISH',