@guanghechen/commander 4.7.3 → 4.7.4

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,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.7.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix commander completion metadata and negative option rules.
8
+ - Include built-in control options (help/version) in completion metadata.
9
+ - Prevent generating negative completions for reserved controls (--no-help/--no-version).
10
+ - Align completion option metadata with explicit type/args semantics.
11
+
3
12
  ## 4.7.3
4
13
 
5
14
  ### Patch Changes
@@ -864,7 +864,15 @@ class Command {
864
864
  return ` ${outputLabel} ${desc}`;
865
865
  }
866
866
  getCompletionMeta() {
867
- const allOptions = this.#resolveOptionPolicy().mergedOptions;
867
+ const optionMap = new Map();
868
+ for (const option of this.#resolveOptionPolicy().mergedOptions) {
869
+ optionMap.set(option.long, option);
870
+ }
871
+ optionMap.set('help', BUILTIN_HELP_OPTION);
872
+ if (this.#supportsBuiltinVersion()) {
873
+ optionMap.set('version', BUILTIN_VERSION_OPTION);
874
+ }
875
+ const allOptions = Array.from(optionMap.values());
868
876
  const options = [];
869
877
  const argumentsMeta = [];
870
878
  for (const opt of allOptions) {
@@ -872,7 +880,8 @@ class Command {
872
880
  long: opt.long,
873
881
  short: opt.short,
874
882
  desc: opt.desc,
875
- takesValue: opt.args !== 'none',
883
+ type: opt.type,
884
+ args: opt.args,
876
885
  choices: opt.choices?.map(choice => String(choice)),
877
886
  });
878
887
  }
package/lib/cjs/node.cjs CHANGED
@@ -877,7 +877,15 @@ class Command {
877
877
  return ` ${outputLabel} ${desc}`;
878
878
  }
879
879
  getCompletionMeta() {
880
- const allOptions = this.#resolveOptionPolicy().mergedOptions;
880
+ const optionMap = new Map();
881
+ for (const option of this.#resolveOptionPolicy().mergedOptions) {
882
+ optionMap.set(option.long, option);
883
+ }
884
+ optionMap.set('help', BUILTIN_HELP_OPTION);
885
+ if (this.#supportsBuiltinVersion()) {
886
+ optionMap.set('version', BUILTIN_VERSION_OPTION);
887
+ }
888
+ const allOptions = Array.from(optionMap.values());
881
889
  const options = [];
882
890
  const argumentsMeta = [];
883
891
  for (const opt of allOptions) {
@@ -885,7 +893,8 @@ class Command {
885
893
  long: opt.long,
886
894
  short: opt.short,
887
895
  desc: opt.desc,
888
- takesValue: opt.args !== 'none',
896
+ type: opt.type,
897
+ args: opt.args,
889
898
  choices: opt.choices?.map(choice => String(choice)),
890
899
  });
891
900
  }
