burgee 0.2.0 → 0.4.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -9
  3. package/dist/brand.d.ts +62 -1
  4. package/dist/brand.js +73 -7
  5. package/dist/cli.d.ts +11 -0
  6. package/dist/cli.js +17 -1
  7. package/dist/{commander-argument.js → commander/argument.js} +4 -1
  8. package/dist/{commander-command.d.ts → commander/command.d.ts} +15 -5
  9. package/dist/{commander-command.js → commander/command.js} +175 -22
  10. package/dist/{commander-error.js → commander/error.js} +2 -0
  11. package/dist/{commander-help.d.ts → commander/help.d.ts} +3 -3
  12. package/dist/{commander-help.js → commander/help.js} +18 -1
  13. package/dist/{commander-option.d.ts → commander/option.d.ts} +1 -1
  14. package/dist/{commander-option.js → commander/option.js} +15 -1
  15. package/dist/commander.d.ts +8 -8
  16. package/dist/commander.js +8 -8
  17. package/dist/completions.js +15 -4
  18. package/dist/dev.d.ts +47 -0
  19. package/dist/dev.js +132 -0
  20. package/dist/execute.d.ts +22 -1
  21. package/dist/execute.js +75 -24
  22. package/dist/help.d.ts +19 -7
  23. package/dist/help.js +50 -21
  24. package/dist/index.d.ts +3 -3
  25. package/dist/index.js +1 -1
  26. package/dist/manifest.d.ts +15 -0
  27. package/dist/manifest.js +12 -1
  28. package/dist/mcp.d.ts +14 -2
  29. package/dist/mcp.js +15 -2
  30. package/dist/runtime.d.ts +12 -0
  31. package/dist/runtime.js +7 -0
  32. package/dist/schema.d.ts +10 -0
  33. package/dist/schema.js +11 -0
  34. package/dist/testing-helpers.d.ts +18 -1
  35. package/dist/testing-helpers.js +35 -0
  36. package/dist/testing.d.ts +2 -2
  37. package/dist/testing.js +1 -1
  38. package/dist/unknown-option.d.ts +9 -0
  39. package/dist/unknown-option.js +27 -0
  40. package/dist/{yargs-burgee.d.ts → yargs/burgee.d.ts} +2 -2
  41. package/dist/{yargs-burgee.js → yargs/burgee.js} +15 -1
  42. package/dist/{yargs-cliui.js → yargs/cliui.js} +8 -0
  43. package/dist/{yargs-command.d.ts → yargs/command.d.ts} +5 -5
  44. package/dist/{yargs-command.js → yargs/command.js} +9 -2
  45. package/dist/{yargs-completion.d.ts → yargs/completion.d.ts} +3 -3
  46. package/dist/{yargs-completion.js → yargs/completion.js} +7 -2
  47. package/dist/{yargs-factory.d.ts → yargs/factory.d.ts} +8 -8
  48. package/dist/{yargs-factory.js → yargs/factory.js} +63 -13
  49. package/dist/{yargs-middleware.js → yargs/middleware.js} +6 -1
  50. package/dist/{yargs-shim.d.ts → yargs/shim.d.ts} +3 -3
  51. package/dist/{yargs-shim.js → yargs/shim.js} +24 -5
  52. package/dist/{yargs-usage.d.ts → yargs/usage.d.ts} +1 -1
  53. package/dist/{yargs-usage.js → yargs/usage.js} +9 -1
  54. package/dist/{yargs-utils.js → yargs/utils.js} +11 -0
  55. package/dist/{yargs-validation.d.ts → yargs/validation.d.ts} +1 -1
  56. package/dist/{yargs-validation.js → yargs/validation.js} +7 -1
  57. package/dist/{yargs-y18n.js → yargs/y18n.js} +6 -0
  58. package/dist/yargs-helpers.d.ts +1 -1
  59. package/dist/yargs-helpers.js +1 -1
  60. package/dist/yargs.d.ts +4 -4
  61. package/dist/yargs.js +5 -5
  62. package/package.json +3 -2
  63. /package/dist/{commander-argument.d.ts → commander/argument.d.ts} +0 -0
  64. /package/dist/{commander-error.d.ts → commander/error.d.ts} +0 -0
  65. /package/dist/{commander-suggest.d.ts → suggest.d.ts} +0 -0
  66. /package/dist/{commander-suggest.js → suggest.js} +0 -0
  67. /package/dist/{yargs-cliui.d.ts → yargs/cliui.d.ts} +0 -0
  68. /package/dist/{yargs-middleware.d.ts → yargs/middleware.d.ts} +0 -0
  69. /package/dist/{yargs-utils.d.ts → yargs/utils.d.ts} +0 -0
  70. /package/dist/{yargs-y18n.d.ts → yargs/y18n.d.ts} +0 -0
@@ -1,18 +1,33 @@
1
+ /**
2
+ * commander's `Command`, ported method for method from commander 15 and graded by
3
+ * commander's own suite through `compat-oracle`. The parse pipeline, the option
4
+ * grammar, every error string and exit code are commander's — that is what makes a
5
+ * user's existing program run unchanged (J2).
6
+ *
7
+ * burgee's additions sit beside it and never alter the default behaviour:
8
+ * - `manifest` projects the command tree, so plugins (`use`) and the generated
9
+ * surfaces read commander-syntax programs exactly like native ones (J7, J8);
10
+ * - `--json`, when the program has not declared that option itself, wraps the
11
+ * action's return value in the envelope (N-family);
12
+ * - `parse(argv, { stdout, stderr, exit })` injects the streams and the exit, and
13
+ * then reports through the E1 taxonomy — the harness's seam (T1).
14
+ */
1
15
  import childProcess from 'node:child_process';
