burgee 0.7.0 → 0.8.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.
@@ -2,21 +2,6 @@ import { EventEmitter } from 'node:events';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { stripVTControlCharacters } from 'node:util';
5
- /**
6
- * commander's `Command`, ported method for method from commander 15 and graded by
7
- * commander's own suite through `compat-oracle`. The parse pipeline, the option
8
- * grammar, every error string and exit code are commander's — that is what makes a
9
- * user's existing program run unchanged (J2).
10
- *
11
- * burgee's additions sit beside it and never alter the default behaviour:
12
- * - `manifest` projects the command tree, so plugins (`use`) and the generated
13
- * surfaces read commander-syntax programs exactly like native ones (J7, J8);
14
- * - `--json`, when the program has not declared that option itself, wraps the
15
- * action's return value in the envelope (N-family);
16
- * - `parse(argv, { stdout, stderr, exit })` injects the streams and the exit, and
17
- * then reports through the E1 taxonomy — the harness's seam (T1).
18
- */
19
- // eslint-disable-next-line import-next/no-namespace -- `spawn` is read off the namespace at the call site and never captured into a local. commander's own suite mocks `childProcess.spawn` in roughly 23 `executableSubcommand` cases, and a binding captured at import never re-syncs; bellpull's `cross-spawn.ts` reads `spawn` off its default import for this exact consumer, so a named import here would undo that and take the row from 1360 / 1360 to ungradeable.
20
5
  import * as crossSpawn from 'bellpull/cross-spawn';
21
6
  import { ExitCode } from '../exit-code.js';
22
7
  import { Manifest } from '../manifest.js';
@@ -28,7 +13,6 @@ import { Argument, humanReadableArgName } from './argument.js';
28
13
  import { CommanderError } from './error.js';
29
14
  import { Help } from './help.js';
30
15
  import { DualOptions, Option } from './option.js';
31
- /** Names that would reach Object.prototype if used as an option key. */
32
16
  const POLLUTING = new Set(['__proto__', 'constructor', 'prototype']);
33
17
  const ENV_SOURCES = ['default', 'config', 'env'];
34
18
  const IMPLIED_SOURCES = ['default', 'implied'];
@@ -36,12 +20,10 @@ const HOOK_EVENTS = ['preSubcommand', 'preAction', 'postAction'];
36
20
  const HELP_POSITIONS = ['beforeAll', 'before', 'after', 'afterAll'];
37
21
  const SOURCE_EXT = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];
38
22
  const FORWARDED_SIGNALS = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
39
- /** 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. */
40
23
  const quoted = (s) => `'${s}'`;
41
24
  function isThenable(value) {
42
25
  return typeof value?.then === 'function';
43
26
  }
