commander 13.0.0-0 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md CHANGED
@@ -175,7 +175,15 @@ const program = new Command();
175
175
 
176
176
  ## Options
177
177
 
178
- Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|').
178
+ Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). To allow a wider range of short-ish flags than just
179
+ single characters, you may also have two long options. Examples:
180
+
181
+ ```js
182
+ program
183
+ .option('-p, --port <number>', 'server port number')
184
+ .option('--trace', 'add extra debugging output')
185
+ .option('--ws, --workspace <name>', 'use a custom workspace')
186
+ ```
179
187
 
180
188
  The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler.
181
189
 
@@ -921,9 +929,11 @@ program.helpCommand('assist [command]', 'show assistance');
921
929
  ### More configuration
922
930
 
923
931
  The built-in help is formatted using the Help class.
924
- You can configure the Help behaviour by modifying data properties and methods using `.configureHelp()`, or by subclassing using `.createHelp()` if you prefer.
932
+ You can configure the help by modifying data properties and methods using `.configureHelp()`, or by subclassing Help using `.createHelp()` .
933
+
934
+ Simple properties include `sortSubcommands`, `sortOptions`, and `showGlobalOptions`. You can add color using the style methods like `styleTitle()`.
925
935
 
926
- For more detail see (./docs/help-in-depth.md)
936
+ For more detail and examples of changing the displayed text, color, and layout see (./docs/help-in-depth.md)
927
937
 
928
938
  ## Custom event listeners
929
939
 
@@ -957,8 +967,6 @@ program.parse(['--port', '80'], { from: 'user' }); // just user supplied argumen
957
967
 
958
968
  Use parseAsync instead of parse if any of your action handlers are async.
959
969
 
960
- If you want to parse multiple times, create a new program each time. Calling parse does not clear out any previous state.
961
-
962
970
  ### Parsing Configuration
963
971
 
964
972
  If the default parsing does not suit your needs, there are some behaviours to support other usage patterns.
