Skip to content

Commit

Permalink
Merge branch 'develop' into feature/jsdoc-tidy
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowspawn committed Apr 7, 2024
2 parents b53c5d2 + a996782 commit dd2302d
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 61 deletions.
18 changes: 10 additions & 8 deletions Readme.md
Expand Up @@ -955,22 +955,24 @@ program.on('option:verbose', function () {

### .parse() and .parseAsync()

The first argument to `.parse` is the array of strings to parse. You may omit the parameter to implicitly use `process.argv`.
Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!

If the arguments follow different conventions than node you can pass a `from` option in the second parameter:
Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:

- 'node': default, `argv[0]` is the application and `argv[1]` is the script being run, with user parameters after that
- 'electron': `argv[1]` varies depending on whether the electron application is packaged
- 'user': all of the arguments from the user
- `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
- `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
- `'user'`: just user arguments

For example:

```js
program.parse(process.argv); // Explicit, node conventions
program.parse(); // Implicit, and auto-detect electron
program.parse(['-f', 'filename'], { from: 'user' });
program.parse(); // parse process.argv and auto-detect electron and special node flags
program.parse(process.argv); // assume argv[0] is app and argv[1] is script
program.parse(['--port', '80'], { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
```

Use parseAsync instead of parse if any of your action handlers are async.

If you want to parse multiple times, create a new program each time. Calling parse does not clear out any previous state.

### Parsing Configuration
Expand Down
35 changes: 13 additions & 22 deletions eslint.config.js
Expand Up @@ -5,43 +5,34 @@ const tseslint = require('typescript-eslint');
const prettier = require('eslint-config-prettier');
// const jsdoc = require('eslint-plugin-jsdoc');

// Using tseslint config helper to customise its setup the tseslint way.
// Only run tseslint on the files that we have included for TypeScript.
const tsconfigTsFiles = ['**/*.{ts,mts}']; // match "include" in tsconfig.ts.json;
const tsconfigJsFiles = ['*.{js,mjs}', 'lib/**/*.{js,mjs}']; // match "include" in tsconfig.js.json
const tseslintConfigs = tseslint.config(

// Using tseslint.config adds some type safety and `extends` to simplify customising config array.
module.exports = tseslint.config(
// Add recommended rules.
esLintjs.configs.recommended,
// jsdoc.configs['flat/recommended'],
jest.configs['flat/recommended'],
// tseslint with different setup for js/ts
{
files: tsconfigJsFiles,
extends: [...tseslint.configs.recommended],
languageOptions: {
parserOptions: { project: './tsconfig.js.json' },
},
extends: [...tseslint.configs.recommended],
rules: {
'@typescript-eslint/no-var-requires': 'off', // (tseslint does not autodetect commonjs context )
'@typescript-eslint/no-var-requires': 'off', // tseslint does not autodetect commonjs context
},
},
{
files: tsconfigTsFiles,
extends: [...tseslint.configs.recommended],
languageOptions: {
parserOptions: { project: './tsconfig.ts.json' },
},
extends: [...tseslint.configs.recommended],
},
);

module.exports = [
esLintjs.configs.recommended,
jest.configs['flat/recommended'],
...tseslintConfigs,
// {
// files: ['**/*.{js,mjs,cjs}'],
// // @ts-ignore because jsdoc not typed
// ...jsdoc.configs['flat/recommended'],
// },
// {
// files: ['**/*.{ts,mts,cts}'],
// // @ts-ignore because jsdoc not typed
// ...jsdoc.configs['flat/recommended-typescript'],
// },
prettier, // Do Prettier last so it can override previous configs.

// Customise rules.
Expand Down Expand Up @@ -85,4 +76,4 @@ module.exports = [
],
},
},
];
);
63 changes: 44 additions & 19 deletions lib/command.js
@@ -1,8 +1,8 @@
const EventEmitter = require('events').EventEmitter;
const childProcess = require('child_process');
const path = require('path');
const fs = require('fs');
const process = require('process');
const EventEmitter = require('node:events').EventEmitter;
const childProcess = require('node:child_process');
const path = require('node:path');
const fs = require('node:fs');
const process = require('node:process');