@@ -2133,6 +2142,12 @@ class Coerce {
2133
2142
  function camelToKebabCase(str) {
2134
2143
  return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
2135
2144
  }
2145
+ function canGenerateNegativeCompletion(opt) {
2146
+ return (opt.type === 'boolean' && opt.args === 'none' && opt.long !== 'help' && opt.long !== 'version');
2147
+ }
2148
+ function optionTakesValue(opt) {
2149
+ return opt.args !== 'none';
2150
+ }
2136
2151
  const COMPLETION_SHELL_STATE = Symbol('completion-shell-state');
2137
2152
  function getCommandPath(ctx) {
2138
2153
  const names = ctx.chain
@@ -2295,7 +2310,7 @@ class BashCompletion {
2295
2310
  if (opt.short)
2296
2311
  optParts.push(this.#escapeWord(`-${opt.short}`));
2297
2312
  optParts.push(this.#escapeWord(`--${kebabLong}`));
2298
- if (!opt.takesValue) {
2313
+ if (canGenerateNegativeCompletion(opt)) {
2299
2314
  optParts.push(this.#escapeWord(`--no-${kebabLong}`));
2300
2315
  }
2301
2316
  }
@@ -2327,7 +2342,7 @@ class BashCompletion {
2327
2342
  return words.map(choice => this.#escapeWord(choice)).join(' ');
2328
2343
  }
2329
2344
  #appendChoiceLogicForCommand(lines, indent, cmd, depth) {
2330
- const valueOptions = cmd.options.filter(opt => opt.takesValue);
2345
+ const valueOptions = cmd.options.filter(optionTakesValue);
2331
2346
  const valueOptionsWithChoices = valueOptions.filter(opt => opt.choices && opt.choices.length > 0);
2332
2347
  const valueLongPatterns = valueOptions.map(opt => `--${camelToKebabCase(opt.long)}`);
2333
2348
  const valueShortPatterns = valueOptions
@@ -2454,7 +2469,7 @@ class FishCompletion {
2454
2469
  line += ` -xa '${opt.choices.map(choice => this.#escapeChoice(choice)).join(' ')}'`;
2455
2470
  }
2456
2471
  lines.push(line);
2457
- if (!opt.takesValue) {
2472
+ if (canGenerateNegativeCompletion(opt)) {
2458
2473
  let noLine = `complete -c ${this.#programName}`;
2459
2474
  if (condition)
2460
2475
  noLine += ` -n '${condition}'`;
@@ -2464,11 +2479,11 @@ class FishCompletion {
2464
2479
  }
2465
2480
  }
2466
2481
  const valueOptionLongs = cmd.options
2467
- .filter(opt => opt.takesValue)
2482
+ .filter(optionTakesValue)
2468
2483
  .map(opt => camelToKebabCase(opt.long))
2469
2484
  .join(',');
2470
2485
  const valueOptionShorts = cmd.options
2471
- .filter(opt => opt.takesValue && opt.short)
2486
+ .filter(opt => optionTakesValue(opt) && opt.short)
2472
2487
  .map(opt => opt.short)
2473
2488
  .join(',');
2474
2489
  const argCount = cmd.arguments.length;
@@ -2667,7 +2682,7 @@ class PwshCompletion {
2667
2682
  ' if ($token.StartsWith("--")) {',
2668
2683
  ' if ($token.Contains("=")) { continue }',
2669
2684
  ' foreach ($opt in $cmd.options) {',
2670
- ' if ($token -eq "--$($opt.long)" -and $opt.takesValue) {',
2685
+ ' if ($token -eq "--$($opt.long)" -and $opt.args -ne "none") {',
2671
2686
  ' $expectValue = $true',
2672
2687
  ' break',
2673
2688
  ' }',
@@ -2677,7 +2692,7 @@ class PwshCompletion {
2677
2692
  ' if ($token.StartsWith("-") -and $token -ne "-") {',
2678
2693
  ' if ($token.Length -eq 2) {',
2679
2694
  ' foreach ($opt in $cmd.options) {',
2680
- ' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.takesValue) {',
2695
+ ' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.args -ne "none") {',
2681
2696
  ' $expectValue = $true',
2682
2697
  ' break',
2683
2698
  ' }',
@@ -2729,7 +2744,7 @@ class PwshCompletion {
2729
2744
  ' $opt.description',
2730
2745
  ' )',
2731
2746
  ' }',
2732
- ' if ($opt.isBoolean -and "--no-$($opt.long)" -like "$current*") {',
2747
+ ' if ($opt.canNegate -and "--no-$($opt.long)" -like "$current*") {',
2733
2748
  ' $completions += [System.Management.Automation.CompletionResult]::new(',
2734
2749
  ' "--no-$($opt.long)",',
2735
2750
  ' "no-$($opt.long)",',
@@ -2779,8 +2794,9 @@ class PwshCompletion {
2779
2794
  lines.push(`${indent} short = '${opt.short}'`);
2780
2795
  lines.push(`${indent} long = '${kebabLong}'`);
2781
2796
  lines.push(`${indent} description = '${this.#escape(opt.desc)}'`);
2782
- lines.push(`${indent} isBoolean = $${!opt.takesValue}`);
2783
- lines.push(`${indent} takesValue = $${opt.takesValue}`);
2797
+ lines.push(`${indent} type = '${opt.type}'`);
2798
+ lines.push(`${indent} args = '${opt.args}'`);
2799
+ lines.push(`${indent} canNegate = $${canGenerateNegativeCompletion(opt)}`);
2784
2800
  if (opt.choices) {
2785
2801
  lines.push(`${indent} choices = @('${opt.choices
2786
2802
  .map(choice => this.#escape(choice))
@@ -862,7 +862,15 @@ class Command {
862
862
  return ` ${outputLabel} ${desc}`;
863
863
  }
864
864
  getCompletionMeta() {
865
- const allOptions = this.#resolveOptionPolicy().mergedOptions;
865
+ const optionMap = new Map();
866
+ for (const option of this.#resolveOptionPolicy().mergedOptions) {
867
+ optionMap.set(option.long, option);
868
+ }
869
+ optionMap.set('help', BUILTIN_HELP_OPTION);
870
+ if (this.#supportsBuiltinVersion()) {
871
+ optionMap.set('version', BUILTIN_VERSION_OPTION);
872
+ }
873
+ const allOptions = Array.from(optionMap.values());
866
874
  const options = [];
867
875
  const argumentsMeta = [];
868
876
  for (const opt of allOptions) {
@@ -870,7 +878,8 @@ class Command {
870
878
  long: opt.long,
871
879
  short: opt.short,
872
880
  desc: opt.desc,
873
- takesValue: opt.args !== 'none',
881
+ type: opt.type,
882
+ args: opt.args,
874
883
  choices: opt.choices?.map(choice => String(choice)),
875
884
  });
876
885
  }
package/lib/esm/node.mjs CHANGED
@@ -875,7 +875,15 @@ class Command {
875
875
  return ` ${outputLabel} ${desc}`;
876
876
  }
877
877
  getCompletionMeta() {
878
- const allOptions = this.#resolveOptionPolicy().mergedOptions;
878
+ const optionMap = new Map();
879
+ for (const option of this.#resolveOptionPolicy().mergedOptions) {
880
+ optionMap.set(option.long, option);
881
+ }
882
+ optionMap.set('help', BUILTIN_HELP_OPTION);
883
+ if (this.#supportsBuiltinVersion()) {
884
+ optionMap.set('version', BUILTIN_VERSION_OPTION);
885
+ }
886
+ const allOptions = Array.from(optionMap.values());
879
887
  const options = [];
880
888
  const argumentsMeta = [];
881
889
  for (const opt of allOptions) {
@@ -883,7 +891,8 @@ class Command {
883
891
  long: opt.long,
884
892
  short: opt.short,
885
893
  desc: opt.desc,
886
- takesValue: opt.args !== 'none',
894
+ type: opt.type,
895
+ args: opt.args,
887
896
  choices: opt.choices?.map(choice => String(choice)),
888
897
  });
889
898
  }
@@ -2131,6 +2140,12 @@ class Coerce {
2131
2140
  function camelToKebabCase(str) {
2132
2141
  return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
2133
2142
  }
2143
+ function canGenerateNegativeCompletion(opt) {
2144
+ return (opt.type === 'boolean' && opt.args === 'none' && opt.long !== 'help' && opt.long !== 'version');
2145
+ }
2146
+ function optionTakesValue(opt) {
2147
+ return opt.args !== 'none';
2148
+ }
2134
2149
  const COMPLETION_SHELL_STATE = Symbol('completion-shell-state');
2135
2150
  function getCommandPath(ctx) {
2136
2151
  const names = ctx.chain
@@ -2293,7 +2308,7 @@ class BashCompletion {
2293
2308
  if (opt.short)
2294
2309
  optParts.push(this.#escapeWord(`-${opt.short}`));
2295
2310
  optParts.push(this.#escapeWord(`--${kebabLong}`));
2296
- if (!opt.takesValue) {
2311
+ if (canGenerateNegativeCompletion(opt)) {
2297
2312
  optParts.push(this.#escapeWord(`--no-${kebabLong}`));
2298
2313
  }
2299
2314
  }
@@ -2325,7 +2340,7 @@ class BashCompletion {
2325
2340
  return words.map(choice => this.#escapeWord(choice)).join(' ');
2326
2341
  }
2327
2342
  #appendChoiceLogicForCommand(lines, indent, cmd, depth) {
2328
- const valueOptions = cmd.options.filter(opt => opt.takesValue);
2343
+ const valueOptions = cmd.options.filter(optionTakesValue);
2329
2344
  const valueOptionsWithChoices = valueOptions.filter(opt => opt.choices && opt.choices.length > 0);
2330
2345
  const valueLongPatterns = valueOptions.map(opt => `--${camelToKebabCase(opt.long)}`);
2331
2346
  const valueShortPatterns = valueOptions
@@ -2452,7 +2467,7 @@ class FishCompletion {
2452
2467
  line += ` -xa '${opt.choices.map(choice => this.#escapeChoice(choice)).join(' ')}'`;
2453
2468
  }
2454
2469
  lines.push(line);
2455
- if (!opt.takesValue) {
2470
+ if (canGenerateNegativeCompletion(opt)) {
2456
2471
  let noLine = `complete -c ${this.#programName}`;
2457
2472
  if (condition)
2458
2473
  noLine += ` -n '${condition}'`;
@@ -2462,11 +2477,11 @@ class FishCompletion {
2462
2477
  }
2463
2478
  }
2464
2479
  const valueOptionLongs = cmd.options
2465
- .filter(opt => opt.takesValue)
2480
+ .filter(optionTakesValue)
2466
2481
  .map(opt => camelToKebabCase(opt.long))
2467
2482
  .join(',');
2468
2483
  const valueOptionShorts = cmd.options
2469
- .filter(opt => opt.takesValue && opt.short)
2484
+ .filter(opt => optionTakesValue(opt) && opt.short)
2470
2485
  .map(opt => opt.short)
2471
2486
  .join(',');
2472
2487
  const argCount = cmd.arguments.length;
@@ -2665,7 +2680,7 @@ class PwshCompletion {
2665
2680
  ' if ($token.StartsWith("--")) {',
2666
2681
  ' if ($token.Contains("=")) { continue }',
2667
2682
  ' foreach ($opt in $cmd.options) {',
2668
- ' if ($token -eq "--$($opt.long)" -and $opt.takesValue) {',
2683
+ ' if ($token -eq "--$($opt.long)" -and $opt.args -ne "none") {',
2669
2684
  ' $expectValue = $true',
2670
2685
  ' break',
2671
2686
  ' }',
@@ -2675,7 +2690,7 @@ class PwshCompletion {
2675
2690
  ' if ($token.StartsWith("-") -and $token -ne "-") {',
2676
2691
  ' if ($token.Length -eq 2) {',
2677
2692
  ' foreach ($opt in $cmd.options) {',
2678
- ' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.takesValue) {',
2693
+ ' if ($opt.short -and $token -eq "-$($opt.short)" -and $opt.args -ne "none") {',
2679
2694
  ' $expectValue = $true',
2680
2695
  ' break',
2681
2696
  ' }',
@@ -2727,7 +2742,7 @@ class PwshCompletion {
2727
2742
  ' $opt.description',
2728
2743
  ' )',
2729
2744
  ' }',
2730
- ' if ($opt.isBoolean -and "--no-$($opt.long)" -like "$current*") {',
2745
+ ' if ($opt.canNegate -and "--no-$($opt.long)" -like "$current*") {',
2731
2746
  ' $completions += [System.Management.Automation.CompletionResult]::new(',
2732
2747
  ' "--no-$($opt.long)",',
2733
2748
  ' "no-$($opt.long)",',
@@ -2777,8 +2792,9 @@ class PwshCompletion {
2777
2792
  lines.push(`${indent} short = '${opt.short}'`);
2778
2793
  lines.push(`${indent} long = '${kebabLong}'`);
2779
2794
  lines.push(`${indent} description = '${this.#escape(opt.desc)}'`);
2780
- lines.push(`${indent} isBoolean = $${!opt.takesValue}`);
2781
- lines.push(`${indent} takesValue = $${opt.takesValue}`);
2795
+ lines.push(`${indent} type = '${opt.type}'`);
2796
+ lines.push(`${indent} args = '${opt.args}'`);
2797
+ lines.push(`${indent} canNegate = $${canGenerateNegativeCompletion(opt)}`);
2782
2798
  if (opt.choices) {
2783
2799
  lines.push(`${indent} choices = @('${opt.choices
2784
2800
  .map(choice => this.#escape(choice))
@@ -365,8 +365,10 @@ interface ICompletionOptionMeta {
365
365
  short?: string;
366
366
  /** Description */
367
367
  desc: string;
368
- /** Whether option takes value (args !== 'none') */
369
- takesValue: boolean;
368
+ /** Option type */
369
+ type: ICommandOptionType;
370
+ /** Option args mode */
371
+ args: ICommandOptionArgs;
370
372
  /** Allowed values */
371
373
  choices?: string[];
372
374
  }
@@ -365,8 +365,10 @@ interface ICompletionOptionMeta {
365
365
  short?: string;
366
366
  /** Description */
367
367
  desc: string;
368
- /** Whether option takes value (args !== 'none') */
369
- takesValue: boolean;
368
+ /** Option type */
369
+ type: ICommandOptionType;
370
+ /** Option args mode */
371
+ args: ICommandOptionArgs;
370
372
  /** Allowed values */
371
373
  choices?: string[];
372
374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guanghechen/commander",
3
- "version": "4.7.3",
3
+ "version": "4.7.4",
4
4
  "description": "A minimal, type-safe command-line interface builder with fluent API",
5
5
  "author": {
6
6
  "name": "guanghechen",
@@ -44,8 +44,8 @@
44
44
  "README.md"
45
45
  ],
46
46
  "dependencies": {
47
- "@guanghechen/env": "^2.0.2",
48
- "@guanghechen/reporter": "^3.3.0"
47
+ "@guanghechen/reporter": "^3.3.0",
48
+ "@guanghechen/env": "^2.0.2"
49
49
  },
50
50
  "scripts": {
51
51
  "build": "rollup -c ../../rollup.config.mjs",