From 898a218630af214f986846cd2ddf988f5f00d73f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 12:44:14 +1300 Subject: [PATCH 1/7] Remove openIssues test for #1062, fixed and being tested --- tests/openIssues.test.js.skip | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/openIssues.test.js.skip b/tests/openIssues.test.js.skip index 202291de5..6179a0f6d 100644 --- a/tests/openIssues.test.js.skip +++ b/tests/openIssues.test.js.skip @@ -66,19 +66,4 @@ describe('open issues', () => { done(); }); }); - - // https://github.com/tj/commander.js/pull/1062 - test.skip('#1062 when .action on program with subcommands and no program argument then program action called', () => { - const actionMock = jest.fn(); - const program = new commander.Command(); - program - .arguments('[file]') - .action(actionMock); - program - .command('subcommand'); - - program.parse(['node', 'test']); - - expect(actionMock).toHaveBeenCalledWith(undefined, program); - }); }); From dc48063e3b1f9a8251eb4d81b555f3075f55de9c Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 13:20:35 +1300 Subject: [PATCH 2/7] Rework parseOptions handling of unknown arguments --- index.js | 124 ++++++++++++++++------------------- tests/command.action.test.js | 2 +- typings/index.d.ts | 12 +++- 3 files changed, 66 insertions(+), 72 deletions(-) diff --git a/index.js b/index.js index 789b9eea5..41cea63ff 100644 --- a/index.js +++ b/index.js @@ -329,26 +329,22 @@ Command.prototype.action = function(fn) { var parsed = self.parseOptions(unknown); - // Output help if necessary outputHelpIfNecessary(self, parsed.unknown); self._checkForMissingMandatoryOptions(); - // If there are still any unknown options, then we simply - // die, unless someone asked for help, in which case we give it - // to them, and then we die. + // If there are still any unknown options, then we simply die. if (parsed.unknown.length > 0) { self.unknownOption(parsed.unknown[0]); } - // Leftover arguments need to be pushed back. Fixes issue #56 - if (parsed.args.length) args = parsed.args.concat(args); + args = args.concat(parsed.operands, parsed.unknown); - self._args.forEach(function(arg, i) { - if (arg.required && args[i] == null) { - self.missingArgument(arg.name); - } else if (arg.variadic) { + self._args.forEach(function(expectedArg, i) { + if (expectedArg.required && args[i] == null) { + self.missingArgument(expectedArg.name); + } else if (expectedArg.variadic) { if (i !== self._args.length - 1) { - self.variadicArgNotLast(arg.name); + self.variadicArgNotLast(expectedArg.name); } args[i] = args.splice(i); @@ -634,11 +630,12 @@ Command.prototype.parse = function(argv) { } // process argv - var normalized = this.normalize(argv.slice(2)); - var parsed = this.parseOptions(normalized); - var args = this.args = parsed.args; + const normalized = this.normalize(argv.slice(2)); + const parsed = this.parseOptions(normalized); + const args = parsed.operands.concat(parsed.unknown); + this.args = args.slice(); - var result = this.parseArgs(this.args, parsed.unknown); + var result = this.parseArgs(parsed.operands, parsed.unknown); if (args[0] === 'help' && args.length === 1) this.help(); @@ -688,7 +685,7 @@ Command.prototype.parse = function(argv) { } if (this._execs.has(name)) { - return this.executeSubCommand(argv, args, parsed.unknown, subCommand ? subCommand._executableFile : undefined); + return this.executeSubCommand(argv, args, subCommand ? subCommand._executableFile : undefined); } return result; @@ -704,9 +701,7 @@ Command.prototype.parse = function(argv) { * @api private */ -Command.prototype.executeSubCommand = function(argv, args, unknown, executableFile) { - args = args.concat(unknown); - +Command.prototype.executeSubCommand = function(argv, args, executableFile) { if (!args.length) this.help(); var isExplicitJS = false; // Whether to use node to launch "executable" @@ -868,16 +863,14 @@ Command.prototype.normalize = function(args) { * @api private */ -Command.prototype.parseArgs = function(args, unknown) { - var name; - - if (args.length) { - name = args[0]; +Command.prototype.parseArgs = function(operands, unknown) { + if (operands.length) { + const name = operands[0]; if (this.listeners('command:' + name).length) { - this.emit('command:' + args.shift(), args, unknown); + this.emit('command:' + operands[0], operands.slice(1), unknown); } else { - this.emit('program-command', args, unknown); - this.emit('command:*', args, unknown); + this.emit('program-command', operands, unknown); + this.emit('command:*', operands, unknown); } } else { outputHelpIfNecessary(this, unknown); @@ -904,11 +897,7 @@ Command.prototype.parseArgs = function(args, unknown) { */ Command.prototype.optionFor = function(arg) { - for (var i = 0, len = this.options.length; i < len; ++i) { - if (this.options[i].is(arg)) { - return this.options[i]; - } - } + return this.options.find(option => option.is(arg)); }; /** @@ -929,57 +918,62 @@ Command.prototype._checkForMissingMandatoryOptions = function() { }; /** - * Parse options from `argv` returning `argv` - * void of these options. + * Parse options from `argv` removing known options, + * and return argv split into operands and unknown arguments. * - * @param {Array} argv - * @return {Array} + * Examples: + * + * argv => operands, unknown + * --known kkk op => [op], [] + * sub --unknown uuu op => [sub], [--unknown uuu op] + * sub -- --unknown uuu op => [sub --unknown uuu op], [] + * sub --unknown1 1 --unknown 222 op => [sub], [-unknown1 1 -- --unknown 222 op] + * + * @param {String[]} argv + * @return {{operands: String[], unknown: String[]}} * @api public */ Command.prototype.parseOptions = function(argv) { - var args = [], - len = argv.length, - literal, - option, - arg; - - var unknownOptions = []; + const operands = []; // operands, not options or values + const unknown = []; // first unknown option and remaining unknown args + let literal = false; + let dest = operands; // parse options - for (var i = 0; i < len; ++i) { - arg = argv[i]; + for (var i = 0; i < argv.length; ++i) { + const arg = argv[i]; // literal args after -- if (literal) { - args.push(arg); + dest.push(arg); continue; } if (arg === '--') { literal = true; + if (dest === unknown) dest.push('--'); continue; } // find matching Option - option = this.optionFor(arg); + const option = this.optionFor(arg); - // option is defined + // recognised option, call listener to assign value with possible custom processing if (option) { - // requires arg if (option.required) { - arg = argv[++i]; - if (arg == null) return this.optionMissingArgument(option); - this.emit('option:' + option.name(), arg); - // optional arg + const value = argv[++i]; + if (value === undefined) this.optionMissingArgument(option); + this.emit('option:' + option.name(), value); } else if (option.optional) { - arg = argv[i + 1]; - if (arg == null || (arg[0] === '-' && arg !== '-')) { - arg = null; + let value = argv[i + 1]; + // do not use a following option as a value + if (value === undefined || (value[0] === '-' && value !== '-')) { + value = null; } else { ++i; } - this.emit('option:' + option.name(), arg); + this.emit('option:' + option.name(), value); // flag } else { this.emit('option:' + option.name()); @@ -987,24 +981,16 @@ Command.prototype.parseOptions = function(argv) { continue; } - // looks like an option + // looks like an option, unknowns from here if (arg.length > 1 && arg[0] === '-') { - unknownOptions.push(arg); - - // If the next argument looks like it might be - // an argument for this option, we pass it on. - // If it isn't, then it'll simply be ignored - if ((i + 1) < argv.length && (argv[i + 1][0] !== '-' || argv[i + 1] === '-')) { - unknownOptions.push(argv[++i]); - } - continue; + dest = unknown; } // arg - args.push(arg); + dest.push(arg); } - return { args: args, unknown: unknownOptions }; + return { operands, unknown }; }; /** diff --git a/tests/command.action.test.js b/tests/command.action.test.js index ebe9f3caf..4aa892962 100644 --- a/tests/command.action.test.js +++ b/tests/command.action.test.js @@ -20,7 +20,7 @@ test('when .action called then program.args only contains args', () => { .command('info ') .action(() => {}); program.parse(['node', 'test', 'info', 'my-file']); - expect(program.args).toEqual(['my-file']); + expect(program.args).toEqual(['info', 'my-file']); }); test('when .action called with extra arguments then extras also passed to action', () => { diff --git a/typings/index.d.ts b/typings/index.d.ts index 2ed0fa6c4..18462249e 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -204,7 +204,15 @@ declare namespace commander { parse(argv: string[]): Command; /** - * Parse options from `argv` returning `argv` void of these options. + * Parse options from `argv` removing known options, + * and return argv split into operands and unknown arguments. + * + * @example + * argv => operands, unknown + * --known kkk op => [op], [] + * sub --unknown uuu op => [sub], [--unknown uuu op] + * sub -- --unknown uuu op => [sub --unknown uuu op], [] + * sub --unknown1 1 --unknown 222 op => [sub], [-unknown1 1 -- --unknown 222 op] */ parseOptions(argv: string[]): commander.ParseOptionsResult; @@ -286,7 +294,7 @@ declare namespace commander { } interface ParseOptionsResult { - args: string[]; + operands: string[]; unknown: string[]; } From f978b77c00fce6c04de061da79e55992f327f7ac Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 14:56:03 +1300 Subject: [PATCH 3/7] First tests for parseOptions, enable prepared regression tests --- tests/command.parseOptions.test.js | 54 +++++++++++++++++++++++++ tests/openIssues.test.js.skip | 65 ------------------------------ 2 files changed, 54 insertions(+), 65 deletions(-) create mode 100644 tests/command.parseOptions.test.js diff --git a/tests/command.parseOptions.test.js b/tests/command.parseOptions.test.js new file mode 100644 index 000000000..2618e8c34 --- /dev/null +++ b/tests/command.parseOptions.test.js @@ -0,0 +1,54 @@ +const commander = require('../'); +const childProcess = require('child_process'); +const path = require('path'); + +// Combination of parse and parseOptions tests which are are more about details +// than high level behaviours which are tested elsewhere. + +// Tests created from reported bugs +describe('regression tests', () => { + // https://github.com/tj/commander.js/issues/1039 + // https://github.com/tj/commander.js/issues/561 + test('when unknown option and multiple arguments then unknown option detected', () => { + const program = new commander.Command(); + program + .exitOverride(); + expect(() => { + program.parse(['node', 'text', '--bug', '0', '1', '2', '3']); + }).toThrow(); + }); + + // https://github.com/tj/commander.js/issues/1032 + function createProgram1032() { + const program = new commander.Command(); + program + .command('doit [id]') + .option('--better', 'do it better') + .action((id, cmd) => { + }); + return program; + }; + + // https://github.com/tj/commander.js/issues/1032 + test('when specify subcommand and argument then program.args not empty', () => { + const program = createProgram1032(); + program.parse(['node', 'test.js', 'doit', 'myid']); + expect(program.args.length).toBeGreaterThan(0); + }); + + // https://github.com/tj/commander.js/issues/1032 + test('when specify subcommand and option and argument then program.args not empty', () => { + const program = createProgram1032(); + program.parse(['node', 'test.js', 'doit', '--better', 'myid']); + expect(program.args.length).toBeGreaterThan(0); + }); + + // https://github.com/tj/commander.js/issues/508 + test('when arguments to executable include option flags then argument order preserved', (done) => { + const pm = path.join(__dirname, 'fixtures/pm'); + childProcess.execFile('node', [pm, 'echo', '1', '2', '--dry-run', '3', '4', '5', '6'], function(_error, stdout, stderr) { + expect(stdout).toBe("[ '1', '2', '--dry-run', '3', '4', '5', '6' ]\n"); + done(); + }); + }); +}); diff --git a/tests/openIssues.test.js.skip b/tests/openIssues.test.js.skip index 6179a0f6d..d224ac9fe 100644 --- a/tests/openIssues.test.js.skip +++ b/tests/openIssues.test.js.skip @@ -1,69 +1,4 @@ const commander = require('../'); -const childProcess = require('child_process'); -const path = require('path'); describe('open issues', () => { - // https://github.com/tj/commander.js/issues/1039 - test.skip('#1039: when unknown option then unknown option detected', () => { - const program = new commander.Command(); - program - .exitOverride(); - expect(() => { - program.parse(['node', 'text', '--bug']); - }).toThrow(); - }); - - test.skip('#1039: when unknown option and multiple arguments then unknown option detected', () => { - const program = new commander.Command(); - program - .exitOverride(); - expect(() => { - program.parse(['node', 'text', '--bug', '0', '1', '2', '3']); - }).toThrow(); - }); - - // https://github.com/tj/commander.js/issues/1032 - function createProgram1032() { - const program = new commander.Command(); - program - .command('doit [id]') - .option('--better', 'do it better') - .action((id, cmd) => { - }); - return program; - }; - - test.skip('#1032: when specify subcommand then args not empty', () => { - const program = createProgram1032(); - program.parse(['node', 'test.js', 'doit', 'myid']); - expect(program.args.length).toBeGreaterThan(0); - }); - - test.skip('#1032: when specify subcommand and option then args not empty', () => { - const program = createProgram1032(); - program.parse(['node', 'test.js', 'doit', '--better', 'myid']); - expect(program.args.length).toBeGreaterThan(0); - }); - - // https://github.com/tj/commander.js/issues/561 - test.skip('#561: when specify argument and unknown option then error', () => { - const program = new commander.Command(); - program - .exitOverride() - .option('-x, --x-flag', 'A flag') - .arguments(''); - - expect(() => { - program.parse(['node', 'test', '--NONSENSE', '1', '2', '3']); - }).toThrow(); - }); - - // https://github.com/tj/commander.js/issues/508 - test.skip('#508: when arguments to executable include option flags then argument order preserved', (done) => { - const pm = path.join(__dirname, 'fixtures/pm'); - childProcess.execFile('node', [pm, 'echo', '1', '2', '--dry-run', '3', '4', '5', '6'], function(_error, stdout, stderr) { - expect(stdout).toBe("[ '1', '2', '--dry-run', '3', '4', '5', '6' ]\n"); - done(); - }); - }); }); From 3b3f8c0606830a94a99da96dadf84bdcde079a05 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 17:23:41 +1300 Subject: [PATCH 4/7] Add tests for parseOptions --- tests/command.parseOptions.test.js | 113 +++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/command.parseOptions.test.js b/tests/command.parseOptions.test.js index 2618e8c34..817af406d 100644 --- a/tests/command.parseOptions.test.js +++ b/tests/command.parseOptions.test.js @@ -52,3 +52,116 @@ describe('regression tests', () => { }); }); }); + +describe('parseOptions', () => { + function createProgram() { + const program = new commander.Command(); + program + .option('--global-flag') + .option('--global-value ') + .command('sub [args...]') + .option('--sub-flag') + .option('--sub-value '); + program + .action(() => { }); + return program; + }; + + // Subcommands are just another potential operand as far as parseOptions is concerned, so limited testing of subcommand as such. + + test('when empty args then empty results', () => { + const program = createProgram(); + const result = program.parseOptions([]); + expect(result).toEqual({ operands: [], unknown: [] }); + }); + + test('when only operands then results has all operands', () => { + const program = createProgram(); + const result = program.parseOptions('one two three'.split(' ')); + expect(result).toEqual({ operands: ['one', 'two', 'three'], unknown: [] }); + }); + + test('when subcommand and operand then results has subcommand and operand', () => { + const program = createProgram(); + const result = program.parseOptions('sub one'.split(' ')); + expect(result).toEqual({ operands: ['sub', 'one'], unknown: [] }); + }); + + test('when program has flag then option removed', () => { + const program = createProgram(); + const result = program.parseOptions('--global-flag'.split(' ')); + expect(result).toEqual({ operands: [], unknown: [] }); + }); + + test('when program has option with value then option removed', () => { + const program = createProgram(); + const result = program.parseOptions('--global-value foo'.split(' ')); + expect(result).toEqual({ operands: [], unknown: [] }); + }); + + test('when program has flag before operand then option removed', () => { + const program = createProgram(); + const result = program.parseOptions('--global-flag arg'.split(' ')); + expect(result).toEqual({ operands: ['arg'], unknown: [] }); + }); + + test('when program has flag after operand then option removed', () => { + const program = createProgram(); + const result = program.parseOptions('arg --global-flag'.split(' ')); + expect(result).toEqual({ operands: ['arg'], unknown: [] }); + }); + + test('when program has flag after subcommand then option removed', () => { + const program = createProgram(); + const result = program.parseOptions('sub --global-flag'.split(' ')); + expect(result).toEqual({ operands: ['sub'], unknown: [] }); + }); + + test('when program has unknown option then option returned in unknown', () => { + const program = createProgram(); + const result = program.parseOptions('--unknown'.split(' ')); + expect(result).toEqual({ operands: [], unknown: ['--unknown'] }); + }); + + test('when program has unknown option before operands then all unknown in same order', () => { + const program = createProgram(); + const result = program.parseOptions('--unknown arg'.split(' ')); + expect(result).toEqual({ operands: [], unknown: ['--unknown', 'arg'] }); + }); + + test('when program has unknown option after operand then option returned in unknown', () => { + const program = createProgram(); + const result = program.parseOptions('arg --unknown'.split(' ')); + expect(result).toEqual({ operands: ['arg'], unknown: ['--unknown'] }); + }); + + test('when program has flag after unknown option then flag removed', () => { + const program = createProgram(); + const result = program.parseOptions('--unknown --global-flag'.split(' ')); + expect(result).toEqual({ operands: [], unknown: ['--unknown'] }); + }); + + test('when subcommand has flag then flag returned as unknown', () => { + const program = createProgram(); + const result = program.parseOptions('sub --sub-flag'.split(' ')); + expect(result).toEqual({ operands: ['sub'], unknown: ['--sub-flag'] }); + }); + + test('when program has literal before known flag then option returned as operand', () => { + const program = createProgram(); + const result = program.parseOptions('-- --global-flag'.split(' ')); + expect(result).toEqual({ operands: ['--global-flag'], unknown: [] }); + }); + + test('when program has literal before unknown option then option returned as operand', () => { + const program = createProgram(); + const result = program.parseOptions('-- --unknown uuu'.split(' ')); + expect(result).toEqual({ operands: ['--unknown', 'uuu'], unknown: [] }); + }); + + test('when program has literal after unknown option then literal preserved too', () => { + const program = createProgram(); + const result = program.parseOptions('--unknown1 -- --unknown2'.split(' ')); + expect(result).toEqual({ operands: [], unknown: ['--unknown1', '--', '--unknown2'] }); + }); +}); From 450eba486013431b2a0d8dfa7d9214a3d202d971 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 18:08:21 +1300 Subject: [PATCH 5/7] Add tests on program.args after calling parse --- tests/command.parseOptions.test.js | 35 ++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/tests/command.parseOptions.test.js b/tests/command.parseOptions.test.js index 817af406d..f4b96e8f8 100644 --- a/tests/command.parseOptions.test.js +++ b/tests/command.parseOptions.test.js @@ -60,8 +60,7 @@ describe('parseOptions', () => { .option('--global-flag') .option('--global-value ') .command('sub [args...]') - .option('--sub-flag') - .option('--sub-value '); + .option('--sub-flag'); program .action(() => { }); return program; @@ -165,3 +164,35 @@ describe('parseOptions', () => { expect(result).toEqual({ operands: [], unknown: ['--unknown1', '--', '--unknown2'] }); }); }); + +// parse now sets program.args to the result of parseOptions (operands + unknown). Some limited testing. +describe('parse and program.args', () => { + test('when program has known flag and operand then option removed and operand returned', () => { + const program = new commander.Command(); + program + .option('--global-flag'); + program.parse('node test.js --global-flag arg'.split(' ')); + expect(program.args).toEqual(['arg']); + }); + + test('when program has mixed arguments then known options removed and rest returned in same order', () => { + const program = new commander.Command(); + program + .allowUnknownOption() + .option('--global-flag') + .option('--global-value '); + program.parse('node test.js aaa --global-flag bbb --unknown ccc --global-value value'.split(' ')); + expect(program.args).toEqual(['aaa', 'bbb', '--unknown', 'ccc']); + }); + + test('when subcommand has mixed arguments then program flags removed and rest returned in same order', () => { + const program = new commander.Command(); + program + .option('--global-flag') + .option('--global-value ') + .command('sub [args...]') + .option('--sub-flag'); + program.parse('node test.js --global-flag sub --sub-flag arg --global-value value'.split(' ')); + expect(program.args).toEqual(['sub', '--sub-flag', 'arg']); + }); +}); From 36243bc52ccd679fb7cc5600e976ff13f9d2ca26 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 19:09:40 +1300 Subject: [PATCH 6/7] Minor update to README for changed parse behaviour --- Readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index afe042d5e..6a4dc1eb4 100644 --- a/Readme.md +++ b/Readme.md @@ -11,7 +11,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Commander.js](#commanderjs) - [Installation](#installation) - - [Declaring _program_ variable](#declaring-program-variable) + - [Declaring program variable](#declaring-program-variable) - [Options](#options) - [Common option types, boolean and value](#common-option-types-boolean-and-value) - [Default option value](#default-option-value) @@ -33,7 +33,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Bits and pieces](#bits-and-pieces) - [Avoiding option name clashes](#avoiding-option-name-clashes) - [TypeScript](#typescript) - - [Node options such as `--harmony`](#node-options-such-as---harmony) + - [Node options such as --harmony](#node-options-such-as---harmony) - [Node debugging](#node-debugging) - [Override exit handling](#override-exit-handling) - [Examples](#examples) @@ -109,7 +109,7 @@ pizza details: - cheese ``` -`program.parse(arguments)` processes the arguments, leaving any args not consumed by the options as the `program.args` array. +`program.parse(arguments)` processes the arguments, leaving any args not consumed by the program options in the `program.args` array. ### Default option value @@ -356,7 +356,7 @@ program program.parse(process.argv); ``` -The variadic argument is passed to the action handler as an array. (And this also applies to `program.args`.) +The variadic argument is passed to the action handler as an array. ### Action handler (sub)commands @@ -546,7 +546,7 @@ program.on('option:verbose', function () { // error on unknown commands program.on('command:*', function () { - console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' ')); + console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args[0]]); process.exit(1); }); ``` From 5b2fd424effed6d08b0d2d6659962cf897f09b04 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 10 Jan 2020 21:54:28 +1300 Subject: [PATCH 7/7] Tweak inline .parseOption examples --- index.js | 2 +- typings/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index a58835b8d..82c64c801 100644 --- a/index.js +++ b/index.js @@ -948,9 +948,9 @@ Command.prototype._checkForMissingMandatoryOptions = function() { * * argv => operands, unknown * --known kkk op => [op], [] + * op --known kkk => [op], [] * sub --unknown uuu op => [sub], [--unknown uuu op] * sub -- --unknown uuu op => [sub --unknown uuu op], [] - * sub --unknown1 1 --unknown 222 op => [sub], [-unknown1 1 -- --unknown 222 op] * * @param {String[]} argv * @return {{operands: String[], unknown: String[]}} diff --git a/typings/index.d.ts b/typings/index.d.ts index 9c141a3fb..f0f0474ce 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -219,9 +219,9 @@ declare namespace commander { * @example * argv => operands, unknown * --known kkk op => [op], [] + * op --known kkk => [op], [] * sub --unknown uuu op => [sub], [--unknown uuu op] * sub -- --unknown uuu op => [sub --unknown uuu op], [] - * sub --unknown1 1 --unknown 222 op => [sub], [--unknown1 1 -- --unknown 222 op] */ parseOptions(argv: string[]): commander.ParseOptionsResult;