@guanghechen/commander 4.3.0 → 4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - enforce per-node help subcommand semantics
8
+
3
9
  ## 4.3.0
4
10
 
5
11
  ### Minor Changes
package/lib/cjs/index.cjs CHANGED
@@ -613,18 +613,48 @@ class Command {
613
613
  }),
614
614
  };
615
615
  }
616
+ #findSubcommandEntry(token) {
617
+ return this.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
618
+ }
619
+ #createUnknownSubcommandError(subcommand) {
620
+ const commandPath = this.#getCommandPath();
621
+ return new CommanderError('UnknownSubcommand', `unknown subcommand "${subcommand}" for command "${commandPath}"`, commandPath);
622
+ }
616
623
  #processHelpSubcommand(argv) {
617
- if (!this.#builtin.command.help)
618
- return argv;
619
- if (argv.length < 1 || argv[0] !== 'help')
620
- return argv;
621
- if (argv.length === 1 || this.#subcommandsList.length === 0) {
622
- return ['--help'];
623
- }
624
- const subName = argv[1];
625
- const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
626
- if (entry) {
627
- return [subName, '--help', ...argv.slice(2)];
624
+ let current = this;
625
+ for (let i = 0; i < argv.length; ++i) {
626
+ const token = argv[i];
627
+ if (token.startsWith('-')) {
628
+ return argv;
629
+ }
630
+ if (token === 'help') {
631
+ if (!current.#builtin.command.help) {
632
+ if (current.#subcommandsList.length > 0) {
633
+ throw current.#createUnknownSubcommandError('help');
634
+ }
635
+ return argv;
636
+ }
637
+ if (current.#subcommandsList.length === 0) {
638
+ return argv;
639
+ }
640
+ const target = argv[i + 1];
641
+ if (target === undefined) {
642
+ return [...argv.slice(0, i), '--help'];
643
+ }
644
+ const targetEntry = current.#findSubcommandEntry(target);
645
+ if (targetEntry === undefined) {
646
+ throw current.#createUnknownSubcommandError(target);
647
+ }
648
+ if (argv[i + 2] !== undefined) {
649
+ throw new CommanderError('UnexpectedArgument', 'help subcommand accepts at most one subcommand argument', current.#getCommandPath());
650
+ }
651
+ return [...argv.slice(0, i), target, '--help'];
652
+ }
653
+ const entry = current.#findSubcommandEntry(token);
654
+ if (entry === undefined) {
655
+ return argv;
656
+ }
657
+ current = entry.command;
628
658
  }
629
659
  return argv;
630
660
  }
@@ -636,7 +666,7 @@ class Command {
636
666
  const token = argv[idx];
637
667
  if (token.startsWith('-'))
638
668
  break;
639
- const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
669
+ const entry = current.#findSubcommandEntry(token);
640
670
  if (!entry)
641
671
  break;
642
672
  current = entry.command;
package/lib/esm/index.mjs CHANGED
@@ -591,18 +591,48 @@ class Command {
591
591
  }),
592
592
  };
593
593
  }
594
+ #findSubcommandEntry(token) {
595
+ return this.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
596
+ }
597
+ #createUnknownSubcommandError(subcommand) {
598
+ const commandPath = this.#getCommandPath();
599
+ return new CommanderError('UnknownSubcommand', `unknown subcommand "${subcommand}" for command "${commandPath}"`, commandPath);
600
+ }
594
601
  #processHelpSubcommand(argv) {
595
- if (!this.#builtin.command.help)
596
- return argv;
597
- if (argv.length < 1 || argv[0] !== 'help')
598
- return argv;
599
- if (argv.length === 1 || this.#subcommandsList.length === 0) {
600
- return ['--help'];
601
- }
602
- const subName = argv[1];
603
- const entry = this.#subcommandsList.find(e => e.name === subName || e.aliases.includes(subName));
604
- if (entry) {
605
- return [subName, '--help', ...argv.slice(2)];
602
+ let current = this;
603
+ for (let i = 0; i < argv.length; ++i) {
604
+ const token = argv[i];
605
+ if (token.startsWith('-')) {
606
+ return argv;
607
+ }
608
+ if (token === 'help') {
609
+ if (!current.#builtin.command.help) {
610
+ if (current.#subcommandsList.length > 0) {
611
+ throw current.#createUnknownSubcommandError('help');
612
+ }
613
+ return argv;
614
+ }
615
+ if (current.#subcommandsList.length === 0) {
616
+ return argv;
617
+ }
618
+ const target = argv[i + 1];
619
+ if (target === undefined) {
620
+ return [...argv.slice(0, i), '--help'];
621
+ }
622
+ const targetEntry = current.#findSubcommandEntry(target);
623
+ if (targetEntry === undefined) {
624
+ throw current.#createUnknownSubcommandError(target);
625
+ }
626
+ if (argv[i + 2] !== undefined) {
627
+ throw new CommanderError('UnexpectedArgument', 'help subcommand accepts at most one subcommand argument', current.#getCommandPath());
628
+ }
629
+ return [...argv.slice(0, i), target, '--help'];
630
+ }
631
+ const entry = current.#findSubcommandEntry(token);
632
+ if (entry === undefined) {
633
+ return argv;
634
+ }
635
+ current = entry.command;
606
636
  }
607
637
  return argv;
608
638
  }
@@ -614,7 +644,7 @@ class Command {
614
644
  const token = argv[idx];
615
645
  if (token.startsWith('-'))
616
646
  break;
617
- const entry = current.#subcommandsList.find(e => e.name === token || e.aliases.includes(token));
647
+ const entry = current.#findSubcommandEntry(token);
618
648
  if (!entry)
619
649
  break;
620
650
  current = entry.command;
@@ -224,7 +224,7 @@ interface ICommandParseResult {
224
224
  rawArgs: string[];
225
225
  }
226
226
  /** Error kinds for command parsing */
227
- type ICommanderErrorKind = 'InvalidOptionFormat' | 'InvalidNegativeOption' | 'NegativeOptionWithValue' | 'NegativeOptionType' | 'UnknownOption' | 'UnexpectedArgument' | 'MissingValue' | 'InvalidType' | 'UnsupportedShortSyntax' | 'OptionConflict' | 'MissingRequired' | 'InvalidChoice' | 'InvalidBooleanValue' | 'MissingRequiredArgument' | 'TooManyArguments' | 'ConfigurationError';
227
+ type ICommanderErrorKind = 'InvalidOptionFormat' | 'InvalidNegativeOption' | 'NegativeOptionWithValue' | 'NegativeOptionType' | 'UnknownOption' | 'UnknownSubcommand' | 'UnexpectedArgument' | 'MissingValue' | 'InvalidType' | 'UnsupportedShortSyntax' | 'OptionConflict' | 'MissingRequired' | 'InvalidChoice' | 'InvalidBooleanValue' | 'MissingRequiredArgument' | 'TooManyArguments' | 'ConfigurationError';
228
228
  /** Commander error with structured information */
229
229
  declare class CommanderError extends Error {
230
230
  readonly kind: ICommanderErrorKind;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanghechen/commander",
3
- "version": "4.3.0",
3
+ "version": "4.4.0",
4
4
  "description": "A minimal, type-safe command-line interface builder with fluent API",
5
5
  "author": {
6
6
  "name": "guanghechen",