package/lib/command.js CHANGED
@@ -55,6 +55,7 @@ class Command extends EventEmitter {
55
55
  /** @type {(boolean | string)} */
56
56
  this._showHelpAfterError = false;
57
57
  this._showSuggestionAfterError = true;
58
+ this._savedState = null; // used in save/restoreStateBeforeParse
58
59
 
59
60
  // see configureOutput() for docs
60
61
  this._outputConfiguration = {
@@ -755,7 +756,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
755
756
  * @example
756
757
  * program
757
758
  * .option('-p, --pepper', 'add pepper')
758
- * .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument
759
+ * .option('--pt, --pizza-type <TYPE>', 'type of pizza') // required option-argument
759
760
  * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
760
761
  * .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
761
762
  *
@@ -1069,6 +1070,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1069
1070
  */
1070
1071
 
1071
1072
  parse(argv, parseOptions) {
1073
+ this._prepareForParse();
1072
1074
  const userArgs = this._prepareUserArgs(argv, parseOptions);
1073
1075
  this._parseCommand([], userArgs);
1074
1076
 
@@ -1097,12 +1099,82 @@ Expecting one of '${allowedValues.join("', '")}'`);
1097
1099
  */
1098
1100
 
1099
1101
  async parseAsync(argv, parseOptions) {
1102
+ this._prepareForParse();
1100
1103
  const userArgs = this._prepareUserArgs(argv, parseOptions);
1101
1104
  await this._parseCommand([], userArgs);
1102
1105
 
1103
1106
  return this;
1104
1107
  }
1105
1108
 
1109
+ _prepareForParse() {
1110
+ if (this._savedState === null) {
1111
+ this.saveStateBeforeParse();
1112
+ } else {
1113
+ this.restoreStateBeforeParse();
1114
+ }
1115
+ }
1116
+
1117
+ /**
1118
+ * Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
1119
+ * Not usually called directly, but available for subclasses to save their custom state.
1120
+ *
1121
+ * This is called in a lazy way. Only commands used in parsing chain will have state saved.
1122
+ */
1123
+ saveStateBeforeParse() {
1124
+ this._savedState = {
1125
+ // name is stable if supplied by author, but may be unspecified for root command and deduced during parsing
1126
+ _name: this._name,
1127
+ // option values before parse have default values (including false for negated options)
1128
+ // shallow clones
1129
+ _optionValues: { ...this._optionValues },
1130
+ _optionValueSources: { ...this._optionValueSources },
1131
+ };
1132
+ }
1133
+
1134
+ /**
1135
+ * Restore state before parse for calls after the first.
1136
+ * Not usually called directly, but available for subclasses to save their custom state.
1137
+ *
1138
+ * This is called in a lazy way. Only commands used in parsing chain will have state restored.
1139
+ */
1140
+ restoreStateBeforeParse() {
1141
+ if (this._storeOptionsAsProperties)
1142
+ throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
1143
+ - either make a new Command for each call to parse, or stop storing options as properties`);
1144
+
1145
+ // clear state from _prepareUserArgs
1146
+ this._name = this._savedState._name;
1147
+ this._scriptPath = null;
1148
+ this.rawArgs = [];
1149
+ // clear state from setOptionValueWithSource
1150
+ this._optionValues = { ...this._savedState._optionValues };
1151
+ this._optionValueSources = { ...this._savedState._optionValueSources };
1152
+ // clear state from _parseCommand
1153
+ this.args = [];
1154
+ // clear state from _processArguments
1155
+ this.processedArgs = [];
1156
+ }
1157
+
1158
+ /**
1159
+ * Throw if expected executable is missing. Add lots of help for author.
1160
+ *
1161
+ * @param {string} executableFile
1162
+ * @param {string} executableDir
1163
+ * @param {string} subcommandName
1164
+ */
1165
+ _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
1166
+ if (fs.existsSync(executableFile)) return;
1167
+
1168
+ const executableDirMessage = executableDir
1169
+ ? `searched for local subcommand relative to directory '${executableDir}'`
1170
+ : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
1171
+ const executableMissing = `'${executableFile}' does not exist
1172
+ - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1173
+ - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1174
+ - ${executableDirMessage}`;
1175
+ throw new Error(executableMissing);
1176
+ }
1177
+
1106
1178
  /**
1107
1179
  * Execute a sub-command executable.
1108
1180
  *
@@ -1186,6 +1258,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1186
1258
  proc = childProcess.spawn(executableFile, args, { stdio: 'inherit' });
1187
1259
  }
1188
1260
  } else {
1261
+ this._checkForMissingExecutable(
1262
+ executableFile,
1263
+ executableDir,
1264
+ subcommand._name,
1265
+ );
1189
1266
  args.unshift(executableFile);
1190
1267
  // add executable arguments to spawn
1191
1268
  args = incrementNodeInspectorPort(process.execArgv).concat(args);
@@ -1224,14 +1301,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1224
1301
  proc.on('error', (err) => {
1225
1302
  // @ts-ignore: because err.code is an unknown property
1226
1303
  if (err.code === 'ENOENT') {
1227
- const executableDirMessage = executableDir
1228
- ? `searched for local subcommand relative to directory '${executableDir}'`
1229
- : 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
1230
- const executableMissing = `'${executableFile}' does not exist
1231
- - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1232
- - if the default executable name is not suitable, use the executableFile option to supply a custom name or path
1233
- - ${executableDirMessage}`;
1234
- throw new Error(executableMissing);
1304
+ this._checkForMissingExecutable(
1305
+ executableFile,
1306
+ executableDir,
1307
+ subcommand._name,
1308
+ );
1235
1309
  // @ts-ignore: because err.code is an unknown property
1236
1310
  } else if (err.code === 'EACCES') {
1237
1311
  throw new Error(`'${executableFile}' not executable`);
@@ -1261,6 +1335,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1261
1335
  const subCommand = this._findCommand(commandName);
1262
1336
  if (!subCommand) this.help({ error: true });
1263
1337
 
1338
+ subCommand._prepareForParse();
1264
1339
  let promiseChain;
1265
1340
  promiseChain = this._chainOrCallSubCommandHook(
1266
1341
  promiseChain,
@@ -1638,6 +1713,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1638
1713
  * Parse options from `argv` removing known options,
1639
1714
  * and return argv split into operands and unknown arguments.
1640
1715
  *
1716
+ * Side effects: modifies command by storing options. Does not reset state if called again.
1717
+ *
1641
1718
  * Examples:
1642
1719
  *
1643
1720
  * argv => operands, unknown
package/lib/option.js CHANGED
@@ -18,7 +18,7 @@ class Option {
18
18
  this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
19
19
  this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
20
20
  const optionFlags = splitOptionFlags(flags);
21
- this.short = optionFlags.shortFlag;
21
+ this.short = optionFlags.shortFlag; // May be a short flag, undefined, or even a long flag (if option has two long flags).
22
22
  this.long = optionFlags.longFlag;
23
23
  this.negate = false;
24
24
  if (this.long) {
@@ -321,25 +321,44 @@ function splitOptionFlags(flags) {
321
321
  const longFlagExp = /^--[^-]/;
322
322
 
323
323
  const flagParts = flags.split(/[ |,]+/).concat('guard');
324
+ // Normal is short and/or long.
324
325
  if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift();
325
326
  if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift();
327
+ // Long then short. Rarely used but fine.
328
+ if (!shortFlag && shortFlagExp.test(flagParts[0]))
329
+ shortFlag = flagParts.shift();
330
+ // Allow two long flags, like '--ws, --workspace'
331
+ // This is the supported way to have a shortish option flag.
332
+ if (!shortFlag && longFlagExp.test(flagParts[0])) {
333
+ shortFlag = longFlag;
334
+ longFlag = flagParts.shift();
335
+ }
326
336
 
327
- // Check for some unsupported flags that people try.
328
- if (/^-[^-][^-]/.test(flagParts[0]))
329
- throw new Error(
330
- `invalid Option flags, short option is dash and single character: '${flags}'`,
331
- );
332
- if (shortFlag && shortFlagExp.test(flagParts[0]))
333
- throw new Error(
334
- `invalid Option flags, more than one short flag: '${flags}'`,
335
- );
336
- if (longFlag && longFlagExp.test(flagParts[0]))
337
+ // Check for unprocessed flag. Fail noisily rather than silently ignore.
338
+ if (flagParts[0].startsWith('-')) {
339
+ const unsupportedFlag = flagParts[0];
340
+ const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
341
+ if (/^-[^-][^-]/.test(unsupportedFlag))
342
+ throw new Error(
343
+ `${baseError}
344
+ - a short flag is a single dash and a single character
345
+ - either use a single dash and a single character (for a short flag)
346
+ - or use a double dash for a long option (and can have two, like '--ws, --workspace')`,
347
+ );
348
+ if (shortFlagExp.test(unsupportedFlag))
349
+ throw new Error(`${baseError}
350
+ - too many short flags`);
351
+ if (longFlagExp.test(unsupportedFlag))
352
+ throw new Error(`${baseError}
353
+ - too many long flags`);
354
+
355
+ throw new Error(`${baseError}
356
+ - unrecognised flag format`);
357
+ }
358
+ if (shortFlag === undefined && longFlag === undefined)
337
359
  throw new Error(
338
- `invalid Option flags, more than one long flag: '${flags}'`,
360
+ `option creation failed due to no flags found in '${flags}'.`,
339
361
  );
340
- // Generic error if failed to find a flag or an unexpected flag left over.
341
- if (!(shortFlag || longFlag) || flagParts[0].startsWith('-'))
342
- throw new Error(`invalid Option flags: '${flags}'`);
343
362
 
344
363
  return { shortFlag, longFlag };
345
364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commander",
3
- "version": "13.0.0-0",
3
+ "version": "13.1.0",
4
4
  "description": "the complete solution for node.js command-line programs",
5
5
  "keywords": [
6
6
  "commander",
@@ -63,7 +63,7 @@
63
63
  "@eslint/js": "^9.4.0",
64
64
  "@types/jest": "^29.2.4",
65
65
  "@types/node": "^22.7.4",
66
- "eslint": "^8.57.1",
66
+ "eslint": "^9.17.0",
67
67
  "eslint-config-prettier": "^9.1.0",
68
68
  "eslint-plugin-jest": "^28.3.0",
69
69
  "globals": "^15.7.0",
@@ -1,8 +1,6 @@
1
1
  // Type definitions for commander
2
2
  // Original definitions by: Alan Agius <https://github.com/alan-agius4>, Marcelo Dezem <https://github.com/mdezem>, vvakame <https://github.com/vvakame>, Jules Randolph <https://github.com/sveinburne>
3
3
 
4
- // Using method rather than property for method-signature-style, to document method overloads separately. Allow either.
5
- /* eslint-disable @typescript-eslint/method-signature-style */
6
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
7
5
 