2
16
  import { EventEmitter } from 'node:events';
3
17
  import fs from 'node:fs';
4
18
  import path from 'node:path';
5
19
  import process from 'node:process';
6
20
  import { stripVTControlCharacters } from 'node:util';
7
- import { Argument, humanReadableArgName } from './commander-argument.js';
8
- import { CommanderError } from './commander-error.js';
9
- import { Help } from './commander-help.js';
10
- import { DualOptions, Option } from './commander-option.js';
11
- import { suggestSimilar } from './commander-suggest.js';
12
- import { ExitCode } from './exit-code.js';
13
- import { Manifest } from './manifest.js';
14
- import { serveMcp } from './mcp.js';
15
- import { schemaOf } from './schema.js';
21
+ import { ExitCode } from '../exit-code.js';
22
+ import { Manifest } from '../manifest.js';
23
+ import { serveMcp } from '../mcp.js';
24
+ import { machineJson, schemaOf } from '../schema.js';
25
+ import { suggestSimilar } from '../suggest.js';
26
+ import { Argument, humanReadableArgName } from './argument.js';
27
+ import { CommanderError } from './error.js';
28
+ import { Help } from './help.js';
29
+ import { DualOptions, Option } from './option.js';
30
+ /** Names that would reach Object.prototype if used as an option key. */
16
31
  const POLLUTING = new Set(['__proto__', 'constructor', 'prototype']);
17
32
  const ENV_SOURCES = ['default', 'config', 'env'];
18
33
  const IMPLIED_SOURCES = ['default', 'implied'];
@@ -20,10 +35,12 @@ const HOOK_EVENTS = ['preSubcommand', 'preAction', 'postAction'];
20
35
  const HELP_POSITIONS = ['beforeAll', 'before', 'after', 'afterAll'];
21
36
  const SOURCE_EXT = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];
22
37
  const FORWARDED_SIGNALS = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
38
+ /** Wraps in single quotes. Used where a message says "from" right before a quoted method name, which a static import scanner would otherwise read as a specifier. */
23
39
  const quoted = (s) => `'${s}'`;
24
40
  function isThenable(value) {
25
41
  return typeof value?.then === 'function';
26
42
  }
