@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 +9 -0
- package/lib/cjs/browser.cjs +11 -2
- package/lib/cjs/node.cjs +28 -12
- package/lib/esm/browser.mjs +11 -2
- package/lib/esm/node.mjs +28 -12
- package/lib/types/browser.d.ts +4 -2
- package/lib/types/node.d.ts +4 -2
- package/package.json +3 -3
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
|
package/lib/cjs/browser.cjs
CHANGED
|
@@ -864,7 +864,15 @@ class Command {
|
|
|
864
864
|
return ` ${outputLabel} ${desc}`;
|
|
865
865
|
}
|
|
866
866
|
getCompletionMeta() {
|
|
867
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
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 (
|
|
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(
|
|
2482
|
+
.filter(optionTakesValue)
|
|
2468
2483
|
.map(opt => camelToKebabCase(opt.long))
|
|
2469
2484
|
.join(',');
|
|
2470
2485
|
const valueOptionShorts = cmd.options
|
|
2471
|
-
.filter(opt => opt
|
|
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.
|
|
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.
|
|
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.
|
|
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}
|
|
2783
|
-
lines.push(`${indent}
|
|
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))
|
package/lib/esm/browser.mjs
CHANGED
|
@@ -862,7 +862,15 @@ class Command {
|
|
|
862
862
|
return ` ${outputLabel} ${desc}`;
|
|
863
863
|
}
|
|
864
864
|
getCompletionMeta() {
|
|
865
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
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 (
|
|
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(
|
|
2480
|
+
.filter(optionTakesValue)
|
|
2466
2481
|
.map(opt => camelToKebabCase(opt.long))
|
|
2467
2482
|
.join(',');
|
|
2468
2483
|
const valueOptionShorts = cmd.options
|
|
2469
|
-
.filter(opt => opt
|
|
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.
|
|
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.
|
|
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.
|
|
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}
|
|
2781
|
-
lines.push(`${indent}
|
|
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))
|
package/lib/types/browser.d.ts
CHANGED
|
@@ -365,8 +365,10 @@ interface ICompletionOptionMeta {
|
|
|
365
365
|
short?: string;
|
|
366
366
|
/** Description */
|
|
367
367
|
desc: string;
|
|
368
|
-
/**
|
|
369
|
-
|
|
368
|
+
/** Option type */
|
|
369
|
+
type: ICommandOptionType;
|
|
370
|
+
/** Option args mode */
|
|
371
|
+
args: ICommandOptionArgs;
|
|
370
372
|
/** Allowed values */
|
|
371
373
|
choices?: string[];
|
|
372
374
|
}
|
package/lib/types/node.d.ts
CHANGED
|
@@ -365,8 +365,10 @@ interface ICompletionOptionMeta {
|
|
|
365
365
|
short?: string;
|
|
366
366
|
/** Description */
|
|
367
367
|
desc: string;
|
|
368
|
-
/**
|
|
369
|
-
|
|
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
|
+
"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/
|
|
48
|
-
"@guanghechen/
|
|
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",
|