const { Argument, humanReadableArgName } = require('./argument.js');
const { CommanderError } = require('./error.js');
Expand Down Expand Up @@ -976,16 +976,30 @@ Expecting one of '${allowedValues.join("', '")}'`);
}
parseOptions = parseOptions || {};

// Default to using process.argv
if (argv === undefined) {
argv = process.argv;
if (process.versions && process.versions.electron) {
// auto-detect argument conventions if nothing supplied
if (argv === undefined && parseOptions.from === undefined) {
if (process.versions?.electron) {
parseOptions.from = 'electron';
}
// check node specific options for scenarios where user CLI args follow executable without scriptname
const execArgv = process.execArgv ?? [];
if (
execArgv.includes('-e') ||
execArgv.includes('--eval') ||
execArgv.includes('-p') ||
execArgv.includes('--print')
) {
parseOptions.from = 'eval'; // internal usage, not documented
}
}

// default to using process.argv
if (argv === undefined) {
argv = process.argv;
}
this.rawArgs = argv.slice();

// make it a little easier for callers by supporting various argv conventions
// extract the user args and scriptPath
let userArgs;
switch (parseOptions.from) {
case undefined:
Expand All @@ -1005,6 +1019,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
case 'user':
userArgs = argv.slice(0);
break;
case 'eval':
userArgs = argv.slice(1);
break;
default:
throw new Error(
`unexpected parse option { from: '${parseOptions.from}' }`,
Expand All @@ -1022,12 +1039,18 @@ Expecting one of '${allowedValues.join("', '")}'`);
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
* Use parseAsync instead of parse if any of your action handlers are async.
*
* Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
*
* Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
* - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
* - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
* - `'user'`: just user arguments
*
* @example
* program.parse(process.argv);
* program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
* program.parse(); // parse process.argv and auto-detect electron and special node flags
* program.parse(process.argv); // assume argv[0] is app and argv[1] is script
* program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
*
* @param {string[]} [argv] - optional, defaults to process.argv
Expand All @@ -1046,14 +1069,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
* Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
* Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
* - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
* - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
* - `'user'`: just user arguments
*
* @example
* await program.parseAsync(process.argv);
* await program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
* await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags
* await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script
* await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
*
* @param {string[]} [argv]
Expand Down
19 changes: 19 additions & 0 deletions tests/command.parse.test.js
Expand Up @@ -4,6 +4,9 @@ const commander = require('../');
// https://github.com/electron/electron/issues/4690#issuecomment-217435222
// https://www.electronjs.org/docs/api/process#processdefaultapp-readonly

// (If mutating process.argv and process.execArgv causes problems, could add utility
// functions to get them and then mock the functions for tests.)

describe('.parse() args from', () => {
test('when no args then use process.argv and app/script/args', () => {
const program = new commander.Command();
Expand Down Expand Up @@ -67,6 +70,22 @@ describe('.parse() args from', () => {
program.parse(['node', 'script.js'], { from: 'silly' });
}).toThrow();
});

test.each(['-e', '--eval', '-p', '--print'])(
'when node execArgv includes %s then app/args',
(flag) => {
const program = new commander.Command();
const holdExecArgv = process.execArgv;
const holdArgv = process.argv;
process.argv = ['node', 'user-arg'];
process.execArgv = [flag, 'console.log("hello, world")'];
program.parse();
process.argv = holdArgv;
process.execArgv = holdExecArgv;
expect(program.args).toEqual(['user-arg']);
process.execArgv = holdExecArgv;
},
);
});

describe('return type', () => {
Expand Down
35 changes: 23 additions & 12 deletions typings/index.d.ts
Expand Up @@ -724,38 +724,49 @@ export class Command {
/**
* Parse `argv`, setting options and invoking commands when defined.
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
* Use parseAsync instead of parse if any of your action handlers are async.
*
* Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
*
* Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
* - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
* - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
* - `'user'`: just user arguments
*
* @example
* ```
* program.parse(process.argv);
* program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
* program.parse(); // parse process.argv and auto-detect electron and special node flags
* program.parse(process.argv); // assume argv[0] is app and argv[1] is script
* program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
* ```
*
* @returns `this` command for chaining
*/
parse(argv?: readonly string[], options?: ParseOptions): this;
parse(argv?: readonly string[], parseOptions?: ParseOptions): this;

/**
* Parse `argv`, setting options and invoking commands when defined.
*
* Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
* Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!
*
* The default expectation is that the arguments are from node and have the application as argv[0]
* and the script being run in argv[1], with user parameters after that.
* Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:
* - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that
* - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged
* - `'user'`: just user arguments
*
* @example
* ```
* program.parseAsync(process.argv);
* program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
* program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
* await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags
* await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script
* await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
* ```
*
* @returns Promise
*/
parseAsync(argv?: readonly string[], options?: ParseOptions): Promise<this>;
parseAsync(
argv?: readonly string[],
parseOptions?: ParseOptions,
): Promise<this>;

/**
* Parse options from `argv` removing known options,
Expand Down

0 comments on commit dd2302d

Please sign in to comment.