43
+ /** commander's exit codes, read through burgee's taxonomy when the exit is injected (E1). */
27
44
  function e1(err) {
28
45
  switch (err.code) {
29
46
  case 'commander.helpDisplayed':
@@ -38,6 +55,7 @@ function e1(err) {
38
55
  return err.exitCode === 1 ? ExitCode.USAGE : err.exitCode;
39
56
  }
40
57
  }
58
+ /** What a run prints for an action's return value when the streams are injected. */
41
59
  function render(value) {
42
60
  if (value === undefined || value === null)
43
61
  return '';
@@ -55,9 +73,12 @@ export class Command extends EventEmitter {
55
73
  options = [];
56
74
  parent = null;
57
75
  registeredArguments = [];
76
+ /** @deprecated old name for registeredArguments */
58
77
  _args;
78
+ /** cli args with options removed */
59
79
  args = [];
60
80
  rawArgs = [];
81
+ /** like .args but after custom processing and collecting variadic */
61
82
  processedArgs = [];
62
83
  runningCommand = undefined;
63
84
  _allowUnknownOption = false;
@@ -86,6 +107,7 @@ export class Command extends EventEmitter {
86
107
  _savedState = null;
87
108
  _outputConfiguration;
88
109
  _hidden = false;
110
+ /** Lazy created on demand; null once disabled. */
89
111
  _helpOption = undefined;
90
112
  _addImplicitHelpCommand = undefined;
91
113
  _helpCommand = undefined;
@@ -96,8 +118,14 @@ export class Command extends EventEmitter {
96
118
  _version = undefined;
97
119
  _versionOptionName = undefined;
98
120
  _usage = undefined;
121
+ /** burgee: the root's projection, created on first use. */
99
122
  _manifest = undefined;
123
+ /** burgee: what this command does to the world (N6); declaring it exposes the command as an MCP tool. */
100
124
  _effects = undefined;
125
+ /** burgee: `true`, or the replacement's name (M5). Shown in help, schema and a one-line warning on use. */
126
+ _deprecated = undefined;
127
+ _deprecationWarned = false;
128
+ /** burgee: set for the duration of a parse that injected the streams or the exit. */
101
129
  _burgee = undefined;
102
130
  constructor(name) {
103
131
  super();
@@ -114,6 +142,7 @@ export class Command extends EventEmitter {
114
142
  stripColor: (str) => stripVTControlCharacters(str),
115
143
  };
116
144
  }
145
+ /** Copy settings useful to share between the root and its subcommands. */
117
146
  copyInheritedSettings(sourceCommand) {
118
147
  this._outputConfiguration = sourceCommand._outputConfiguration;
119
148
  this._helpOption = sourceCommand._helpOption;
@@ -161,6 +190,7 @@ export class Command extends EventEmitter {
161
190
  return this;
162
191
  return cmd;
163
192
  }
193
+ /** Factory for an unattached command; override to customise subcommands. */
164
194
  createCommand(name) {
165
195
  return new Command(name);
166
196
  }
@@ -232,6 +262,7 @@ export class Command extends EventEmitter {
232
262
  this.registeredArguments.push(argument);
233
263
  return this;
234
264
  }
265
+ /** Customise or disable the default help command (added by default when there are subcommands). */
235
266
  helpCommand(enableOrNameAndArgs, description) {
236
267
  if (typeof enableOrNameAndArgs === 'boolean') {
237
268
  this._addImplicitHelpCommand = enableOrNameAndArgs;
@@ -288,21 +319,25 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
288
319
  this._lifeCycleHooks[event] = [listener];
289
320
  return this;
290
321
  }
322
+ /** Replace the call to process.exit; defaults to throwing the CommanderError. */
291
323
  exitOverride(fn) {
292
324
  this._exitCallback =
293
325
  fn ??
294
326
  ((err) => {
295
327
  if (err.code !== 'commander.executeSubCommandAsync')
296
328
  throw err;
329
+ // Async callback from spawn events, not useful to throw.
297
330
  });
298
331
  return this;
299
332
  }
300
333
  _exit(exitCode, code, message) {
301
334
  if (this._exitCallback) {
302
335
  this._exitCallback(new CommanderError(exitCode, code, message));
336
+ // Expecting this line is not reached.
303
337
  }
304
338
  process.exit(exitCode);
305
339
  }
340
+ // commander's contract: the positional args, then the options, then the command itself.
306
341
  action(fn) {
307
342
  const listener = (args) => {
308
343
  const expectedArgsCount = this.registeredArguments.length;
@@ -317,6 +352,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
317
352
  createOption(flags, description) {
318
353
  return new Option(flags, description);
319
354
  }
355
+ /** Wrap parseArg to turn `commander.invalidArgument` into an error with context. */
320
356
  _callParseArg(target, value, previous, invalidArgumentMessage) {
321
357
  try {
322
358
  return target.parseArg?.(value, previous);
@@ -364,6 +400,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
364
400
  if (option.defaultValue !== undefined) {
365
401
  this.setOptionValueWithSource(name, option.defaultValue, 'default');
366
402
  }
403
+ // val is null for an optional option used without its argument, undefined for boolean and negated.
367
404
  const handleOptionValue = (val, invalidValueMessage, valueSource) => {
368
405
  let value = val;
369
406
  if (value == null && option.presetArg !== undefined)
@@ -381,7 +418,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
381
418
  else if (option.isBoolean() || option.optional)
382
419
  value = true;
383
420
  else
384
- value = '';
421
+ value = ''; // not normal, parseArg might have failed or be a mock function for testing
385
422
  }
386
423
  this.setOptionValueWithSource(name, value, valueSource);
387
424
  };
@@ -405,6 +442,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
405
442
  option.default(defaultValue).argParser(fn);
406
443
  }
407
444
  else if (fn instanceof RegExp) {
445
+ // deprecated
408
446
  const regex = fn;
409
447
  option.default(defaultValue).argParser((val, def) => {
410
448
  const m = regex.exec(val);
@@ -422,6 +460,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
422
460
  requiredOption(flags, description, parseArg, defaultValue) {
423
461
  return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
424
462
  }
463
+ /** `-f80` as `--flag=80` (default) versus `-fb` as `-f -b`. */
425
464
  combineFlagAndOptionalValue(combine = true) {
426
465
  this._combineFlagAndOptionalValue = !!combine;
427
466
  return this;
@@ -434,10 +473,12 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
434
473
  this._allowExcessArguments = !!allowExcess;
435
474
  return this;
436
475
  }
476
+ /** Global options before subcommands only, so subcommands may reuse option names. */
437
477
  enablePositionalOptions(positional = true) {
438
478
  this._enablePositionalOptions = !!positional;
439
479
  return this;
440
480
  }
481
+ /** Options after the first command-argument are passed through, not parsed. */
441
482
  passThroughOptions(passThrough = true) {
442
483
  this._passThroughOptions = !!passThrough;
443
484
  this._checkForBrokenPassThrough();
@@ -464,6 +505,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
464
505
  setOptionValue(key, value) {
465
506
  return this.setOptionValueWithSource(key, value, undefined);
466
507
  }
508
+ /** `source` is default | config | env | cli | implied. */
467
509
  setOptionValueWithSource(key, value, source) {
468
510
  if (this._storeOptionsAsProperties)
469
511
  this[key] = value;
@@ -475,6 +517,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
475
517
  getOptionValueSource(key) {
476
518
  return this._optionValueSources[key];
477
519
  }
520
+ /** Globals overwrite locals, like optsWithGlobals. */
478
521
  getOptionValueSourceWithGlobals(key) {
479
522
  let source;
480
523
  for (const cmd of this._getCommandAndAncestors()) {
@@ -483,6 +526,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
483
526
  }
484
527
  return source;
485
528
  }
529
+ /** User args from argv per `from`; sets `_scriptPath` and the default program name. */
486
530
  _prepareUserArgs(argv, parseOptions) {
487
531
  if (argv !== undefined && !Array.isArray(argv))
488
532
  throw new Error('first parameter to parse must be array or undefined');
@@ -492,7 +536,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
492
536
  parseOptions.from = 'electron';
493
537
  const execArgv = process.execArgv ?? [];
494
538
  if (execArgv.includes('-e') || execArgv.includes('--eval') || execArgv.includes('-p') || execArgv.includes('--print')) {
495
- parseOptions.from = 'eval';
539
+ parseOptions.from = 'eval'; // internal usage, not documented
496
540
  }
497
541
  }
498
542
  if (argv === undefined)
@@ -528,10 +572,17 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
528
572
  this._name = this._name || 'program';
529
573
  return userArgs;
530
574
  }
575
+ /**
576
+ * Parse argv, set options and run commands. Use `parseAsync` when an action is async.
577
+ * With no arguments, parses process.argv and auto-detects Electron and `node --eval`.
578
+ */
531
579
  parse(argv, parseOptions) {
532
580
  const from = this._prepareBurgee(parseOptions);
533
581
  this._prepareForParse();
534
582
  const userArgs = this._prepareUserArgs(argv, from);
583
+ // The surface check is synchronous unless a surface is actually served (completions,
584
+ // --mcp), so a synchronous action has run by the time parse() returns — commander's
585
+ // contract, which its suite asserts on after every parse(). commander-sync.test.ts.
535
586
  this._runBurgee(() => {
536
587
  const served = this._burgeeSurface(userArgs);
537
588
  if (isThenable(served))
@@ -544,6 +595,8 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
544
595
  const from = this._prepareBurgee(parseOptions);
545
596
  this._prepareForParse();
546
597
  const userArgs = this._prepareUserArgs(argv, from);
598
+ // Same synchronous start as parse(): a preAction hook has run before the promise is
599
+ // handed back, which commander's hook tests assert on.
547
600
  await this._runBurgee(() => {
548
601
  const served = this._burgeeSurface(userArgs);
549
602
  if (isThenable(served))
@@ -554,6 +607,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
554
607
  }
555
608
  _prepareForParse() {
556
609
  if (this._savedState === null) {
610
+ // Lone negated option (--no-foo without --foo) defaults to true, now that all options are known.
557
611
  for (const option of this.options) {
558
612
  if (option.negate && option.defaultValue === undefined && this.getOptionValue(option.attributeName()) === undefined) {
559
613
  const positiveLongFlag = (option.long ?? '').replace(/^--no-/, '--');
@@ -567,6 +621,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
567
621
  this.restoreStateBeforeParse();
568
622
  }
569
623
  }
624
+ /** Called lazily on first parse; available for subclasses to save custom state. */
570
625
  saveStateBeforeParse() {
571
626
  this._savedState = {
572
627
  _name: this._name,
@@ -612,6 +667,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
612
667
  const foundExt = SOURCE_EXT.find((ext) => fs.existsSync(`${localBin}${ext}`));
613
668
  return foundExt ? `${localBin}${foundExt}` : undefined;
614
669
  };
670
+ // Not checking for help first: can't robustly test for help flags in an external command.
615
671
  this._checkForMissingMandatoryOptions();
616
672
  this._checkForConflictingOptions();
617
673
  let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
@@ -628,6 +684,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
628
684
  }
629
685
  if (executableDir) {
630
686
  let localFile = findFile(executableDir, executableFile);
687
+ // Legacy search using the script name as prefix instead of the command name.
631
688
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
632
689
  const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
633
690
  if (legacyName !== this._name)
@@ -654,6 +711,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
654
711
  proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
655
712
  }
656
713
  if (!proc.killed) {
714
+ // Testing mainly to avoid leak warnings during unit tests with mocked spawn.
657
715
  for (const signal of FORWARDED_SIGNALS) {
658
716
  process.on(signal, () => {
659
717
  if (proc.killed === false && proc.exitCode === null)
@@ -663,7 +721,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
663
721
  }
664
722
  const exitCallback = this._exitCallback;
665
723
  proc.on('close', (code) => {
666
- code = code ?? 1;
724
+ code = code ?? 1; // null when the spawned process terminated due to a signal
667
725
  if (!exitCallback)
668
726
  process.exit(code);
669
727
  else
@@ -703,12 +761,14 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
703
761
  });
704
762
  return promiseChain;
705
763
  }
764
+ /** `help foo`: invoke help directly if possible, or dispatch if necessary. */
706
765
  _dispatchHelpCommand(subcommandName) {
707
766
  if (!subcommandName)
708
767
  this.help();
709
768
  const subCommand = this._findCommand(subcommandName);
710
769
  if (subCommand && !subCommand._executableHandler)
711
770
  subCommand.help();
771
+ // Fallback to parsing the help flag to invoke the help.
712
772
  return this._dispatchSubcommand(subcommandName ?? '', [], [
713
773
  this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help',
714
774
  ]);
@@ -724,6 +784,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
724
784
  if (this.args.length > this.registeredArguments.length)
725
785
  this._excessArguments(this.args);
726
786
  }
787
+ /** Process this.args against registeredArguments into this.processedArgs. */
727
788
  _processArguments() {
728
789
  const myParseArg = (argument, value, previous) => {
729
790
  let parsedValue = value;
@@ -756,6 +817,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
756
817
  });
757
818
  this.processedArgs = processedArgs;
758
819
  }
820
+ /** Chain once we have a promise; call synchronously until then. */
759
821
  _chainOrCall(promise, fn) {
760
822
  if (isThenable(promise))
761
823
  return promise.then(() => fn());
@@ -782,9 +844,10 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
782
844
  }
783
845
  return result;
784
846
  }
847
+ /** Process arguments in the context of this command; returns the action result in case it is a promise. */
785
848
  _parseCommand(operands, unknown) {
786
849
  const parsed = this.parseOptions(unknown);
787
- this._parseOptionsEnv();
850
+ this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
788
851
  this._parseOptionsImplied();
789
852
  operands = operands.concat(parsed.operands);
790
853
  unknown = parsed.unknown;
@@ -799,15 +862,17 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
799
862
  return this._dispatchHelpCommand(operands[1]);
800
863
  }
801
864
  if (this._defaultCommandName) {
802
- this._outputHelpIfRequested(unknown);
865
+ this._outputHelpIfRequested(unknown); // help for the default command comes from the parent
803
866
  return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
804
867
  }
805
868
  if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
869
+ // probably missing subcommand and no handler, user needs help (and exit)
806
870
  this.help({ error: true });
807
871
  }
808
872
  this._outputHelpIfRequested(parsed.unknown);
809
873
  this._checkForMissingMandatoryOptions();
810
874
  this._checkForConflictingOptions();
875
+ // Not always called, to avoid masking a "better" error like unknown command.
811
876
  const checkForUnknownOptions = () => {
812
877
  if (parsed.unknown.length > 0)
813
878
  this.unknownOption(parsed.unknown[0] ?? '');
@@ -821,7 +886,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
821
886
  promiseChain = this._chainOrCall(promiseChain, () => this._runAction());
822
887
  if (this.parent) {
823
888
  promiseChain = this._chainOrCall(promiseChain, () => {
824
- this.parent?.emit(commandEvent, operands, unknown);
889
+ this.parent?.emit(commandEvent, operands, unknown); // legacy
825
890
  });
826
891
  }
827
892
  promiseChain = this._chainOrCallHooks(promiseChain, 'postAction');
@@ -830,13 +895,15 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
830
895
  if (this.parent?.listenerCount(commandEvent)) {
831
896
  checkForUnknownOptions();
832
897
  this._processArguments();
833
- this.parent.emit(commandEvent, operands, unknown);
898
+ this.parent.emit(commandEvent, operands, unknown); // legacy
834
899
  }
835
900
  else if (operands.length) {
836
901
  if (this._findCommand('*')) {
902
+ // legacy default command
837
903
  return this._dispatchSubcommand('*', operands, unknown);
838
904
  }
839
905
  if (this.listenerCount('command:*')) {
906
+ // skip option check, emit event for possible misspelling suggestion
840
907
  this.emit('command:*', operands, unknown);
841
908
  }
842
909
  else if (this.commands.length) {
@@ -849,11 +916,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
849
916
  }
850
917
  else if (this.commands.length) {
851
918
  checkForUnknownOptions();
919
+ // This command has subcommands and nothing hooked up at this level, so display help (and exit).
852
920
  this.help({ error: true });
853
921
  }
854
922
  else {
855
923
  checkForUnknownOptions();
856
924
  this._processArguments();
925
+ // fall through for caller to handle after calling .parse()
857
926
  }
858
927
  return undefined;
859
928
  }
@@ -865,6 +934,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
865
934
  _findOption(arg) {
866
935
  return this.options.find((option) => option.is(arg));
867
936
  }
937
+ /** Walks up the hierarchy so a subcommand can check after displaying help. */
868
938
  _checkForMissingMandatoryOptions() {
869
939
  for (const cmd of this._getCommandAndAncestors()) {
870
940
  for (const anOption of cmd.options) {
@@ -892,6 +962,15 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
892
962
  for (const cmd of this._getCommandAndAncestors())
893
963
  cmd._checkForConflictingLocalOptions();
894
964
  }
965
+ /**
966
+ * Parse options from `args`, removing known options, and return argv split into
967
+ * operands and unknown arguments. Side effect: stores option values on the command.
968
+ *
969
+ * --known kkk op => [op], []
970
+ * op --known kkk => [op], []
971
+ * sub --unknown uuu op => [sub], [--unknown uuu op]
972
+ * sub -- --unknown uuu op => [sub --unknown uuu op], []
973
+ */
895
974
  parseOptions(args) {
896
975
  const operands = [];
897
976
  const unknown = [];
@@ -900,10 +979,11 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
900
979
  const negativeNumberArg = (arg) => {
901
980
  if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg))
902
981
  return false;
982
+ // a negative number is ok unless a digit is used as an option in the command hierarchy
903
983
  return !this._getCommandAndAncestors().some((cmd) => cmd.options.some((opt) => /^-\d$/.test(opt.short ?? '')));
904
984
  };
905
985
  let activeVariadicOption = null;
906
- let activeGroup = null;
986
+ let activeGroup = null; // working through a group of short options, like -abc
907
987
  let i = 0;
908
988
  while (i < args.length || activeGroup) {
909
989
  const arg = activeGroup ?? args[i++] ?? '';
@@ -930,6 +1010,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
930
1010
  }
931
1011
  else if (option.optional) {
932
1012
  let value = null;
1013
+ // historical behaviour: the optional value is the following arg unless it is an option
933
1014
  const next = args[i];
934
1015
  if (i < args.length && next !== undefined && (!maybeOption(next) || negativeNumberArg(next))) {
935
1016
  value = next;
@@ -944,6 +1025,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
944
1025
  continue;
945
1026
  }
946
1027
  }
1028
+ // Combined short options: eat the first one if known.
947
1029
  if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
948
1030
  const option = this._findOption(`-${arg[1]}`);
949
1031
  if (option) {
@@ -957,6 +1039,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
957
1039
  continue;
958
1040
  }
959
1041
  }
1042
+ // Known long flag with value, like --foo=bar
960
1043
  if (/^--[^=]+=/.test(arg)) {
961
1044
  const index = arg.indexOf('=');
962
1045
  const option = this._findOption(arg.slice(0, index));
@@ -965,9 +1048,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
965
1048
  continue;
966
1049
  }
967
1050
  }
1051
+ // Not recognised by this command: command-argument, subcommand option, unknown option, or help.
1052
+ // An unknown option makes everything after it unknown too, for a subcommand to reprocess.
1053
+ // A negative number in a leaf command is not an unknown option.
968
1054
  if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) {
969
1055
  dest = unknown;
970
1056
  }
1057
+ // Positional options: stop processing our options at a subcommand.
971
1058
  if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
972
1059
  if (this._findCommand(arg)) {
973
1060
  operands.push(arg);
@@ -983,6 +1070,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
983
1070
  break;
984
1071
  }
985
1072
  }
1073
+ // Pass-through options: stop processing options at the first command-argument.
986
1074
  if (this._passThroughOptions) {
987
1075
  dest.push(arg, ...args.slice(i));
988
1076
  break;
@@ -991,6 +1079,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
991
1079
  }
992
1080
  return { operands, unknown };
993
1081
  }
1082
+ /** Local option values as key-value pairs. */
994
1083
  opts() {
995
1084
  if (this._storeOptionsAsProperties) {
996
1085
  const result = {};
@@ -1002,9 +1091,11 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1002
1091
  }
1003
1092
  return this._optionValues;
1004
1093
  }
1094
+ /** Merged local and global option values; globals overwrite locals. */
1005
1095
  optsWithGlobals() {
1006
1096
  return this._getCommandAndAncestors().reduce((combined, cmd) => Object.assign(combined, cmd.opts()), {});
1007
1097
  }
1098
+ /** Display an error message and exit (or call exitOverride). */
1008
1099
  error(message, errorOptions) {
1009
1100
  this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
1010
1101
  if (typeof this._showHelpAfterError === 'string') {
@@ -1019,10 +1110,12 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1019
1110
  const code = config.code || 'commander.error';
1020
1111
  this._exit(exitCode, code, message);
1021
1112
  }
1113
+ /** Apply environment variables to options that have no value from the cli or client code. */
1022
1114
  _parseOptionsEnv() {
1023
1115
  for (const option of this.options) {
1024
1116
  if (option.envVar && option.envVar in process.env) {
1025
1117
  const optionKey = option.attributeName();
1118
+ // Do not overwrite cli values or values from an unknown (client-code) source.
1026
1119
  if (this.getOptionValue(optionKey) === undefined || ENV_SOURCES.includes(this.getOptionValueSource(optionKey) ?? '')) {
1027
1120
  if (option.required || option.optional)
1028
1121
  this.emit(`optionEnv:${option.name()}`, process.env[option.envVar]);
@@ -1032,6 +1125,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1032
1125
  }
1033
1126
  }
1034
1127
  }
1128
+ /** Apply implied option values where the option is undefined or at its default. */
1035
1129
  _parseOptionsImplied() {
1036
1130
  const dualHelper = new DualOptions(this.options);
1037
1131
  const hasCustomOptionValue = (optionKey) => this.getOptionValue(optionKey) !== undefined && !IMPLIED_SOURCES.includes(this.getOptionValueSource(optionKey) ?? '');
@@ -1055,6 +1149,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1055
1149
  this.error(`error: required option '${option.flags}' not specified`, { code: 'commander.missingMandatoryOptionValue' });
1056
1150
  }
1057
1151
  _conflictingOption(option, conflictingOption) {
1152
+ // The caller does not know whether a negated option is the source of the value; take an educated guess.
1058
1153
  const findBestOptionFromValue = (candidate) => {
1059
1154
  const optionKey = candidate.attributeName();
1060
1155
  const optionValue = this.getOptionValue(optionKey);
@@ -1082,6 +1177,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1082
1177
  return;
1083
1178
  let suggestion = '';
1084
1179
  if (flag.startsWith('--') && this._showSuggestionAfterError) {
1180
+ // Looping to pick up the global options too.
1085
1181
  let candidateFlags = [];
1086
1182
  let command = this;
1087
1183
  do {
@@ -1158,6 +1254,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1158
1254
  let command = this;
1159
1255
  const last = this.commands[this.commands.length - 1];
1160
1256
  if (this.commands.length !== 0 && last?._executableHandler) {
1257
+ // assume adding an alias for the last added executable subcommand, rather than this
1161
1258
  command = last;
1162
1259
  }
1163
1260
  if (alias === command._name)
@@ -1221,6 +1318,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1221
1318
  if (this._defaultCommandGroup && !cmd.helpGroup())
1222
1319
  cmd.helpGroup(this._defaultCommandGroup);
1223
1320
  }
1321
+ /** Name the command from a script filename, such as process.argv[1] or import.meta.filename. */
1224
1322
  nameFromFilename(filename) {
1225
1323
  this._name = path.basename(filename, path.extname(filename));
1226
1324
  return this;
@@ -1267,6 +1365,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1267
1365
  };
1268
1366
  return { error, write, hasColors, helpWidth };
1269
1367
  }
1368
+ /** Output built-in help plus any text added with `addHelpText`. */
1270
1369
  outputHelp(contextOptions) {
1271
1370
  let deprecatedCallback;
1272
1371
  if (typeof contextOptions === 'function') {
@@ -1288,16 +1387,17 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1288
1387
  outputContext.write(String(helpInformation));
1289
1388
  const helpLong = this._getHelpOption()?.long;
1290
1389
  if (helpLong)
1291
- this.emit(helpLong);
1390
+ this.emit(helpLong); // deprecated
1292
1391
  this.emit('afterHelp', eventContext);
1293
1392
  for (const command of this._getCommandAndAncestors())
1294
1393
  command.emit('afterAllHelp', eventContext);
1295
1394
  }
1395
+ /** Customise the built-in help option, or pass false to disable it. */
1296
1396
  helpOption(flags, description) {
1297
1397
  if (typeof flags === 'boolean') {
1298
1398
  if (flags) {
1299
1399
  if (this._helpOption === null)
1300
- this._helpOption = undefined;
1400
+ this._helpOption = undefined; // reenable
1301
1401
  if (this._defaultOptionGroup) {
1302
1402
  const helpOption = this._getHelpOption();
1303
1403
  if (helpOption)
@@ -1305,7 +1405,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1305
1405
  }
1306
1406
  }
1307
1407
  else {
1308
- this._helpOption = null;
1408
+ this._helpOption = null; // disable
1309
1409
  }
1310
1410
  return this;
1311
1411
  }
@@ -1314,6 +1414,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1314
1414
  this._initOptionGroup(this._helpOption);
1315
1415
  return this;
1316
1416
  }
1417
+ /** Lazily created; null once disabled with `helpOption(false)`. */
1317
1418
  _getHelpOption() {
1318
1419
  if (this._helpOption === undefined)
1319
1420
  this.helpOption(undefined, undefined);
@@ -1324,13 +1425,16 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1324
1425
  this._initOptionGroup(option);
1325
1426
  return this;
1326
1427
  }
1428
+ /** Output help and exit. */
1327
1429
  help(contextOptions) {
1328
1430
  this.outputHelp(contextOptions);
1329
1431
  let exitCode = Number(process.exitCode ?? 0);
1330
1432
  if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error)
1331
1433
  exitCode = 1;
1434
+ // message: not all displayed text is available, so only a placeholder is passed.
1332
1435
  this._exit(exitCode, 'commander.help', '(outputHelp)');
1333
1436
  }
1437
+ /** Extra help text: 'before'/'after' for this command, 'beforeAll'/'afterAll' for its subcommands too. */
1334
1438
  addHelpText(position, text) {
1335
1439
  if (!HELP_POSITIONS.includes(position)) {
1336
1440
  throw new Error(`Unexpected value for position to addHelpText.
@@ -1350,12 +1454,17 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1350
1454
  this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
1351
1455
  }
1352
1456
  }
1457
+ // ───── burgee: the manifest projection, plugins, `--json` and the injected seam ─────
1353
1458
  _root() {
1354
1459
  let command = this;
1355
1460
  while (command.parent)
1356
1461
  command = command.parent;
1357
1462
  return command;
1358
1463
  }
1464
+ /**
1465
+ * The manifest every surface reads. Projected from the command tree on each access,
1466
+ * so it is never stale; plugin-contributed nodes are kept across projections.
1467
+ */
1359
1468
  get manifest() {
1360
1469
  const root = this._root();
1361
1470
  root._manifest ??= new Manifest();
@@ -1375,6 +1484,8 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1375
1484
  ...(cmd._description ? { description: cmd._description } : {}),
1376
1485
  ...(cmd._summary ? { summary: cmd._summary } : {}),
1377
1486
  ...(cmd._effects === undefined ? {} : { effects: cmd._effects }),
1487
+ ...(cmd._deprecated === undefined ? {} : { deprecated: cmd._deprecated }),
1488
+ ...(cmd._helpGroupHeading === undefined || cmd._helpGroupHeading === '' ? {} : { group: cmd._helpGroupHeading }),
1378
1489
  ...(cmd._hidden ? { hidden: true } : {}),
1379
1490
  options: cmd._optionSpecs(),
1380
1491
  arguments: cmd.registeredArguments.map((a) => ({ name: a.name(), required: a.required, variadic: a.variadic, ...(a.description ? { description: a.description } : {}) })),
@@ -1385,6 +1496,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1385
1496
  };
1386
1497
  visit(this, [rootName]);
1387
1498
  }
1499
+ /** Options as the manifest describes them, on a null-prototype record. */
1388
1500
  _optionSpecs() {
1389
1501
  const specs = Object.create(null);
1390
1502
  for (const option of this.options) {
@@ -1403,17 +1515,33 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1403
1515
  }
1404
1516
  return specs;
1405
1517
  }
1518
+ /** burgee: declare what the command does to the world (N6). This is what exposes it as an MCP tool (N2). */
1406
1519
  effects(value) {
1407
1520
  this._effects = value;
1408
1521
  return this;
1409
1522
  }
1523
+ /**
1524
+ * burgee: mark the command deprecated (M5). Help and `--schema` show it; running it prints
1525
+ * `warning: 'old' is deprecated, use 'new'` on stderr once and goes on, exit unchanged.
1526
+ */
1527
+ deprecate(use) {
1528
+ this._deprecated = use ?? true;
1529
+ return this;
1530
+ }
1531
+ /**
1532
+ * burgee: `--schema` and `--mcp` on a commander-syntax program, from its manifest (J2).
1533
+ * Only when the program declares neither option itself; `--mcp` runs commands through
1534
+ * this very program with the streams captured, so tool results are the `--json` envelope.
1535
+ */
1410
1536
  _burgeeSurface(userArgs) {
1411
1537
  const root = this._root();
1538
+ // Any command in the tree that declares the flag keeps it: the surface is additive only.
1412
1539
  const declared = (flag, at = root) => at._findOption(flag) !== undefined || at.commands.some((sub) => declared(flag, sub));
1413
1540
  const terminator = userArgs.indexOf('--');
1414
1541
  const head = terminator === -1 ? userArgs : userArgs.slice(0, terminator);
1415
1542
  if (head[0] === 'completion' && root._findCommand('completion') === undefined) {
1416
- return import('./completions.js').then(({ renderCompletion, renderFigSpec, SHELLS }) => {
1543
+ // Loaded on this command only (K6), exactly as the engine does.
1544
+ return import('../completions.js').then(({ renderCompletion, renderFigSpec, SHELLS }) => {
1417
1545
  const shell = head[1] ?? '';
1418
1546
  if (shell === 'fig') {
1419
1547
  root._outputConfiguration.writeOut(`${JSON.stringify(renderFigSpec(this.manifest), null, 2)}\n`);
@@ -1429,10 +1557,13 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1429
1557
  }
1430
1558
  return this._burgeeSurfaceRest(head, declared);
1431
1559
  }
1560
+ /** The surfaces after `completion`: `--schema` is synchronous, `--mcp` serves until stdin closes. */
1432
1561
  _burgeeSurfaceRest(head, declared) {
1433
1562
  const root = this._root();
1434
1563
  if (head.includes('--schema') && !declared('--schema')) {
1435
- root._outputConfiguration.writeOut(`${JSON.stringify(schemaOf(this.manifest), null, 2)}\n`);
1564
+ // R1, and the same escape hatch the engine has: `--schema` is burgee's surface, not
1565
+ // commander's, so it answers to E-floor byte discipline rather than to the host.
1566
+ root._outputConfiguration.writeOut(`${machineJson(schemaOf(this.manifest), head)}\n`);
1436
1567
  return true;
1437
1568
  }
1438
1569
  if (head[0] === '--mcp' && !declared('--mcp')) {
@@ -1447,10 +1578,12 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1447
1578
  }
1448
1579
  return false;
1449
1580
  }
1581
+ /** Additive, and the point of the whole exercise: plugins commander has never had (#2505, unlanded). */
1450
1582
  use(plugin) {
1451
1583
  this.manifest.use(plugin);
1452
1584
  return this;
1453
1585
  }
1586
+ /** Inject the streams and the exit for one parse; returns commander's own parse options. */
1454
1587
  _prepareBurgee(parseOptions) {
1455
1588
  if (parseOptions === undefined)
1456
1589
  return undefined;
@@ -1477,6 +1610,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1477
1610
  visit(root);
1478
1611
  return from;
1479
1612
  }
1613
+ /** In burgee mode the whole run settles to one E1 exit; otherwise commander's behaviour, untouched. */
1480
1614
  _runBurgee(run) {
1481
1615
  const root = this._root();
1482
1616
  const burgee = root._burgee;
@@ -1513,6 +1647,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1513
1647
  return undefined;
1514
1648
  }
1515
1649
  }
1650
+ /** `--json` that no command in the chain declared is burgee's envelope, not an unknown option. */
1516
1651
  _takeJson(unknown) {
1517
1652
  const terminator = unknown.indexOf('--');
1518
1653
  const index = unknown.indexOf('--json');
@@ -1525,10 +1660,12 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1525
1660
  root._burgee ??= { exit: undefined, json: false };
1526
1661
  root._burgee.json = true;
1527
1662
  }
1663
+ /** The action, wrapped in the plugin hooks and followed by the envelope or the rendering. */
1528
1664
  _runAction() {
1529
1665
  const handler = this._actionHandler;
1530
1666
  if (handler === null)
1531
1667
  return undefined;
1668
+ this._warnDeprecated();
1532
1669
  const root = this._root();
1533
1670
  const manifest = root._manifest;
1534
1671
  const settle = (value) => this._emitResult(value);
@@ -1546,6 +1683,15 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1546
1683
  settle(value);
1547
1684
  });
1548
1685
  }
1686
+ /** burgee (M5): once per process, on stderr; only for a command that asked, so commander's own output is untouched. */
1687
+ _warnDeprecated() {
1688
+ if (this._deprecated === undefined || this._deprecated === false || this._deprecationWarned)
1689
+ return;
1690
+ this._deprecationWarned = true;
1691
+ const use = typeof this._deprecated === 'string' ? `, use '${this._deprecated}'` : '';
1692
+ this._outputConfiguration.writeErr(`warning: '${this.name()}' is deprecated${use}\n`);
1693
+ }
1694
+ /** burgee: where every option value came from, from commander's own value sources (V3). */
1549
1695
  _provenance() {
1550
1696
  const out = {};
1551
1697
  const names = { cli: 'flag', env: 'env', config: 'config', default: 'default', implied: 'implied' };
@@ -1568,7 +1714,9 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1568
1714
  this._outputConfiguration.writeOut(text);
1569
1715
  }
1570
1716
  }
1717
+ /** Bump inspector ports so a spawned subcommand does not collide with the parent. */
1571
1718
  function incrementNodeInspectorPort(args) {
1719
+ // --inspect[=[host:]port], --inspect-brk[=[host:]port], --inspect-port=[host:]port
1572
1720
  return args.map((arg) => {
1573
1721
  if (!arg.startsWith('--inspect'))
1574
1722
  return arg;
@@ -1596,6 +1744,10 @@ function incrementNodeInspectorPort(args) {
1596
1744
  return arg;
1597
1745
  });
1598
1746
  }
1747
+ /**
1748
+ * The common colour conventions: NO_COLOR and FORCE_COLOR=0/false disable, FORCE_COLOR
1749
+ * and CLICOLOR_FORCE enable, otherwise undecided (the stream's TTY-ness decides).
1750
+ */
1599
1751
  export function useColor() {
1600
1752
  if (process.env['NO_COLOR'] || process.env['FORCE_COLOR'] === '0' || process.env['FORCE_COLOR'] === 'false')
1601
1753
  return false;
@@ -1603,3 +1755,4 @@ export function useColor() {
1603
1755
  return true;
1604
1756
  return undefined;
1605
1757
  }
1758
+ //# sourceMappingURL=command.js.map