burgee 0.3.0 → 0.5.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 (53) 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/{commander-argument.js → commander/argument.js} +4 -1
  6. package/dist/{commander-command.d.ts → commander/command.d.ts} +5 -5
  7. package/dist/{commander-command.js → commander/command.js} +159 -22
  8. package/dist/{commander-error.js → commander/error.js} +2 -0
  9. package/dist/{commander-help.d.ts → commander/help.d.ts} +3 -3
  10. package/dist/{commander-help.js → commander/help.js} +18 -1
  11. package/dist/{commander-option.d.ts → commander/option.d.ts} +1 -1
  12. package/dist/{commander-option.js → commander/option.js} +15 -1
  13. package/dist/commander.d.ts +8 -8
  14. package/dist/commander.js +8 -8
  15. package/dist/completions.js +15 -4
  16. package/dist/contrast.d.ts +9 -11
  17. package/dist/contrast.js +2 -33
  18. package/dist/execute.js +39 -24
  19. package/dist/schema.d.ts +34 -0
  20. package/dist/schema.js +12 -0
  21. package/dist/unknown-option.d.ts +9 -0
  22. package/dist/unknown-option.js +27 -0
  23. package/dist/{yargs-burgee.d.ts → yargs/burgee.d.ts} +2 -2
  24. package/dist/{yargs-burgee.js → yargs/burgee.js} +15 -1
  25. package/dist/{yargs-cliui.js → yargs/cliui.js} +8 -0
  26. package/dist/{yargs-command.d.ts → yargs/command.d.ts} +5 -5
  27. package/dist/{yargs-command.js → yargs/command.js} +9 -2
  28. package/dist/{yargs-completion.d.ts → yargs/completion.d.ts} +3 -3
  29. package/dist/{yargs-completion.js → yargs/completion.js} +7 -2
  30. package/dist/{yargs-factory.d.ts → yargs/factory.d.ts} +8 -8
  31. package/dist/{yargs-factory.js → yargs/factory.js} +63 -13
  32. package/dist/{yargs-middleware.js → yargs/middleware.js} +6 -1
  33. package/dist/{yargs-shim.d.ts → yargs/shim.d.ts} +3 -3
  34. package/dist/{yargs-shim.js → yargs/shim.js} +24 -5
  35. package/dist/{yargs-usage.d.ts → yargs/usage.d.ts} +1 -1
  36. package/dist/{yargs-usage.js → yargs/usage.js} +9 -1
  37. package/dist/{yargs-utils.js → yargs/utils.js} +11 -0
  38. package/dist/{yargs-validation.d.ts → yargs/validation.d.ts} +1 -1
  39. package/dist/{yargs-validation.js → yargs/validation.js} +7 -1
  40. package/dist/{yargs-y18n.js → yargs/y18n.js} +6 -0
  41. package/dist/yargs-helpers.d.ts +1 -1
  42. package/dist/yargs-helpers.js +1 -1
  43. package/dist/yargs.d.ts +4 -4
  44. package/dist/yargs.js +5 -5
  45. package/package.json +5 -1
  46. /package/dist/{commander-argument.d.ts → commander/argument.d.ts} +0 -0
  47. /package/dist/{commander-error.d.ts → commander/error.d.ts} +0 -0
  48. /package/dist/{commander-suggest.d.ts → suggest.d.ts} +0 -0
  49. /package/dist/{commander-suggest.js → suggest.js} +0 -0
  50. /package/dist/{yargs-cliui.d.ts → yargs/cliui.d.ts} +0 -0
  51. /package/dist/{yargs-middleware.d.ts → yargs/middleware.d.ts} +0 -0
  52. /package/dist/{yargs-utils.d.ts → yargs/utils.d.ts} +0 -0
  53. /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,10 +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. */
101
126
  _deprecated = undefined;
102
127
  _deprecationWarned = false;
128
+ /** burgee: set for the duration of a parse that injected the streams or the exit. */
103
129
  _burgee = undefined;
104
130
  constructor(name) {
105
131
  super();
@@ -116,6 +142,7 @@ export class Command extends EventEmitter {
116
142
  stripColor: (str) => stripVTControlCharacters(str),
117
143
  };
118
144
  }