44
- /** commander's exit codes, read through burgee's taxonomy when the exit is injected (E1). */
45
27
  function e1(err) {
46
28
  switch (err.code) {
47
29
  case 'commander.helpDisplayed':
@@ -56,7 +38,6 @@ function e1(err) {
56
38
  return err.exitCode === 1 ? ExitCode.USAGE : err.exitCode;
57
39
  }
58
40
  }
59
- /** What a run prints for an action's return value when the streams are injected. */
60
41
  function render(value) {
61
42
  if (value === undefined || value === null)
62
43
  return '';
@@ -74,12 +55,9 @@ export class Command extends EventEmitter {
74
55
  options = [];
75
56
  parent = null;
76
57
  registeredArguments = [];
77
- /** @deprecated old name for registeredArguments */
78
58
  _args;
79
- /** cli args with options removed */
80
59
  args = [];
81
60
  rawArgs = [];
82
- /** like .args but after custom processing and collecting variadic */
83
61
  processedArgs = [];
84
62
  runningCommand = undefined;
85
63
  _allowUnknownOption = false;
@@ -108,7 +86,6 @@ export class Command extends EventEmitter {
108
86
  _savedState = null;
109
87
  _outputConfiguration;
110
88
  _hidden = false;
111
- /** Lazy created on demand; null once disabled. */
112
89
  _helpOption = undefined;
113
90
  _addImplicitHelpCommand = undefined;
114
91
  _helpCommand = undefined;
@@ -119,14 +96,10 @@ export class Command extends EventEmitter {
119
96
  _version = undefined;
120
97
  _versionOptionName = undefined;
121
98
  _usage = undefined;
122
- /** burgee: the root's projection, created on first use. */
123
99
  _manifest = undefined;
124
- /** burgee: what this command does to the world (N6); declaring it exposes the command as an MCP tool. */
125
100
  _effects = undefined;
126
- /** burgee: `true`, or the replacement's name (M5). Shown in help, schema and a one-line warning on use. */
127
101
  _deprecated = undefined;
128
102
  _deprecationWarned = false;
129
- /** burgee: set for the duration of a parse that injected the streams or the exit. */
130
103
  _burgee = undefined;
131
104
  constructor(name) {
132
105
  super();
@@ -143,7 +116,6 @@ export class Command extends EventEmitter {
143
116
  stripColor: (str) => stripVTControlCharacters(str),
144
117
  };
145
118
  }
146
- /** Copy settings useful to share between the root and its subcommands. */
147
119
  copyInheritedSettings(sourceCommand) {
148
120
  this._outputConfiguration = sourceCommand._outputConfiguration;
149
121
  this._helpOption = sourceCommand._helpOption;
@@ -191,7 +163,6 @@ export class Command extends EventEmitter {
191
163
  return this;
192
164
  return cmd;
193
165
  }
194
- /** Factory for an unattached command; override to customise subcommands. */
195
166
  createCommand(name) {
196
167
  return new Command(name);
197
168
  }
@@ -263,7 +234,6 @@ export class Command extends EventEmitter {
263
234
  this.registeredArguments.push(argument);
264
235
  return this;
265
236
  }
266
- /** Customise or disable the default help command (added by default when there are subcommands). */
267
237
  helpCommand(enableOrNameAndArgs, description) {
268
238
  if (typeof enableOrNameAndArgs === 'boolean') {
269
239
  this._addImplicitHelpCommand = enableOrNameAndArgs;
@@ -320,25 +290,21 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
320
290
  this._lifeCycleHooks[event] = [listener];
321
291
  return this;
322
292
  }
323
- /** Replace the call to process.exit; defaults to throwing the CommanderError. */
324
293
  exitOverride(fn) {
325
294
  this._exitCallback =
326
295
  fn ??
327
296
  ((err) => {
328
297
  if (err.code !== 'commander.executeSubCommandAsync')
329
298
  throw err;
330
- // Async callback from spawn events, not useful to throw.
331
299
  });
332
300
  return this;
333
301
  }
334
302
  _exit(exitCode, code, message) {
335
303
  if (this._exitCallback) {
336
304
  this._exitCallback(new CommanderError(exitCode, code, message));
337
- // Expecting this line is not reached.
338
305
  }
339
306
  return host.exit(exitCode);
340
307
  }
341
- // commander's contract: the positional args, then the options, then the command itself.
342
308
  action(fn) {
343
309
  const listener = (args) => {
344
310
  const expectedArgsCount = this.registeredArguments.length;
@@ -353,7 +319,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
353
319
  createOption(flags, description) {
354
320
  return new Option(flags, description);
355
321
  }
356
- /** Wrap parseArg to turn `commander.invalidArgument` into an error with context. */
357
322
  _callParseArg(target, value, previous, invalidArgumentMessage) {
358
323
  try {
359
324
  return target.parseArg?.(value, previous);
@@ -401,7 +366,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
401
366
  if (option.defaultValue !== undefined) {
402
367
  this.setOptionValueWithSource(name, option.defaultValue, 'default');
403
368
  }
404
- // val is null for an optional option used without its argument, undefined for boolean and negated.
405
369
  const handleOptionValue = (val, invalidValueMessage, valueSource) => {
406
370
  let value = val;
407
371
  if (value == null && option.presetArg !== undefined)
@@ -419,7 +383,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
419
383
  else if (option.isBoolean() || option.optional)
420
384
  value = true;
421
385
  else
422
- value = ''; // not normal, parseArg might have failed or be a mock function for testing
386
+ value = '';
423
387
  }
424
388
  this.setOptionValueWithSource(name, value, valueSource);
425
389
  };
@@ -443,7 +407,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
443
407
  option.default(defaultValue).argParser(fn);
444
408
  }
445
409
  else if (fn instanceof RegExp) {
446
- // deprecated
447
410
  const regex = fn;
448
411
  option.default(defaultValue).argParser((val, def) => {
449
412
  const m = regex.exec(val);
@@ -461,7 +424,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
461
424
  requiredOption(flags, description, parseArg, defaultValue) {
462
425
  return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
463
426
  }
464
- /** `-f80` as `--flag=80` (default) versus `-fb` as `-f -b`. */
465
427
  combineFlagAndOptionalValue(combine = true) {
466
428
  this._combineFlagAndOptionalValue = !!combine;
467
429
  return this;
@@ -474,12 +436,10 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
474
436
  this._allowExcessArguments = !!allowExcess;
475
437
  return this;
476
438
  }
477
- /** Global options before subcommands only, so subcommands may reuse option names. */
478
439
  enablePositionalOptions(positional = true) {
479
440
  this._enablePositionalOptions = !!positional;
480
441
  return this;
481
442
  }
482
- /** Options after the first command-argument are passed through, not parsed. */
483
443
  passThroughOptions(passThrough = true) {
484
444
  this._passThroughOptions = !!passThrough;
485
445
  this._checkForBrokenPassThrough();
@@ -506,7 +466,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
506
466
  setOptionValue(key, value) {
507
467
  return this.setOptionValueWithSource(key, value, undefined);
508
468
  }
509
- /** `source` is default | config | env | cli | implied. */
510
469
  setOptionValueWithSource(key, value, source) {
511
470
  if (this._storeOptionsAsProperties)
512
471
  this[key] = value;
@@ -518,7 +477,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
518
477
  getOptionValueSource(key) {
519
478
  return this._optionValueSources[key];
520
479
  }
521
- /** Globals overwrite locals, like optsWithGlobals. */
522
480
  getOptionValueSourceWithGlobals(key) {
523
481
  let source;
524
482
  for (const cmd of this._getCommandAndAncestors()) {
@@ -527,7 +485,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
527
485
  }
528
486
  return source;
529
487
  }
530
- /** User args from argv per `from`; sets `_scriptPath` and the default program name. */
531
488
  _prepareUserArgs(argv, parseOptions) {
532
489
  if (argv !== undefined && !Array.isArray(argv))
533
490
  throw new Error('first parameter to parse must be array or undefined');
@@ -537,7 +494,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
537
494
  parseOptions.from = 'electron';
538
495
  const execArgv = host.execArgv ?? [];
539
496
  if (execArgv.includes('-e') || execArgv.includes('--eval') || execArgv.includes('-p') || execArgv.includes('--print')) {
540
- parseOptions.from = 'eval'; // internal usage, not documented
497
+ parseOptions.from = 'eval';
541
498
  }
542
499
  }
543
500
  if (argv === undefined)
@@ -573,17 +530,10 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
573
530
  this._name = this._name || 'program';
574
531
  return userArgs;
575
532
  }
576
- /**
577
- * Parse argv, set options and run commands. Use `parseAsync` when an action is async.
578
- * With no arguments, parses process.argv and auto-detects Electron and `node --eval`.
579
- */
580
533
  parse(argv, parseOptions) {
581
534
  const from = this._prepareBurgee(parseOptions);
582
535
  this._prepareForParse();
583
536
  const userArgs = this._prepareUserArgs(argv, from);
584
- // The surface check is synchronous unless a surface is actually served (completions,
585
- // --mcp), so a synchronous action has run by the time parse() returns — commander's
586
- // contract, which its suite asserts on after every parse(). commander-sync.test.ts.
587
537
  this._runBurgee(() => {
588
538
  const served = this._burgeeSurface(userArgs);
589
539
  if (isThenable(served))
@@ -596,8 +546,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
596
546
  const from = this._prepareBurgee(parseOptions);
597
547
  this._prepareForParse();
598
548
  const userArgs = this._prepareUserArgs(argv, from);
599
- // Same synchronous start as parse(): a preAction hook has run before the promise is
600
- // handed back, which commander's hook tests assert on.
601
549
  await this._runBurgee(() => {
602
550
  const served = this._burgeeSurface(userArgs);
603
551
  if (isThenable(served))
@@ -608,7 +556,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
608
556
  }
609
557
  _prepareForParse() {
610
558
  if (this._savedState === null) {
611
- // Lone negated option (--no-foo without --foo) defaults to true, now that all options are known.
612
559
  for (const option of this.options) {
613
560
  if (option.negate && option.defaultValue === undefined && this.getOptionValue(option.attributeName()) === undefined) {
614
561
  const positiveLongFlag = (option.long ?? '').replace(/^--no-/, '--');
@@ -622,7 +569,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
622
569
  this.restoreStateBeforeParse();
623
570
  }
624
571
  }
625
- /** Called lazily on first parse; available for subclasses to save custom state. */
626
572
  saveStateBeforeParse() {
627
573
  this._savedState = {
628
574
  _name: this._name,
@@ -668,7 +614,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
668
614
  const foundExt = SOURCE_EXT.find((ext) => fs.existsSync(`${localBin}${ext}`));
669
615
  return foundExt ? `${localBin}${foundExt}` : undefined;
670
616
  };
671
- // Not checking for help first: can't robustly test for help flags in an external command.
672
617
  this._checkForMissingMandatoryOptions();
673
618
  this._checkForConflictingOptions();
674
619
  let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
@@ -685,7 +630,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
685
630
  }
686
631
  if (executableDir) {
687
632
  let localFile = findFile(executableDir, executableFile);
688
- // Legacy search using the script name as prefix instead of the command name.
689
633
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
690
634
  const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
691
635
  if (legacyName !== this._name)
@@ -694,10 +638,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
694
638
  executableFile = localFile || executableFile;
695
639
  }
696
640
  const launchWithNode = SOURCE_EXT.includes(path.extname(executableFile));
697
- // Through `bellpull`, not `node:child_process`: it resolves the executable properly on
698
- // Windows, where upstream sends every spawn through `node` to dodge `PATHEXT`. The
699
- // reasoning, the measurement and the mock constraint are in `weight.test.ts`'s
700
- // `./commander` entry — they are prose, and prose in this file ships.
701
641
  let proc;
702
642
  if (host.platform !== 'win32') {
703
643
  if (launchWithNode) {
@@ -717,12 +657,10 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
717
657
  proc = crossSpawn.spawn(host.execPath, args, { stdio: 'inherit' });
718
658
  }
719
659
  else {
720
- // The case upstream cannot reach: a `.cmd`, a `.bat`, or a shebang that is not node.
721
660
  proc = crossSpawn.spawn(executableFile, args, { stdio: 'inherit' });
722
661
  }
723
662
  }
724
663
  if (!proc.killed) {
725
- // Testing mainly to avoid leak warnings during unit tests with mocked spawn.
726
664
  for (const signal of FORWARDED_SIGNALS) {
727
665
  host.on(signal, () => {
728
666
  if (proc.killed === false && proc.exitCode === null)
@@ -732,7 +670,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
732
670
  }
733
671
  const exitCallback = this._exitCallback;
734
672
  proc.on('close', (code) => {
735
- code = code ?? 1; // null when the spawned process terminated due to a signal
673
+ code = code ?? 1;
736
674
  if (!exitCallback)
737
675
  host.exit(code);
738
676
  else
@@ -772,14 +710,12 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
772
710
  });
773
711
  return promiseChain;
774
712
  }
775
- /** `help foo`: invoke help directly if possible, or dispatch if necessary. */
776
713
  _dispatchHelpCommand(subcommandName) {
777
714
  if (!subcommandName)
778
715
  this.help();
779
716
  const subCommand = this._findCommand(subcommandName);
780
717
  if (subCommand && !subCommand._executableHandler)
781
718
  subCommand.help();
782
- // Fallback to parsing the help flag to invoke the help.
783
719
  return this._dispatchSubcommand(subcommandName ?? '', [], [
784
720
  this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help',
785
721
  ]);
@@ -795,7 +731,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
795
731
  if (this.args.length > this.registeredArguments.length)
796
732
  this._excessArguments(this.args);
797
733
  }
798
- /** Process this.args against registeredArguments into this.processedArgs. */
799
734
  _processArguments() {
800
735
  const myParseArg = (argument, value, previous) => {
801
736
  let parsedValue = value;
@@ -828,7 +763,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
828
763
  });
829
764
  this.processedArgs = processedArgs;
830
765
  }
831
- /** Chain once we have a promise; call synchronously until then. */
832
766
  _chainOrCall(promise, fn) {
833
767
  if (isThenable(promise))
834
768
  return promise.then(() => fn());
@@ -855,15 +789,12 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
855
789
  }
856
790
  return result;
857
791
  }
858
- /** Process arguments in the context of this command; returns the action result in case it is a promise. */
859
792
  _parseCommand(operands, unknown) {
860
793
  const parsed = this.parseOptions(unknown);
861
- this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
794
+ this._parseOptionsEnv();
862
795
  this._parseOptionsImplied();
863
796
  operands = operands.concat(parsed.operands);
864
797
  unknown = parsed.unknown;
865
- if (this._actionHandler && !this._findCommand(operands[0]) && !this._defaultCommandName)
866
- this._takeJson(unknown);
867
798
  this.args = operands.concat(unknown);
868
799
  if (operands && this._findCommand(operands[0])) {
869
800
  return this._dispatchSubcommand(operands[0] ?? '', operands.slice(1), unknown);
@@ -873,17 +804,15 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
873
804
  return this._dispatchHelpCommand(operands[1]);
874
805
  }
875
806
  if (this._defaultCommandName) {
876
- this._outputHelpIfRequested(unknown); // help for the default command comes from the parent
807
+ this._outputHelpIfRequested(unknown);
877
808
  return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
878
809
  }
879
810
  if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
880
- // probably missing subcommand and no handler, user needs help (and exit)
881
811
  this.help({ error: true });
882
812
  }
883
813
  this._outputHelpIfRequested(parsed.unknown);
884
814
  this._checkForMissingMandatoryOptions();
885
815
  this._checkForConflictingOptions();
886
- // Not always called, to avoid masking a "better" error like unknown command.
887
816
  const checkForUnknownOptions = () => {
888
817
  if (parsed.unknown.length > 0)
889
818
  this.unknownOption(parsed.unknown[0] ?? '');
@@ -897,7 +826,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
897
826
  promiseChain = this._chainOrCall(promiseChain, () => this._runAction());
898
827
  if (this.parent) {
899
828
  promiseChain = this._chainOrCall(promiseChain, () => {
900
- this.parent?.emit(commandEvent, operands, unknown); // legacy
829
+ this.parent?.emit(commandEvent, operands, unknown);
901
830
  });
902
831
  }
903
832
  promiseChain = this._chainOrCallHooks(promiseChain, 'postAction');
@@ -906,15 +835,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
906
835
  if (this.parent?.listenerCount(commandEvent)) {
907
836
  checkForUnknownOptions();
908
837
  this._processArguments();
909
- this.parent.emit(commandEvent, operands, unknown); // legacy
838
+ this.parent.emit(commandEvent, operands, unknown);
910
839
  }
911
840
  else if (operands.length) {
912
841
  if (this._findCommand('*')) {
913
- // legacy default command
914
842
  return this._dispatchSubcommand('*', operands, unknown);
915
843
  }
916
844
  if (this.listenerCount('command:*')) {
917
- // skip option check, emit event for possible misspelling suggestion
918
845
  this.emit('command:*', operands, unknown);
919
846
  }
920
847
  else if (this.commands.length) {
@@ -927,13 +854,11 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
927
854
  }
928
855
  else if (this.commands.length) {
929
856
  checkForUnknownOptions();
930
- // This command has subcommands and nothing hooked up at this level, so display help (and exit).
931
857
  this.help({ error: true });
932
858
  }
933
859
  else {
934
860
  checkForUnknownOptions();
935
861
  this._processArguments();
936
- // fall through for caller to handle after calling .parse()
937
862
  }
938
863
  return undefined;
939
864
  }
@@ -945,7 +870,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
945
870
  _findOption(arg) {
946
871
  return this.options.find((option) => option.is(arg));
947
872
  }
948
- /** Walks up the hierarchy so a subcommand can check after displaying help. */
949
873
  _checkForMissingMandatoryOptions() {
950
874
  for (const cmd of this._getCommandAndAncestors()) {
951
875
  for (const anOption of cmd.options) {
@@ -973,15 +897,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
973
897
  for (const cmd of this._getCommandAndAncestors())
974
898
  cmd._checkForConflictingLocalOptions();
975
899
  }
976
- /**
977
- * Parse options from `args`, removing known options, and return argv split into
978
- * operands and unknown arguments. Side effect: stores option values on the command.
979
- *
980
- * --known kkk op => [op], []
981
- * op --known kkk => [op], []
982
- * sub --unknown uuu op => [sub], [--unknown uuu op]
983
- * sub -- --unknown uuu op => [sub --unknown uuu op], []
984
- */
985
900
  parseOptions(args) {
986
901
  const operands = [];
987
902
  const unknown = [];
@@ -990,11 +905,10 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
990
905
  const negativeNumberArg = (arg) => {
991
906
  if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg))
992
907
  return false;
993
- // a negative number is ok unless a digit is used as an option in the command hierarchy
994
908
  return !this._getCommandAndAncestors().some((cmd) => cmd.options.some((opt) => /^-\d$/.test(opt.short ?? '')));
995
909
  };
996
910
  let activeVariadicOption = null;
997
- let activeGroup = null; // working through a group of short options, like -abc
911
+ let activeGroup = null;
998
912
  let i = 0;
999
913
  while (i < args.length || activeGroup) {
1000
914
  const arg = activeGroup ?? args[i++] ?? '';
@@ -1021,7 +935,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1021
935
  }
1022
936
  else if (option.optional) {
1023
937
  let value = null;
1024
- // historical behaviour: the optional value is the following arg unless it is an option
1025
938
  const next = args[i];
1026
939
  if (i < args.length && next !== undefined && (!maybeOption(next) || negativeNumberArg(next))) {
1027
940
  value = next;
@@ -1035,8 +948,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1035
948
  activeVariadicOption = option.variadic ? option : null;
1036
949
  continue;
1037
950
  }
951
+ if (arg === '--json' && !this._root()._declares('--json')) {
952
+ const root = this._root();
953
+ root._burgee ??= { exit: undefined, json: false };
954
+ root._burgee.json = true;
955
+ continue;
956
+ }
1038
957
  }
1039
- // Combined short options: eat the first one if known.
1040
958
  if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
1041
959
  const option = this._findOption(`-${arg[1]}`);
1042
960
  if (option) {
@@ -1050,7 +968,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1050
968
  continue;
1051
969
  }
1052
970
  }
1053
- // Known long flag with value, like --foo=bar
1054
971
  if (/^--[^=]+=/.test(arg)) {
1055
972
  const index = arg.indexOf('=');
1056
973
  const option = this._findOption(arg.slice(0, index));
@@ -1059,13 +976,9 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1059
976
  continue;
1060
977
  }
1061
978
  }
1062
- // Not recognised by this command: command-argument, subcommand option, unknown option, or help.
1063
- // An unknown option makes everything after it unknown too, for a subcommand to reprocess.
1064
- // A negative number in a leaf command is not an unknown option.
1065
979
  if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) {
1066
980
  dest = unknown;
1067
981
  }
1068
- // Positional options: stop processing our options at a subcommand.
1069
982
  if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
1070
983
  if (this._findCommand(arg)) {
1071
984
  operands.push(arg);
@@ -1081,7 +994,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1081
994
  break;
1082
995
  }
1083
996
  }
1084
- // Pass-through options: stop processing options at the first command-argument.
1085
997
  if (this._passThroughOptions) {
1086
998
  dest.push(arg, ...args.slice(i));
1087
999
  break;
@@ -1090,7 +1002,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1090
1002
  }
1091
1003
  return { operands, unknown };
1092
1004
  }
1093
- /** Local option values as key-value pairs. */
1094
1005
  opts() {
1095
1006
  if (this._storeOptionsAsProperties) {
1096
1007
  const result = {};
@@ -1102,31 +1013,40 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1102
1013
  }
1103
1014
  return this._optionValues;
1104
1015
  }
1105
- /** Merged local and global option values; globals overwrite locals. */
1106
1016
  optsWithGlobals() {
1107
1017
  return this._getCommandAndAncestors().reduce((combined, cmd) => Object.assign(combined, cmd.opts()), {});
1108
1018
  }
1109
- /** Display an error message and exit (or call exitOverride). */
1110
1019
  error(message, errorOptions) {
1111
- this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
1112
- if (typeof this._showHelpAfterError === 'string') {
1113
- this._outputConfiguration.writeErr(`${this._showHelpAfterError}\n`);
1114
- }
1115
- else if (this._showHelpAfterError) {
1116
- this._outputConfiguration.writeErr('\n');
1117
- this.outputHelp({ error: true });
1118
- }
1119
1020
  const config = errorOptions ?? {};
1120
1021
  const exitCode = config.exitCode || 1;
1121
1022
  const code = config.code || 'commander.error';
1023
+ if (this._root()._burgee?.json === true)
1024
+ this._reportJson(code, message);
1025
+ else {
1026
+ this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
1027
+ if (typeof this._showHelpAfterError === 'string') {
1028
+ this._outputConfiguration.writeErr(`${this._showHelpAfterError}\n`);
1029
+ }
1030
+ else if (this._showHelpAfterError) {
1031
+ this._outputConfiguration.writeErr('\n');
1032
+ this.outputHelp({ error: true });
1033
+ }
1034
+ }
1122
1035
  this._exit(exitCode, code, message);
1123
1036
  }
1124
- /** Apply environment variables to options that have no value from the cli or client code. */
1037
+ _reportJson(code, message) {
1038
+ const burgee = this._root()._burgee;
1039
+ if (burgee === undefined || !burgee.json || burgee.reported)
1040
+ return;
1041
+ burgee.reported = true;
1042
+ const [first = '', ...rest] = message.replace(/^error: /, '').split('\n');
1043
+ const guess = /\(Did you mean (\S+)\?\)/.exec(rest.join(''));
1044
+ this._outputConfiguration.writeOut(`${JSON.stringify({ ok: false, error: { code, message: first, ...(guess === null ? {} : { fix: guess[1] }) } })}\n`);
1045
+ }
1125
1046
  _parseOptionsEnv() {
1126
1047
  for (const option of this.options) {
1127
1048
  if (option.envVar && option.envVar in host.env) {
1128
1049
  const optionKey = option.attributeName();
1129
- // Do not overwrite cli values or values from an unknown (client-code) source.
1130
1050
  if (this.getOptionValue(optionKey) === undefined || ENV_SOURCES.includes(this.getOptionValueSource(optionKey) ?? '')) {
1131
1051
  if (option.required || option.optional)
1132
1052
  this.emit(`optionEnv:${option.name()}`, host.env[option.envVar]);
@@ -1136,7 +1056,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1136
1056
  }
1137
1057
  }
1138
1058
  }
1139
- /** Apply implied option values where the option is undefined or at its default. */
1140
1059
  _parseOptionsImplied() {
1141
1060
  const dualHelper = new DualOptions(this.options);
1142
1061
  const hasCustomOptionValue = (optionKey) => this.getOptionValue(optionKey) !== undefined && !IMPLIED_SOURCES.includes(this.getOptionValueSource(optionKey) ?? '');
@@ -1160,7 +1079,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1160
1079
  this.error(`error: required option '${option.flags}' not specified`, { code: 'commander.missingMandatoryOptionValue' });
1161
1080
  }
1162
1081
  _conflictingOption(option, conflictingOption) {
1163
- // The caller does not know whether a negated option is the source of the value; take an educated guess.
1164
1082
  const findBestOptionFromValue = (candidate) => {
1165
1083
  const optionKey = candidate.attributeName();
1166
1084
  const optionValue = this.getOptionValue(optionKey);
@@ -1188,7 +1106,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1188
1106
  return;
1189
1107
  let suggestion = '';
1190
1108
  if (flag.startsWith('--') && this._showSuggestionAfterError) {
1191
- // Looping to pick up the global options too.
1192
1109
  let candidateFlags = [];
1193
1110
  let command = this;
1194
1111
  do {
@@ -1265,7 +1182,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1265
1182
  let command = this;
1266
1183
  const last = this.commands[this.commands.length - 1];
1267
1184
  if (this.commands.length !== 0 && last?._executableHandler) {
1268
- // assume adding an alias for the last added executable subcommand, rather than this
1269
1185
  command = last;
1270
1186
  }
1271
1187
  if (alias === command._name)
@@ -1329,7 +1245,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1329
1245
  if (this._defaultCommandGroup && !cmd.helpGroup())
1330
1246
  cmd.helpGroup(this._defaultCommandGroup);
1331
1247
  }
1332
- /** Name the command from a script filename, such as process.argv[1] or import.meta.filename. */
1333
1248
  nameFromFilename(filename) {
1334
1249
  this._name = path.basename(filename, path.extname(filename));
1335
1250
  return this;
@@ -1376,7 +1291,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1376
1291
  };
1377
1292
  return { error, write, hasColors, helpWidth };
1378
1293
  }
1379
- /** Output built-in help plus any text added with `addHelpText`. */
1380
1294
  outputHelp(contextOptions) {
1381
1295
  let deprecatedCallback;
1382
1296
  if (typeof contextOptions === 'function') {
@@ -1398,17 +1312,16 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1398
1312
  outputContext.write(String(helpInformation));
1399
1313
  const helpLong = this._getHelpOption()?.long;
1400
1314
  if (helpLong)
1401
- this.emit(helpLong); // deprecated
1315
+ this.emit(helpLong);
1402
1316
  this.emit('afterHelp', eventContext);
1403
1317
  for (const command of this._getCommandAndAncestors())
1404
1318
  command.emit('afterAllHelp', eventContext);
1405
1319
  }
1406
- /** Customise the built-in help option, or pass false to disable it. */
1407
1320
  helpOption(flags, description) {
1408
1321
  if (typeof flags === 'boolean') {
1409
1322
  if (flags) {
1410
1323
  if (this._helpOption === null)
1411
- this._helpOption = undefined; // reenable
1324
+ this._helpOption = undefined;
1412
1325
  if (this._defaultOptionGroup) {
1413
1326
  const helpOption = this._getHelpOption();
1414
1327
  if (helpOption)
@@ -1416,7 +1329,7 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1416
1329
  }
1417
1330
  }
1418
1331
  else {
1419
- this._helpOption = null; // disable
1332
+ this._helpOption = null;
1420
1333
  }
1421
1334
  return this;
1422
1335
  }
@@ -1425,7 +1338,6 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1425
1338
  this._initOptionGroup(this._helpOption);
1426
1339
  return this;
1427
1340
  }
1428
- /** Lazily created; null once disabled with `helpOption(false)`. */
1429
1341
  _getHelpOption() {
1430
1342
  if (this._helpOption === undefined)
1431
1343
  this.helpOption(undefined, undefined);
@@ -1436,16 +1348,13 @@ Expecting one of '${HOOK_EVENTS.join("', '")}'`);
1436
1348
  this._initOptionGroup(option);
1437
1349
  return this;
1438
1350
  }
1439
- /** Output help and exit. */
1440
1351
  help(contextOptions) {
1441
1352
  this.outputHelp(contextOptions);
1442
1353
  let exitCode = Number(host.exitCode ?? 0);
1443
1354
  if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error)
1444
1355
  exitCode = 1;
1445
- // message: not all displayed text is available, so only a placeholder is passed.
1446
1356
  this._exit(exitCode, 'commander.help', '(outputHelp)');
1447
1357
  }
1448
- /** Extra help text: 'before'/'after' for this command, 'beforeAll'/'afterAll' for its subcommands too. */
1449
1358
  addHelpText(position, text) {
1450
1359
  if (!HELP_POSITIONS.includes(position)) {
1451
1360
  throw new Error(`Unexpected value for position to addHelpText.
@@ -1465,17 +1374,12 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1465
1374
  this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
1466
1375
  }
1467
1376
  }
1468
- // ───── burgee: the manifest projection, plugins, `--json` and the injected seam ─────
1469
1377
  _root() {
1470
1378
  let command = this;
1471
1379
  while (command.parent)
1472
1380
  command = command.parent;
1473
1381
  return command;
1474
1382
  }
1475
- /**
1476
- * The manifest every surface reads. Projected from the command tree on each access,
1477
- * so it is never stale; plugin-contributed nodes are kept across projections.
1478
- */
1479
1383
  get manifest() {
1480
1384
  const root = this._root();
1481
1385
  root._manifest ??= new Manifest();
@@ -1507,7 +1411,6 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1507
1411
  };
1508
1412
  visit(this, [rootName]);
1509
1413
  }
1510
- /** Options as the manifest describes them, on a null-prototype record. */
1511
1414
  _optionSpecs() {
1512
1415
  const specs = Object.create(null);
1513
1416
  for (const option of this.options) {
@@ -1526,32 +1429,19 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1526
1429
  }
1527
1430
  return specs;
1528
1431
  }
1529
- /** burgee: declare what the command does to the world (N6). This is what exposes it as an MCP tool (N2). */
1530
1432
  effects(value) {
1531
1433
  this._effects = value;
1532
1434
  return this;
1533
1435
  }
1534
- /**
1535
- * burgee: mark the command deprecated (M5). Help and `--schema` show it; running it prints
1536
- * `warning: 'old' is deprecated, use 'new'` on stderr once and goes on, exit unchanged.
1537
- */
1538
1436
  deprecate(use) {
1539
1437
  this._deprecated = use ?? true;
1540
1438
  return this;
1541
1439
  }
1542
- /**
1543
- * burgee: `--schema` and `--mcp` on a commander-syntax program, from its manifest (J2).
1544
- * Only when the program declares neither option itself; `--mcp` runs commands through
1545
- * this very program with the streams captured, so tool results are the `--json` envelope.
1546
- */
1547
1440
  _burgeeSurface(userArgs) {
1548
1441
  const root = this._root();
1549
- // Any command in the tree that declares the flag keeps it: the surface is additive only.
1550
- const declared = (flag, at = root) => at._findOption(flag) !== undefined || at.commands.some((sub) => declared(flag, sub));
1551
1442
  const terminator = userArgs.indexOf('--');
1552
1443
  const head = terminator === -1 ? userArgs : userArgs.slice(0, terminator);
1553
1444
  if (head[0] === 'completion' && root._findCommand('completion') === undefined) {
1554
- // Loaded on this command only (K6), exactly as the engine does.
1555
1445
  return import('../completions.js').then(({ renderCompletion, renderFigSpec, SHELLS }) => {
1556
1446
  const shell = head[1] ?? '';
1557
1447
  if (shell === 'fig') {
@@ -1563,21 +1453,19 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1563
1453
  root._outputConfiguration.writeOut(renderCompletion(this.manifest, known));
1564
1454
  return true;
1565
1455
  }
1566
- return this._burgeeSurfaceRest(head, declared);
1456
+ return this._burgeeSurfaceRest(head);
1567
1457
  });
1568
1458
  }
1569
- return this._burgeeSurfaceRest(head, declared);
1459
+ return this._burgeeSurfaceRest(head);
1570
1460
  }
1571
- /** The surfaces after `completion`: `--schema` is synchronous, `--mcp` serves until stdin closes. */
1572
- _burgeeSurfaceRest(head, declared) {
1461
+ _burgeeSurfaceRest(head) {
1573
1462
  const root = this._root();
1574
- if (head.includes('--schema') && !declared('--schema')) {
1575
- // R1, and the same escape hatch the engine has: `--schema` is burgee's surface, not
1576
- // commander's, so it answers to E-floor byte discipline rather than to the host.
1463
+ if (head.includes('--schema') && !root._declares('--schema')) {
1577
1464
  root._outputConfiguration.writeOut(`${machineJson(schemaOf(this.manifest), head)}\n`);
1578
1465
  return true;
1579
1466
  }
1580
- if (head[0] === '--mcp' && !declared('--mcp')) {
1467
+ if (head[0] === '--mcp' && !root._declares('--mcp')) {
1468
+ const writeOut = root._outputConfiguration.writeOut;
1581
1469
  const invoke = async (args) => {
1582
1470
  const out = [];
1583
1471
  const err = [];
@@ -1585,16 +1473,14 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1585
1473
  await root.parseAsync(args, { from: 'user', stdout: { write: (s) => out.push(s) }, stderr: { write: (s) => err.push(s) }, exit: (c) => void (code = c) });
1586
1474
  return { stdout: out.join(''), stderr: err.join(''), code };
1587
1475
  };
1588
- return serveMcp(this.manifest, { input: host.stdin, output: { write: (s) => root._outputConfiguration.writeOut(s) }, invoke }).then(() => true);
1476
+ return serveMcp(this.manifest, { input: host.stdin, output: { write: writeOut }, invoke }).then(() => true);
1589
1477
  }
1590
1478
  return false;
1591
1479
  }
1592
- /** Additive, and the point of the whole exercise: plugins commander has never had (#2505, unlanded). */
1593
1480
  use(plugin) {
1594
1481
  this.manifest.use(plugin);
1595
1482
  return this;
1596
1483
  }
1597
- /** Inject the streams and the exit for one parse; returns commander's own parse options. */
1598
1484
  _prepareBurgee(parseOptions) {
1599
1485
  if (parseOptions === undefined)
1600
1486
  return undefined;
@@ -1621,7 +1507,6 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1621
1507
  visit(root);
1622
1508
  return from;
1623
1509
  }
1624
- /** In burgee mode the whole run settles to one E1 exit; otherwise commander's behaviour, untouched. */
1625
1510
  _runBurgee(run) {
1626
1511
  const root = this._root();
1627
1512
  const burgee = root._burgee;
@@ -1633,15 +1518,14 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1633
1518
  };
1634
1519
  const fail = (err) => {
1635
1520
  if (err instanceof CommanderError) {
1636
- if (burgee.json && err.code !== 'commander.helpDisplayed' && err.code !== 'commander.version') {
1637
- root._outputConfiguration.writeOut(`${JSON.stringify({ ok: false, error: { code: err.code, message: err.message } })}\n`);
1638
- }
1521
+ if (err.code !== 'commander.helpDisplayed' && err.code !== 'commander.version')
1522
+ root._reportJson(err.code, err.message);
1639
1523
  finish(e1(err));
1640
1524
  return;
1641
1525
  }
1642
1526
  const message = err instanceof Error ? err.message : String(err);
1643
1527
  if (burgee.json)
1644
- root._outputConfiguration.writeOut(`${JSON.stringify({ ok: false, error: { code: 'runtime', message } })}\n`);
1528
+ root._reportJson('runtime', message);
1645
1529
  else
1646
1530
  root._outputConfiguration.writeErr(`error: ${message}\n`);
1647
1531
  finish(ExitCode.RUNTIME);
@@ -1658,20 +1542,9 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1658
1542
  return undefined;
1659
1543
  }
1660
1544
  }
1661
- /** `--json` that no command in the chain declared is burgee's envelope, not an unknown option. */
1662
- _takeJson(unknown) {
1663
- const terminator = unknown.indexOf('--');
1664
- const index = unknown.indexOf('--json');
1665
- if (index === -1 || (terminator !== -1 && index > terminator))
1666
- return;
1667
- if (this._getCommandAndAncestors().some((cmd) => cmd._findOption('--json')))
1668
- return;
1669
- unknown.splice(index, 1);
1670
- const root = this._root();
1671
- root._burgee ??= { exit: undefined, json: false };
1672
- root._burgee.json = true;
1545
+ _declares(flag) {
1546
+ return this._findOption(flag) !== undefined || this.commands.some((sub) => sub._declares(flag));
1673
1547
  }
1674
- /** The action, wrapped in the plugin hooks and followed by the envelope or the rendering. */
1675
1548
  _runAction() {
1676
1549
  const handler = this._actionHandler;
1677
1550
  if (handler === null)
@@ -1694,7 +1567,6 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1694
1567
  settle(value);
1695
1568
  });
1696
1569
  }
1697
- /** burgee (M5): once per process, on stderr; only for a command that asked, so commander's own output is untouched. */
1698
1570
  _warnDeprecated() {
1699
1571
  if (this._deprecated === undefined || this._deprecated === false || this._deprecationWarned)
1700
1572
  return;
@@ -1702,7 +1574,6 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1702
1574
  const use = typeof this._deprecated === 'string' ? `, use '${this._deprecated}'` : '';
1703
1575
  this._outputConfiguration.writeErr(`warning: '${this.name()}' is deprecated${use}\n`);
1704
1576
  }
1705
- /** burgee: where every option value came from, from commander's own value sources (V3). */
1706
1577
  _provenance() {
1707
1578
  const out = {};
1708
1579
  const names = { cli: 'flag', env: 'env', config: 'config', default: 'default', implied: 'implied' };
@@ -1725,9 +1596,7 @@ Expecting one of '${HELP_POSITIONS.join("', '")}'`);
1725
1596
  this._outputConfiguration.writeOut(text);
1726
1597
  }
1727
1598
  }
1728
- /** Bump inspector ports so a spawned subcommand does not collide with the parent. */
1729
1599
  function incrementNodeInspectorPort(args) {
1730
- // --inspect[=[host:]port], --inspect-brk[=[host:]port], --inspect-port=[host:]port
1731
1600
  return args.map((arg) => {
1732
1601
  if (!arg.startsWith('--inspect'))
1733
1602
  return arg;
@@ -1755,10 +1624,6 @@ function incrementNodeInspectorPort(args) {
1755
1624
  return arg;
1756
1625
  });
1757
1626
  }
1758
- /**
1759
- * The common colour conventions: NO_COLOR and FORCE_COLOR=0/false disable, FORCE_COLOR
1760
- * and CLICOLOR_FORCE enable, otherwise undecided (the stream's TTY-ness decides).
1761
- */
1762
1627
  export function useColor() {
1763
1628
  if (host.env['NO_COLOR'] || host.env['FORCE_COLOR'] === '0' || host.env['FORCE_COLOR'] === 'false')
1764
1629
  return false;
@@ -1766,4 +1631,3 @@ export function useColor() {
1766
1631
  return true;
1767
1632
  return undefined;
1768
1633
  }
1769
- //# sourceMappingURL=command.js.map