8
6
  // This is a trick to encourage editor to suggest the known literals while still
@@ -619,7 +617,7 @@ export class Command {
619
617
  * ```js
620
618
  * program
621
619
  * .option('-p, --pepper', 'add pepper')
622
- * .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument
620
+ * .option('--pt, --pizza-type <TYPE>', 'type of pizza') // required option-argument
623
621
  * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
624
622
  * .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
625
623
  * ```
@@ -823,10 +821,28 @@ export class Command {
823
821
  parseOptions?: ParseOptions,
824
822
  ): Promise<this>;
825
823
 
824
+ /**
825
+ * Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
826
+ * Not usually called directly, but available for subclasses to save their custom state.
827
+ *
828
+ * This is called in a lazy way. Only commands used in parsing chain will have state saved.
829
+ */
830
+ saveStateBeforeParse(): void;
831
+
832
+ /**
833
+ * Restore state before parse for calls after the first.
834
+ * Not usually called directly, but available for subclasses to save their custom state.
835
+ *
836
+ * This is called in a lazy way. Only commands used in parsing chain will have state restored.
837
+ */
838
+ restoreStateBeforeParse(): void;
839
+
826
840
  /**
827
841
  * Parse options from `argv` removing known options,
828
842
  * and return argv split into operands and unknown arguments.
829
843
  *
844
+ * Side effects: modifies command by storing options. Does not reset state if called again.
845
+ *
830
846
  * argv => operands, unknown
831
847
  * --known kkk op => [op], []
832
848
  * op --known kkk => [op], []