145
+ /** Copy settings useful to share between the root and its subcommands. */
119
146
  copyInheritedSettings(sourceCommand) {
120
147
  this._outputConfiguration = sourceCommand._outputConfiguration;
121
148
  this._helpOption = sourceCommand._helpOption;
@@ -163,6 +190,7 @@ export class Command extends EventEmitter {
163
190
  return this;
164
191
  return cmd;
165
192
  }
193
+ /** Factory for an unattached command; override to customise subcommands. */
166
194
  createCommand(name) {
167
195
  return new Command(name);
168
196
  }
@@ -234,6 +262,7 @@ export class Command extends EventEmitter {
234
262
  this.registeredArguments.push(argument);
235
263
  return this;
236
264
  }
265
+ /** Customise or disable the default help command (added by default when there are subcommands). */
237
266
  helpCommand(enableOrNameAndArgs, description) {
238
267
  if (typeof enableOrNameAndArgs === 'boolean') {
239
268
  this._addImplicitHelpCommand = enableOrNameAndArgs;
@@ -290,21 +319,25 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
290
319
  this._lifeCycleHooks[event] = [listener];
291
320
  return this;
292
321
  }
322
+ /** Replace the call to process.exit; defaults to throwing the CommanderError. */
293
323
  exitOverride(fn) {
294
324
  this._exitCallback =
295
325
  fn ??
296
326
  ((err) => {
297
327
  if (err.code !== 'commander.executeSubCommandAsync')
298
328
  throw err;
329
+ // Async callback from spawn events, not useful to throw.
299
330
  });
300
331
  return this;
301
332
  }
302
333
  _exit(exitCode, code, message) {
303
334
  if (this._exitCallback) {
304
335
  this._exitCallback(new CommanderError(exitCode, code, message));
336
+ // Expecting this line is not reached.
305
337
  }
306
338
  process.exit(exitCode);
307
339
  }
340
+ // commander's contract: the positional args, then the options, then the command itself.
308
341
  action(fn) {
309
342
  const listener = (args) => {
310
343
  const expectedArgsCount = this.registeredArguments.length;
@@ -319,6 +352,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
319
352
  createOption(flags, description) {
320
353
  return new Option(flags, description);
321
354
  }
355
+ /** Wrap parseArg to turn `commander.invalidArgument` into an error with context. */
322
356
  _callParseArg(target, value, previous, invalidArgumentMessage) {
323
357
  try {
324
358
  return target.parseArg?.(value, previous);
@@ -366,6 +400,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
366
400
  if (option.defaultValue !== undefined) {
367
401
  this.setOptionValueWithSource(name, option.defaultValue, 'default');
368
402
  }
403
+ // val is null for an optional option used without its argument, undefined for boolean and negated.
369
404
  const handleOptionValue = (val, invalidValueMessage, valueSource) => {
370
405
  let value = val;
371
406
  if (value == null && option.presetArg !== undefined)
@@ -383,7 +418,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
383
418
  else if (option.isBoolean() || option.optional)
384
419
  value = true;
385
420
  else
386
- value = '';
421
+ value = ''; // not normal, parseArg might have failed or be a mock function for testing
387
422
  }
388
423
  this.setOptionValueWithSource(name, value, valueSource);
389
424
  };
@@ -407,6 +442,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
407
442
  option.default(defaultValue).argParser(fn);
408
443
  }
409
444
  else if (fn instanceof RegExp) {
445
+ // deprecated
410
446
  const regex = fn;
411
447
  option.default(defaultValue).argParser((val, def) => {
412
448
  const m = regex.exec(val);
@@ -424,6 +460,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
424
460
  requiredOption(flags, description, parseArg, defaultValue) {
425
461
  return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
426
462
  }
463
+ /** `-f80` as `--flag=80` (default) versus `-fb` as `-f -b`. */
427
464
  combineFlagAndOptionalValue(combine = true) {
428
465
  this._combineFlagAndOptionalValue = !!combine;
429
466
  return this;
@@ -436,10 +473,12 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
436
473
  this._allowExcessArguments = !!allowExcess;
437
474
  return this;
438
475
  }
476
+ /** Global options before subcommands only, so subcommands may reuse option names. */
439
477
  enablePositionalOptions(positional = true) {
440
478
  this._enablePositionalOptions = !!positional;
441
479
  return this;
442
480
  }
481
+ /** Options after the first command-argument are passed through, not parsed. */
443
482
  passThroughOptions(passThrough = true) {
444
483
  this._passThroughOptions = !!passThrough;
445
484
  this._checkForBrokenPassThrough();
@@ -466,6 +505,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
466
505
  setOptionValue(key, value) {
467
506
  return this.setOptionValueWithSource(key, value, undefined);
468
507
  }
508
+ /** `source` is default | config | env | cli | implied. */
469
509
  setOptionValueWithSource(key, value, source) {
470
510
  if (this._storeOptionsAsProperties)
471
511
  this[key] = value;
@@ -477,6 +517,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
477
517
  getOptionValueSource(key) {
478
518
  return this._optionValueSources[key];
479
519
  }
520
+ /** Globals overwrite locals, like optsWithGlobals. */
480
521
  getOptionValueSourceWithGlobals(key) {
481
522
  let source;
482
523
  for (const cmd of this._getCommandAndAncestors()) {
@@ -485,6 +526,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
485
526
  }
486
527
  return source;
487
528
  }
529
+ /** User args from argv per `from`; sets `_scriptPath` and the default program name. */
488
530
  _prepareUserArgs(argv, parseOptions) {
489
531
  if (argv !== undefined && !Array.isArray(argv))
490
532
  throw new Error('first parameter to parse must be array or undefined');
@@ -494,7 +536,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
494
536
  parseOptions.from = 'electron';
495
537
  const execArgv = process.execArgv ?? [];
496
538
  if (execArgv.includes('-e') || execArgv.includes('--eval') || execArgv.includes('-p') || execArgv.includes('--print')) {
497
- parseOptions.from = 'eval';
539
+ parseOptions.from = 'eval'; // internal usage, not documented
498
540
  }
499
541
  }
500
542
  if (argv === undefined)
@@ -530,10 +572,17 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
530
572
  this._name = this._name || 'program';
531
573
  return userArgs;
532
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
+ */
533
579
  parse(argv, parseOptions) {
534
580
  const from = this._prepareBurgee(parseOptions);
535
581
  this._prepareForParse();
536
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.
537
586
  this._runBurgee(() => {
538
587
  const served = this._burgeeSurface(userArgs);
539
588
  if (isThenable(served))
@@ -546,6 +595,8 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
546
595
  const from = this._prepareBurgee(parseOptions);
547
596
  this._prepareForParse();
548
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.
549
600
  await this._runBurgee(() => {
550
601
  const served = this._burgeeSurface(userArgs);
551
602
  if (isThenable(served))
@@ -556,6 +607,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
556
607
  }
557
608
  _prepareForParse() {
558
609
  if (this._savedState === null) {
610
+ // Lone negated option (--no-foo without --foo) defaults to true, now that all options are known.
559
611
  for (const option of this.options) {
560
612
  if (option.negate && option.defaultValue === undefined && this.getOptionValue(option.attributeName()) === undefined) {
561
613
  const positiveLongFlag = (option.long ?? '').replace(/^--no-/, '--');
@@ -569,6 +621,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
569
621
  this.restoreStateBeforeParse();
570
622
  }
571
623
  }
624
+ /** Called lazily on first parse; available for subclasses to save custom state. */
572
625
  saveStateBeforeParse() {
573
626
  this._savedState = {
574
627
  _name: this._name,
@@ -614,6 +667,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
614
667
  const foundExt = SOURCE_EXT.find((ext) => fs.existsSync(`${localBin}${ext}`));
615
668
  return foundExt ? `${localBin}${foundExt}` : undefined;
616
669
  };
670
+ // Not checking for help first: can't robustly test for help flags in an external command.
617
671
  this._checkForMissingMandatoryOptions();
618
672
  this._checkForConflictingOptions();
619
673
  let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
@@ -630,6 +684,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
630
684
  }
631
685
  if (executableDir) {
632
686
  let localFile = findFile(executableDir, executableFile);
687
+ // Legacy search using the script name as prefix instead of the command name.
633
688
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
634
689
  const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
635
690
  if (legacyName !== this._name)
@@ -656,6 +711,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
656
711
  proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
657
712
  }
658
713
  if (!proc.killed) {
714
+ // Testing mainly to avoid leak warnings during unit tests with mocked spawn.
659
715
  for (const signal of FORWARDED_SIGNALS) {
660
716
  process.on(signal, () => {
661
717
  if (proc.killed === false && proc.exitCode === null)
@@ -665,7 +721,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
665
721
  }
666
722
  const exitCallback = this._exitCallback;
667
723
  proc.on('close', (code) => {
668
- code = code ?? 1;
724
+ code = code ?? 1; // null when the spawned process terminated due to a signal
669
725
  if (!exitCallback)
670
726
  process.exit(code);
671
727
  else
@@ -705,12 +761,14 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
705
761
  });
706
762
  return promiseChain;
707
763
  }
764
+ /** `help foo`: invoke help directly if possible, or dispatch if necessary. */
708
765
  _dispatchHelpCommand(subcommandName) {
709
766
  if (!subcommandName)
710
767
  this.help();
711
768
  const subCommand = this._findCommand(subcommandName);
712
769
  if (subCommand && !subCommand._executableHandler)
713
770
  subCommand.help();
771
+ // Fallback to parsing the help flag to invoke the help.
714
772
  return this._dispatchSubcommand(subcommandName ?? '', [], [
715
773
  this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help',
716
774
  ]);
@@ -726,6 +784,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
726
784
  if (this.args.length > this.registeredArguments.length)
727
785
  this._excessArguments(this.args);
728
786
  }
787
+ /** Process this.args against registeredArguments into this.processedArgs. */
729
788
  _processArguments() {
730
789
  const myParseArg = (argument, value, previous) => {
731
790
  let parsedValue = value;
@@ -758,6 +817,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
758
817
  });
759
818
  this.processedArgs = processedArgs;
760
819
  }
820
+ /** Chain once we have a promise; call synchronously until then. */
761
821
  _chainOrCall(promise, fn) {
762
822
  if (isThenable(promise))
763
823
  return promise.then(() => fn());
@@ -784,9 +844,10 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
784
844
  }
785
845
  return result;
786
846
  }
847
+ /** Process arguments in the context of this command; returns the action result in case it is a promise. */
787
848
  _parseCommand(operands, unknown) {
788
849
  const parsed = this.parseOptions(unknown);
789
- this._parseOptionsEnv();
850
+ this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
790
851
  this._parseOptionsImplied();
791
852
  operands = operands.concat(parsed.operands);
792
853
  unknown = parsed.unknown;
@@ -801,15 +862,17 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
801
862
  return this._dispatchHelpCommand(operands[1]);
802
863
  }
803
864
  if (this._defaultCommandName) {
804
- this._outputHelpIfRequested(unknown);
865
+ this._outputHelpIfRequested(unknown); // help for the default command comes from the parent
805
866
  return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
806
867
  }
807
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)
808
870
  this.help({ error: true });
809
871
  }
810
872
  this._outputHelpIfRequested(parsed.unknown);
811
873
  this._checkForMissingMandatoryOptions();
812
874
  this._checkForConflictingOptions();
875
+ // Not always called, to avoid masking a "better" error like unknown command.
813
876
  const checkForUnknownOptions = () => {
814
877
  if (parsed.unknown.length > 0)
815
878
  this.unknownOption(parsed.unknown[0] ?? '');
@@ -823,7 +886,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
823
886
  promiseChain = this._chainOrCall(promiseChain, () => this._runAction());
824
887
  if (this.parent) {
825
888
  promiseChain = this._chainOrCall(promiseChain, () => {
826
- this.parent?.emit(commandEvent, operands, unknown);
889
+ this.parent?.emit(commandEvent, operands, unknown); // legacy
827
890
  });
828
891
  }
829
892
  promiseChain = this._chainOrCallHooks(promiseChain, 'postAction');
@@ -832,13 +895,15 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
832
895
  if (this.parent?.listenerCount(commandEvent)) {
833
896
  checkForUnknownOptions();
834
897
  this._processArguments();
835
- this.parent.emit(commandEvent, operands, unknown);
898
+ this.parent.emit(commandEvent, operands, unknown); // legacy
836
899
  }
837
900
  else if (operands.length) {
838
901
  if (this._findCommand('*')) {
902
+ // legacy default command
839
903
  return this._dispatchSubcommand('*', operands, unknown);
840
904
  }
841
905
  if (this.listenerCount('command:*')) {
906
+ // skip option check, emit event for possible misspelling suggestion
842
907
  this.emit('command:*', operands, unknown);
843
908
  }
844
909
  else if (this.commands.length) {
@@ -851,11 +916,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
851
916
  }
852
917
  else if (this.commands.length) {
853
918
  checkForUnknownOptions();
919
+ // This command has subcommands and nothing hooked up at this level, so display help (and exit).
854
920
  this.help({ error: true });
855
921
  }
856
922
  else {
857
923
  checkForUnknownOptions();
858
924
  this._processArguments();
925
+ // fall through for caller to handle after calling .parse()
859
926
  }
860
927
  return undefined;
861
928
  }
@@ -867,6 +934,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
867
934
  _findOption(arg) {
868
935
  return this.options.find((option) => option.is(arg));
869
936
  }
937
+ /** Walks up the hierarchy so a subcommand can check after displaying help. */
870
938
  _checkForMissingMandatoryOptions() {
871
939
  for (const cmd of this._getCommandAndAncestors()) {
872
940
  for (const anOption of cmd.options) {
@@ -894,6 +962,15 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
894
962
  for (const cmd of this._getCommandAndAncestors())
895
963
  cmd._checkForConflictingLocalOptions();
896
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
+ */
897
974
  parseOptions(args) {
898
975
  const operands = [];
899
976
  const unknown = [];
@@ -902,10 +979,11 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
902
979
  const negativeNumberArg = (arg) => {
903
980
  if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg))
904
981
  return false;
982
+ // a negative number is ok unless a digit is used as an option in the command hierarchy
905
983
  return !this._getCommandAndAncestors().some((cmd) => cmd.options.some((opt) => /^-\d$/.test(opt.short ?? '')));
906
984
  };
907
985
  let activeVariadicOption = null;
908
- let activeGroup = null;
986
+ let activeGroup = null; // working through a group of short options, like -abc
909
987
  let i = 0;
910
988
  while (i < args.length || activeGroup) {
911
989
  const arg = activeGroup ?? args[i++] ?? '';
@@ -932,6 +1010,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
932
1010
  }
933
1011
  else if (option.optional) {
934
1012
  let value = null;
1013
+ // historical behaviour: the optional value is the following arg unless it is an option
935
1014
  const next = args[i];
936
1015
  if (i < args.length && next !== undefined && (!maybeOption(next) || negativeNumberArg(next))) {
937
1016
  value = next;
@@ -946,6 +1025,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
946
1025
  continue;
947
1026
  }
948
1027
  }
1028
+ // Combined short options: eat the first one if known.
949
1029
  if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
950
1030
  const option = this._findOption(`-${arg[1]}`);
951
1031
  if (option) {
@@ -959,6 +1039,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
959
1039
  continue;
960
1040
  }
961
1041
  }
1042
+ // Known long flag with value, like --foo=bar
962
1043
  if (/^--[^=]+=/.test(arg)) {
963
1044
  const index = arg.indexOf('=');
964
1045
  const option = this._findOption(arg.slice(0, index));
@@ -967,9 +1048,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
967
1048
  continue;
968
1049
  }
969
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.
970
1054
  if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) {
971
1055
  dest = unknown;
972
1056
  }
1057
+ // Positional options: stop processing our options at a subcommand.
973
1058
  if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
974
1059
  if (this._findCommand(arg)) {
975
1060
  operands.push(arg);
@@ -985,6 +1070,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
985
1070
  break;
986
1071
  }
987
1072
  }
1073
+ // Pass-through options: stop processing options at the first command-argument.
988
1074
  if (this._passThroughOptions) {
989
1075
  dest.push(arg, ...args.slice(i));
990
1076
  break;
@@ -993,6 +1079,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
993
1079
  }
994
1080
  return { operands, unknown };
995
1081
  }
1082
+ /** Local option values as key-value pairs. */
996
1083
  opts() {
997
1084
  if (this._storeOptionsAsProperties) {
998
1085
  const result = {};
@@ -1004,9 +1091,11 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1004
1091
  }
1005
1092
  return this._optionValues;
1006
1093
  }
1094
+ /** Merged local and global option values; globals overwrite locals. */
1007
1095
  optsWithGlobals() {
1008
1096
  return this._getCommandAndAncestors().reduce((combined, cmd) => Object.assign(combined, cmd.opts()), {});
1009
1097
  }
1098
+ /** Display an error message and exit (or call exitOverride). */
1010
1099
  error(message, errorOptions) {
1011
1100
  this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
1012
1101
  if (typeof this._showHelpAfterError === 'string') {
@@ -1021,10 +1110,12 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1021
1110
  const code = config.code || 'commander.error';
1022
1111
  this._exit(exitCode, code, message);
1023
1112
  }
1113
+ /** Apply environment variables to options that have no value from the cli or client code. */
1024
1114
  _parseOptionsEnv() {
1025
1115
  for (const option of this.options) {
1026
1116
  if (option.envVar && option.envVar in process.env) {
1027
1117
  const optionKey = option.attributeName();
1118
+ // Do not overwrite cli values or values from an unknown (client-code) source.
1028
1119
  if (this.getOptionValue(optionKey) === undefined || ENV_SOURCES.includes(this.getOptionValueSource(optionKey) ?? '')) {
1029
1120
  if (option.required || option.optional)
1030
1121
  this.emit(`optionEnv:${option.name()}`, process.env[option.envVar]);
@@ -1034,6 +1125,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1034
1125
  }
1035
1126
  }
1036
1127
  }
1128
+ /** Apply implied option values where the option is undefined or at its default. */
1037
1129
  _parseOptionsImplied() {
1038
1130
  const dualHelper = new DualOptions(this.options);
1039
1131
  const hasCustomOptionValue = (optionKey) => this.getOptionValue(optionKey) !== undefined && !IMPLIED_SOURCES.includes(this.getOptionValueSource(optionKey) ?? '');
@@ -1057,6 +1149,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1057
1149
  this.error(`error: required option '${option.flags}' not specified`, { code: 'commander.missingMandatoryOptionValue' });
1058
1150
  }
1059
1151
  _conflictingOption(option, conflictingOption) {
1152
+ // The caller does not know whether a negated option is the source of the value; take an educated guess.
1060
1153
  const findBestOptionFromValue = (candidate) => {
1061
1154
  const optionKey = candidate.attributeName();
1062
1155
  const optionValue = this.getOptionValue(optionKey);
@@ -1084,6 +1177,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1084
1177
  return;
1085
1178
  let suggestion = '';
1086
1179
  if (flag.startsWith('--') && this._showSuggestionAfterError) {
1180
+ // Looping to pick up the global options too.
1087
1181
  let candidateFlags = [];
1088
1182
  let command = this;
1089
1183
  do {
@@ -1160,6 +1254,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1160
1254
  let command = this;
1161
1255
  const last = this.commands[this.commands.length - 1];
1162
1256
  if (this.commands.length !== 0 && last?._executableHandler) {
1257
+ // assume adding an alias for the last added executable subcommand, rather than this
1163
1258
  command = last;
1164
1259
  }
1165
1260
  if (alias === command._name)
@@ -1223,6 +1318,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1223
1318
  if (this._defaultCommandGroup && !cmd.helpGroup())
1224
1319
  cmd.helpGroup(this._defaultCommandGroup);
1225
1320
  }
1321
+ /** Name the command from a script filename, such as process.argv[1] or import.meta.filename. */
1226
1322
  nameFromFilename(filename) {
1227
1323
  this._name = path.basename(filename, path.extname(filename));
1228
1324
  return this;
@@ -1269,6 +1365,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1269
1365
  };
1270
1366
  return { error, write, hasColors, helpWidth };
1271
1367
  }
1368
+ /** Output built-in help plus any text added with `addHelpText`. */
1272
1369
  outputHelp(contextOptions) {
1273
1370
  let deprecatedCallback;
1274
1371
  if (typeof contextOptions === 'function') {
@@ -1290,16 +1387,17 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1290
1387
  outputContext.write(String(helpInformation));
1291
1388
  const helpLong = this._getHelpOption()?.long;
1292
1389
  if (helpLong)
1293
- this.emit(helpLong);
1390
+ this.emit(helpLong); // deprecated
1294
1391
  this.emit('afterHelp', eventContext);
1295
1392
  for (const command of this._getCommandAndAncestors())
1296
1393
  command.emit('afterAllHelp', eventContext);
1297
1394
  }
1395
+ /** Customise the built-in help option, or pass false to disable it. */
1298
1396
  helpOption(flags, description) {
1299
1397
  if (typeof flags === 'boolean') {
1300
1398
  if (flags) {
1301
1399
  if (this._helpOption === null)
1302
- this._helpOption = undefined;
1400
+ this._helpOption = undefined; // reenable
1303
1401
  if (this._defaultOptionGroup) {
1304
1402
  const helpOption = this._getHelpOption();
1305
1403
  if (helpOption)
@@ -1307,7 +1405,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1307
1405
  }
1308
1406
  }
1309
1407
  else {
1310
- this._helpOption = null;
1408
+ this._helpOption = null; // disable
1311
1409
  }
1312
1410
  return this;
1313
1411
  }
@@ -1316,6 +1414,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1316
1414
  this._initOptionGroup(this._helpOption);
1317
1415
  return this;
1318
1416
  }
1417
+ /** Lazily created; null once disabled with `helpOption(false)`. */
1319
1418
  _getHelpOption() {
1320
1419
  if (this._helpOption === undefined)
1321
1420
  this.helpOption(undefined, undefined);
@@ -1326,13 +1425,16 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1326
1425
  this._initOptionGroup(option);
1327
1426
  return this;
1328
1427
  }
1428
+ /** Output help and exit. */
1329
1429
  help(contextOptions) {
1330
1430
  this.outputHelp(contextOptions);
1331
1431
  let exitCode = Number(process.exitCode ?? 0);
1332
1432
  if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error)
1333
1433
  exitCode = 1;
1434
+ // message: not all displayed text is available, so only a placeholder is passed.
1334
1435
  this._exit(exitCode, 'commander.help', '(outputHelp)');
1335
1436
  }
1437
+ /** Extra help text: 'before'/'after' for this command, 'beforeAll'/'afterAll' for its subcommands too. */
1336
1438
  addHelpText(position, text) {
1337
1439
  if (!HELP_POSITIONS.includes(position)) {
1338
1440
  throw new Error(`Unexpected value for position to addHelpText.
@@ -1352,12 +1454,17 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1352
1454
  this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
1353
1455
  }
1354
1456
  }
1457
+ // ───── burgee: the manifest projection, plugins, `--json` and the injected seam ─────
1355
1458
  _root() {
1356
1459
  let command = this;
1357
1460
  while (command.parent)
1358
1461
  command = command.parent;
1359
1462
  return command;
1360
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
+ */
1361
1468
  get manifest() {
1362
1469
  const root = this._root();
1363
1470
  root._manifest ??= new Manifest();
@@ -1389,6 +1496,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1389
1496
  };
1390
1497
  visit(this, [rootName]);
1391
1498
  }
1499
+ /** Options as the manifest describes them, on a null-prototype record. */
1392
1500
  _optionSpecs() {
1393
1501
  const specs = Object.create(null);
1394
1502
  for (const option of this.options) {
@@ -1407,21 +1515,33 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1407
1515
  }
1408
1516
  return specs;
1409
1517
  }
1518
+ /** burgee: declare what the command does to the world (N6). This is what exposes it as an MCP tool (N2). */
1410
1519
  effects(value) {
1411
1520
  this._effects = value;
1412
1521
  return this;
1413
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
+ */
1414
1527
  deprecate(use) {
1415
1528
  this._deprecated = use ?? true;
1416
1529
  return this;
1417
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
+ */
1418
1536
  _burgeeSurface(userArgs) {
1419
1537
  const root = this._root();
1538
+ // Any command in the tree that declares the flag keeps it: the surface is additive only.
1420
1539
  const declared = (flag, at = root) => at._findOption(flag) !== undefined || at.commands.some((sub) => declared(flag, sub));
1421
1540
  const terminator = userArgs.indexOf('--');
1422
1541
  const head = terminator === -1 ? userArgs : userArgs.slice(0, terminator);
1423
1542
  if (head[0] === 'completion' && root._findCommand('completion') === undefined) {
1424
- 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 }) => {
1425
1545
  const shell = head[1] ?? '';
1426
1546
  if (shell === 'fig') {
1427
1547
  root._outputConfiguration.writeOut(`${JSON.stringify(renderFigSpec(this.manifest), null, 2)}\n`);
@@ -1437,10 +1557,13 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1437
1557
  }
1438
1558
  return this._burgeeSurfaceRest(head, declared);
1439
1559
  }
1560
+ /** The surfaces after `completion`: `--schema` is synchronous, `--mcp` serves until stdin closes. */
1440
1561
  _burgeeSurfaceRest(head, declared) {
1441
1562
  const root = this._root();
1442
1563
  if (head.includes('--schema') && !declared('--schema')) {
1443
- 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`);
1444
1567
  return true;
1445
1568
  }
1446
1569
  if (head[0] === '--mcp' && !declared('--mcp')) {
@@ -1455,10 +1578,12 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1455
1578
  }
1456
1579
  return false;
1457
1580
  }
1581
+ /** Additive, and the point of the whole exercise: plugins commander has never had (#2505, unlanded). */
1458
1582
  use(plugin) {
1459
1583
  this.manifest.use(plugin);
1460
1584
  return this;
1461
1585
  }
1586
+ /** Inject the streams and the exit for one parse; returns commander's own parse options. */
1462
1587
  _prepareBurgee(parseOptions) {
1463
1588
  if (parseOptions === undefined)
1464
1589
  return undefined;
@@ -1485,6 +1610,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1485
1610
  visit(root);
1486
1611
  return from;
1487
1612
  }
1613
+ /** In burgee mode the whole run settles to one E1 exit; otherwise commander's behaviour, untouched. */
1488
1614
  _runBurgee(run) {
1489
1615
  const root = this._root();
1490
1616
  const burgee = root._burgee;
@@ -1521,6 +1647,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1521
1647
  return undefined;
1522
1648
  }
1523
1649
  }
1650
+ /** `--json` that no command in the chain declared is burgee's envelope, not an unknown option. */
1524
1651
  _takeJson(unknown) {
1525
1652
  const terminator = unknown.indexOf('--');
1526
1653
  const index = unknown.indexOf('--json');
@@ -1533,6 +1660,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1533
1660
  root._burgee ??= { exit: undefined, json: false };
1534
1661
  root._burgee.json = true;
1535
1662
  }
1663
+ /** The action, wrapped in the plugin hooks and followed by the envelope or the rendering. */
1536
1664
  _runAction() {
1537
1665
  const handler = this._actionHandler;
1538
1666
  if (handler === null)
@@ -1555,6 +1683,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1555
1683
  settle(value);
1556
1684
  });
1557
1685
  }
1686
+ /** burgee (M5): once per process, on stderr; only for a command that asked, so commander's own output is untouched. */
1558
1687
  _warnDeprecated() {
1559
1688
  if (this._deprecated === undefined || this._deprecated === false || this._deprecationWarned)
1560
1689
  return;
@@ -1562,6 +1691,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1562
1691
  const use = typeof this._deprecated === 'string' ? `, use '${this._deprecated}'` : '';
1563
1692
  this._outputConfiguration.writeErr(`warning: '${this.name()}' is deprecated${use}\n`);
1564
1693
  }
1694
+ /** burgee: where every option value came from, from commander's own value sources (V3). */
1565
1695
  _provenance() {
1566
1696
  const out = {};
1567
1697
  const names = { cli: 'flag', env: 'env', config: 'config', default: 'default', implied: 'implied' };
@@ -1584,7 +1714,9 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1584
1714
  this._outputConfiguration.writeOut(text);
1585
1715
  }
1586
1716
  }
1717
+ /** Bump inspector ports so a spawned subcommand does not collide with the parent. */
1587
1718
  function incrementNodeInspectorPort(args) {
1719
+ // --inspect[=[host:]port], --inspect-brk[=[host:]port], --inspect-port=[host:]port
1588
1720
  return args.map((arg) => {
1589
1721
  if (!arg.startsWith('--inspect'))
1590
1722
  return arg;
@@ -1612,6 +1744,10 @@ function incrementNodeInspectorPort(args) {
1612
1744
  return arg;
1613
1745
  });
1614
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
+ */
1615
1751
  export function useColor() {
1616
1752
  if (process.env['NO_COLOR'] || process.env['FORCE_COLOR'] === '0' || process.env['FORCE_COLOR'] === 'false')
1617
1753
  return false;
@@ -1619,3 +1755,4 @@ export function useColor() {
1619
1755
  return true;
1620
1756
  return undefined;
1621
1757
  }
1758
+ //# sourceMappingURL=command.js.map