@guanghechen/commander 4.7.0 → 4.7.2
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 +14 -0
- package/lib/cjs/browser.cjs +290 -42
- package/lib/cjs/index.cjs +2089 -0
- package/lib/cjs/node.cjs +598 -54
- package/lib/esm/browser.mjs +290 -42
- package/lib/esm/index.mjs +2054 -0
- package/lib/esm/node.mjs +598 -54
- package/lib/types/browser.d.ts +27 -6
- package/lib/types/index.d.ts +560 -0
- package/lib/types/node.d.ts +27 -6
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 4.7.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Improve commander release quality by enforcing command validation rules and refining fish
|
|
8
|
+
completion command-chain matching.
|
|
9
|
+
|
|
10
|
+
## 4.7.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Fix built-in `--version` handling so leaf commands with a configured `version` print their own
|
|
15
|
+
version.
|
|
16
|
+
|
|
3
17
|
## 4.7.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
package/lib/cjs/browser.cjs
CHANGED
|
@@ -184,6 +184,116 @@ function kebabToCamelCase(str) {
|
|
|
184
184
|
function camelToKebabCase(str) {
|
|
185
185
|
return str.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
|
|
186
186
|
}
|
|
187
|
+
const ANSI_ESCAPE_REGEX = new RegExp(String.raw `\\x1B\\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
188
|
+
const DECIMAL_INTEGER_REGEX = /^\d(?:_?\d)*$/;
|
|
189
|
+
const DECIMAL_FRACTION_REGEX = /^\d(?:_?\d)*$/;
|
|
190
|
+
const DECIMAL_EXPONENT_REGEX = /^[eE][+-]?\d(?:_?\d)*$/;
|
|
191
|
+
const BINARY_LITERAL_REGEX = /^0[bB][01](?:_?[01])*$/;
|
|
192
|
+
const OCTAL_LITERAL_REGEX = /^0[oO][0-7](?:_?[0-7])*$/;
|
|
193
|
+
const HEX_LITERAL_REGEX = /^0[xX][0-9a-fA-F](?:_?[0-9a-fA-F])*$/;
|
|
194
|
+
function stripAnsi(value) {
|
|
195
|
+
return value.replace(ANSI_ESCAPE_REGEX, '');
|
|
196
|
+
}
|
|
197
|
+
function isCombiningMark(codePoint) {
|
|
198
|
+
return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
|
|
199
|
+
(codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
|
|
200
|
+
(codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
|
|
201
|
+
(codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
|
|
202
|
+
(codePoint >= 0xfe20 && codePoint <= 0xfe2f));
|
|
203
|
+
}
|
|
204
|
+
function isWideCodePoint(codePoint) {
|
|
205
|
+
if (codePoint < 0x1100) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return (codePoint <= 0x115f ||
|
|
209
|
+
codePoint === 0x2329 ||
|
|
210
|
+
codePoint === 0x232a ||
|
|
211
|
+
(codePoint >= 0x2e80 && codePoint <= 0x3247 && codePoint !== 0x303f) ||
|
|
212
|
+
(codePoint >= 0x3250 && codePoint <= 0x4dbf) ||
|
|
213
|
+
(codePoint >= 0x4e00 && codePoint <= 0xa4c6) ||
|
|
214
|
+
(codePoint >= 0xa960 && codePoint <= 0xa97c) ||
|
|
215
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
216
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
217
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
218
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6b) ||
|
|
219
|
+
(codePoint >= 0xff01 && codePoint <= 0xff60) ||
|
|
220
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
221
|
+
(codePoint >= 0x1b000 && codePoint <= 0x1b001) ||
|
|
222
|
+
(codePoint >= 0x1f200 && codePoint <= 0x1f251) ||
|
|
223
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
|
|
224
|
+
}
|
|
225
|
+
function getDisplayWidth(value) {
|
|
226
|
+
const normalized = stripAnsi(value).normalize('NFC');
|
|
227
|
+
let width = 0;
|
|
228
|
+
for (const char of normalized) {
|
|
229
|
+
const codePoint = char.codePointAt(0);
|
|
230
|
+
if (codePoint === undefined || isCombiningMark(codePoint)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
width += isWideCodePoint(codePoint) ? 2 : 1;
|
|
234
|
+
}
|
|
235
|
+
return width;
|
|
236
|
+
}
|
|
237
|
+
function padDisplayEnd(value, targetWidth) {
|
|
238
|
+
const width = getDisplayWidth(value);
|
|
239
|
+
if (width >= targetWidth) {
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
return value + ' '.repeat(targetWidth - width);
|
|
243
|
+
}
|
|
244
|
+
function isValidPrimitiveNumberLiteral(rawValue) {
|
|
245
|
+
if (rawValue.trim() !== rawValue || rawValue.length === 0) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
if (rawValue === 'NaN' || rawValue === 'Infinity' || rawValue === '-Infinity') {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
if (BINARY_LITERAL_REGEX.test(rawValue)) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
if (OCTAL_LITERAL_REGEX.test(rawValue)) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
if (HEX_LITERAL_REGEX.test(rawValue)) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
const sign = rawValue[0] === '+' || rawValue[0] === '-' ? rawValue[0] : '';
|
|
261
|
+
const body = sign ? rawValue.slice(1) : rawValue;
|
|
262
|
+
if (body.length === 0) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
const expIndex = body.search(/[eE]/);
|
|
266
|
+
const basePart = expIndex === -1 ? body : body.slice(0, expIndex);
|
|
267
|
+
const expPart = expIndex === -1 ? '' : body.slice(expIndex);
|
|
268
|
+
if (expPart && !DECIMAL_EXPONENT_REGEX.test(expPart)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (basePart.includes('.')) {
|
|
272
|
+
const decimalParts = basePart.split('.');
|
|
273
|
+
if (decimalParts.length !== 2) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const [intPart, fracPart] = decimalParts;
|
|
277
|
+
const intOk = intPart.length === 0 || DECIMAL_INTEGER_REGEX.test(intPart);
|
|
278
|
+
const fracOk = fracPart.length === 0 || DECIMAL_FRACTION_REGEX.test(fracPart);
|
|
279
|
+
if (!intOk || !fracOk) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
return intPart.length > 0 || fracPart.length > 0;
|
|
283
|
+
}
|
|
284
|
+
return DECIMAL_INTEGER_REGEX.test(basePart);
|
|
285
|
+
}
|
|
286
|
+
function parsePrimitiveNumber(rawValue) {
|
|
287
|
+
if (!isValidPrimitiveNumberLiteral(rawValue)) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
const normalized = rawValue.replaceAll('_', '');
|
|
291
|
+
const value = Number(normalized);
|
|
292
|
+
if (!Number.isFinite(value)) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
187
297
|
function tokenizeLongOption(arg, commandPath) {
|
|
188
298
|
const eqIdx = arg.indexOf('=');
|
|
189
299
|
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
@@ -420,9 +530,13 @@ class Command {
|
|
|
420
530
|
if (cmd.#parent && cmd.#parent !== this) {
|
|
421
531
|
throw new CommanderError('ConfigurationError', `command "${cmd.#name}" already has a parent`, this.#getCommandPath());
|
|
422
532
|
}
|
|
533
|
+
const occupied = this.#subcommandsMap.get(name);
|
|
534
|
+
if (occupied && occupied !== cmd) {
|
|
535
|
+
throw new CommanderError('ConfigurationError', `subcommand name/alias "${name}" conflicts with an existing command`, this.#getCommandPath());
|
|
536
|
+
}
|
|
423
537
|
const existing = this.#subcommandsList.find(e => e.command === cmd);
|
|
424
538
|
if (existing) {
|
|
425
|
-
if (existing.aliases.includes(name)) {
|
|
539
|
+
if (existing.name === name || existing.aliases.includes(name)) {
|
|
426
540
|
return this;
|
|
427
541
|
}
|
|
428
542
|
existing.aliases.push(name);
|
|
@@ -551,10 +665,32 @@ class Command {
|
|
|
551
665
|
else if (arg.kind === 'optional') {
|
|
552
666
|
usage += ` [${arg.name}]`;
|
|
553
667
|
}
|
|
668
|
+
else if (arg.kind === 'some') {
|
|
669
|
+
usage += ` <${arg.name}...>`;
|
|
670
|
+
}
|
|
554
671
|
else {
|
|
555
672
|
usage += ` [${arg.name}...]`;
|
|
556
673
|
}
|
|
557
674
|
}
|
|
675
|
+
const argumentsLines = [];
|
|
676
|
+
for (const arg of this.#arguments) {
|
|
677
|
+
const sig = arg.kind === 'required'
|
|
678
|
+
? `<${arg.name}>`
|
|
679
|
+
: arg.kind === 'optional'
|
|
680
|
+
? `[${arg.name}]`
|
|
681
|
+
: arg.kind === 'some'
|
|
682
|
+
? `<${arg.name}...>`
|
|
683
|
+
: `[${arg.name}...]`;
|
|
684
|
+
const metadata = [`[type: ${arg.type}]`];
|
|
685
|
+
if (arg.kind === 'optional' && arg.default !== undefined) {
|
|
686
|
+
metadata.push(`[default: ${JSON.stringify(arg.default)}]`);
|
|
687
|
+
}
|
|
688
|
+
if (arg.choices && arg.choices.length > 0) {
|
|
689
|
+
metadata.push(`[choices: ${arg.choices.map(choice => JSON.stringify(choice)).join(', ')}]`);
|
|
690
|
+
}
|
|
691
|
+
const desc = metadata.length > 0 ? `${arg.desc} ${metadata.join(' ')}` : arg.desc;
|
|
692
|
+
argumentsLines.push({ sig, desc });
|
|
693
|
+
}
|
|
558
694
|
const options = [];
|
|
559
695
|
for (const opt of allOptions) {
|
|
560
696
|
const kebabLong = camelToKebabCase(opt.long);
|
|
@@ -568,7 +704,7 @@ class Command {
|
|
|
568
704
|
desc += ` (default: ${JSON.stringify(opt.default)})`;
|
|
569
705
|
}
|
|
570
706
|
if (opt.choices) {
|
|
571
|
-
desc += ` [choices: ${opt.choices.join(', ')}]`;
|
|
707
|
+
desc += ` [choices: ${opt.choices.map(choice => JSON.stringify(choice)).join(', ')}]`;
|
|
572
708
|
}
|
|
573
709
|
options.push({ sig, desc });
|
|
574
710
|
if (opt.type === 'boolean' &&
|
|
@@ -600,6 +736,7 @@ class Command {
|
|
|
600
736
|
return {
|
|
601
737
|
desc: this.#desc,
|
|
602
738
|
usage,
|
|
739
|
+
arguments: argumentsLines,
|
|
603
740
|
options,
|
|
604
741
|
commands,
|
|
605
742
|
examples,
|
|
@@ -607,25 +744,29 @@ class Command {
|
|
|
607
744
|
}
|
|
608
745
|
#renderHelpPlain(helpData) {
|
|
609
746
|
const lines = [];
|
|
747
|
+
const labelWidth = this.#getHelpLabelWidth(helpData);
|
|
610
748
|
lines.push(helpData.desc);
|
|
611
749
|
lines.push('');
|
|
612
750
|
lines.push(helpData.usage);
|
|
613
751
|
lines.push('');
|
|
752
|
+
if (helpData.arguments.length > 0) {
|
|
753
|
+
lines.push('Arguments:');
|
|
754
|
+
for (const { sig, desc } of helpData.arguments) {
|
|
755
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
|
|
756
|
+
}
|
|
757
|
+
lines.push('');
|
|
758
|
+
}
|
|
614
759
|
if (helpData.options.length > 0) {
|
|
615
760
|
lines.push('Options:');
|
|
616
|
-
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
617
761
|
for (const { sig, desc } of helpData.options) {
|
|
618
|
-
|
|
619
|
-
lines.push(` ${sig}${padding}${desc}`);
|
|
762
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth));
|
|
620
763
|
}
|
|
621
764
|
lines.push('');
|
|
622
765
|
}
|
|
623
766
|
if (helpData.commands.length > 0) {
|
|
624
767
|
lines.push('Commands:');
|
|
625
|
-
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
626
768
|
for (const { name, desc } of helpData.commands) {
|
|
627
|
-
|
|
628
|
-
lines.push(` ${name}${padding}${desc}`);
|
|
769
|
+
lines.push(this.#renderAlignedHelpLine(name, desc, labelWidth));
|
|
629
770
|
}
|
|
630
771
|
lines.push('');
|
|
631
772
|
}
|
|
@@ -642,25 +783,29 @@ class Command {
|
|
|
642
783
|
}
|
|
643
784
|
#renderHelpTerminal(helpData) {
|
|
644
785
|
const lines = [];
|
|
786
|
+
const labelWidth = this.#getHelpLabelWidth(helpData);
|
|
645
787
|
lines.push(helpData.desc);
|
|
646
788
|
lines.push('');
|
|
647
789
|
lines.push(styleText(helpData.usage, TERMINAL_STYLE.bold));
|
|
648
790
|
lines.push('');
|
|
791
|
+
if (helpData.arguments.length > 0) {
|
|
792
|
+
lines.push(styleText('Arguments:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
793
|
+
for (const { sig, desc } of helpData.arguments) {
|
|
794
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
|
|
795
|
+
}
|
|
796
|
+
lines.push('');
|
|
797
|
+
}
|
|
649
798
|
if (helpData.options.length > 0) {
|
|
650
799
|
lines.push(styleText('Options:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
651
|
-
const maxSigLen = Math.max(...helpData.options.map(line => line.sig.length));
|
|
652
800
|
for (const { sig, desc } of helpData.options) {
|
|
653
|
-
|
|
654
|
-
lines.push(` ${styleText(sig, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
801
|
+
lines.push(this.#renderAlignedHelpLine(sig, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
|
|
655
802
|
}
|
|
656
803
|
lines.push('');
|
|
657
804
|
}
|
|
658
805
|
if (helpData.commands.length > 0) {
|
|
659
806
|
lines.push(styleText('Commands:', TERMINAL_STYLE.bold, TERMINAL_STYLE.underline));
|
|
660
|
-
const maxNameLen = Math.max(...helpData.commands.map(line => line.name.length));
|
|
661
807
|
for (const { name, desc } of helpData.commands) {
|
|
662
|
-
|
|
663
|
-
lines.push(` ${styleText(name, TERMINAL_STYLE.cyan)}${padding}${desc}`);
|
|
808
|
+
lines.push(this.#renderAlignedHelpLine(name, desc, labelWidth, value => styleText(value, TERMINAL_STYLE.cyan)));
|
|
664
809
|
}
|
|
665
810
|
lines.push('');
|
|
666
811
|
}
|
|
@@ -675,16 +820,41 @@ class Command {
|
|
|
675
820
|
}
|
|
676
821
|
return lines.join('\n');
|
|
677
822
|
}
|
|
823
|
+
#getHelpLabelWidth(helpData) {
|
|
824
|
+
const labels = [
|
|
825
|
+
...helpData.arguments.map(line => line.sig),
|
|
826
|
+
...helpData.options.map(line => line.sig),
|
|
827
|
+
...helpData.commands.map(line => line.name),
|
|
828
|
+
];
|
|
829
|
+
if (labels.length === 0) {
|
|
830
|
+
return 0;
|
|
831
|
+
}
|
|
832
|
+
return Math.max(...labels.map(getDisplayWidth));
|
|
833
|
+
}
|
|
834
|
+
#renderAlignedHelpLine(label, desc, labelWidth, styleLabel) {
|
|
835
|
+
const paddedLabel = padDisplayEnd(label, labelWidth);
|
|
836
|
+
const outputLabel = styleLabel ? styleLabel(paddedLabel) : paddedLabel;
|
|
837
|
+
return ` ${outputLabel} ${desc}`;
|
|
838
|
+
}
|
|
678
839
|
getCompletionMeta() {
|
|
679
840
|
const allOptions = this.#resolveOptionPolicy().mergedOptions;
|
|
680
841
|
const options = [];
|
|
842
|
+
const argumentsMeta = [];
|
|
681
843
|
for (const opt of allOptions) {
|
|
682
844
|
options.push({
|
|
683
845
|
long: opt.long,
|
|
684
846
|
short: opt.short,
|
|
685
847
|
desc: opt.desc,
|
|
686
848
|
takesValue: opt.args !== 'none',
|
|
687
|
-
choices: opt.choices,
|
|
849
|
+
choices: opt.choices?.map(choice => String(choice)),
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
for (const arg of this.#arguments) {
|
|
853
|
+
argumentsMeta.push({
|
|
854
|
+
name: arg.name,
|
|
855
|
+
kind: arg.kind,
|
|
856
|
+
type: arg.type,
|
|
857
|
+
choices: arg.type === 'choice' ? arg.choices?.map(choice => String(choice)) : undefined,
|
|
688
858
|
});
|
|
689
859
|
}
|
|
690
860
|
return {
|
|
@@ -692,6 +862,7 @@ class Command {
|
|
|
692
862
|
desc: this.#desc,
|
|
693
863
|
aliases: [],
|
|
694
864
|
options,
|
|
865
|
+
arguments: argumentsMeta,
|
|
695
866
|
subcommands: this.#subcommandsList.map(entry => {
|
|
696
867
|
const subMeta = entry.command.getCompletionMeta();
|
|
697
868
|
return {
|
|
@@ -1337,8 +1508,8 @@ class Command {
|
|
|
1337
1508
|
return opt.coerce(rawValue);
|
|
1338
1509
|
}
|
|
1339
1510
|
if (opt.type === 'number') {
|
|
1340
|
-
const num =
|
|
1341
|
-
if (
|
|
1511
|
+
const num = parsePrimitiveNumber(rawValue);
|
|
1512
|
+
if (num === undefined) {
|
|
1342
1513
|
throw new CommanderError('InvalidType', `invalid number "${rawValue}" for option "--${camelToKebabCase(opt.long)}"`, this.#getCommandPath());
|
|
1343
1514
|
}
|
|
1344
1515
|
return num;
|
|
@@ -1348,12 +1519,34 @@ class Command {
|
|
|
1348
1519
|
#parseArguments(rawArgs) {
|
|
1349
1520
|
const argumentDefs = this.#arguments;
|
|
1350
1521
|
const args = {};
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1522
|
+
const missing = [];
|
|
1523
|
+
let remaining = rawArgs.length;
|
|
1524
|
+
for (const def of argumentDefs) {
|
|
1525
|
+
if (def.kind === 'required') {
|
|
1526
|
+
if (remaining === 0) {
|
|
1527
|
+
missing.push(def.name);
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
remaining -= 1;
|
|
1531
|
+
}
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (def.kind === 'optional') {
|
|
1535
|
+
if (remaining > 0) {
|
|
1536
|
+
remaining -= 1;
|
|
1537
|
+
}
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
if (def.kind === 'some') {
|
|
1541
|
+
if (remaining === 0) {
|
|
1542
|
+
missing.push(def.name);
|
|
1543
|
+
}
|
|
1544
|
+
remaining = 0;
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
remaining = 0;
|
|
1548
|
+
}
|
|
1549
|
+
if (missing.length > 0) {
|
|
1357
1550
|
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${missing.join(', ')}`, this.#getCommandPath());
|
|
1358
1551
|
}
|
|
1359
1552
|
let index = 0;
|
|
@@ -1364,47 +1557,65 @@ class Command {
|
|
|
1364
1557
|
index = rawArgs.length;
|
|
1365
1558
|
break;
|
|
1366
1559
|
}
|
|
1560
|
+
if (def.kind === 'some') {
|
|
1561
|
+
const rest = rawArgs.slice(index);
|
|
1562
|
+
if (rest.length === 0) {
|
|
1563
|
+
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${def.name}`, this.#getCommandPath());
|
|
1564
|
+
}
|
|
1565
|
+
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1566
|
+
index = rawArgs.length;
|
|
1567
|
+
break;
|
|
1568
|
+
}
|
|
1367
1569
|
const raw = rawArgs[index];
|
|
1368
1570
|
if (raw === undefined) {
|
|
1369
1571
|
if (def.kind === 'optional') {
|
|
1370
1572
|
args[def.name] = def.default ?? undefined;
|
|
1371
1573
|
continue;
|
|
1372
1574
|
}
|
|
1575
|
+
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${def.name}`, this.#getCommandPath());
|
|
1373
1576
|
}
|
|
1374
1577
|
else {
|
|
1375
1578
|
args[def.name] = this.#convertArgument(def, raw);
|
|
1376
1579
|
index += 1;
|
|
1377
1580
|
}
|
|
1378
1581
|
}
|
|
1379
|
-
const
|
|
1380
|
-
if (!
|
|
1582
|
+
const hasRestArgument = argumentDefs.some(a => a.kind === 'variadic' || a.kind === 'some');
|
|
1583
|
+
if (!hasRestArgument && index < rawArgs.length) {
|
|
1381
1584
|
throw new CommanderError('TooManyArguments', `too many arguments: expected ${argumentDefs.length}, got ${rawArgs.length}`, this.#getCommandPath());
|
|
1382
1585
|
}
|
|
1383
1586
|
return { args, rawArgs };
|
|
1384
1587
|
}
|
|
1385
1588
|
#convertArgument(def, raw) {
|
|
1589
|
+
let value;
|
|
1386
1590
|
if (def.coerce) {
|
|
1387
1591
|
try {
|
|
1388
|
-
|
|
1592
|
+
value = def.coerce(raw);
|
|
1389
1593
|
}
|
|
1390
1594
|
catch {
|
|
1391
1595
|
throw new CommanderError('InvalidType', `invalid value "${raw}" for argument "${def.name}"`, this.#getCommandPath());
|
|
1392
1596
|
}
|
|
1393
1597
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1598
|
+
else {
|
|
1599
|
+
value = raw;
|
|
1600
|
+
}
|
|
1601
|
+
if (typeof value !== 'string') {
|
|
1602
|
+
throw new CommanderError('InvalidType', `invalid value for argument "${def.name}": expected ${def.type}`, this.#getCommandPath());
|
|
1603
|
+
}
|
|
1604
|
+
if (def.type === 'choice') {
|
|
1605
|
+
const choices = def.choices ?? [];
|
|
1606
|
+
if (!choices.includes(value)) {
|
|
1607
|
+
throw new CommanderError('InvalidChoice', `invalid value "${value}" for argument "${def.name}". Allowed: ${choices
|
|
1608
|
+
.map(choice => JSON.stringify(choice))
|
|
1609
|
+
.join(', ')}`, this.#getCommandPath());
|
|
1398
1610
|
}
|
|
1399
|
-
return n;
|
|
1400
1611
|
}
|
|
1401
|
-
return
|
|
1612
|
+
return value;
|
|
1402
1613
|
}
|
|
1403
1614
|
#hasUserOption(long) {
|
|
1404
1615
|
return this.#options.some(option => option.long === long);
|
|
1405
1616
|
}
|
|
1406
1617
|
#supportsBuiltinVersion() {
|
|
1407
|
-
return this.#
|
|
1618
|
+
return this.#version !== undefined && this.#builtin.option.version;
|
|
1408
1619
|
}
|
|
1409
1620
|
#resolveOptionPolicy() {
|
|
1410
1621
|
const optionMap = new Map();
|
|
@@ -1484,6 +1695,9 @@ class Command {
|
|
|
1484
1695
|
if (!/^[a-z][a-zA-Z0-9]*$/.test(opt.long)) {
|
|
1485
1696
|
throw new CommanderError('ConfigurationError', `option long name must be camelCase: "${opt.long}"`, this.#getCommandPath());
|
|
1486
1697
|
}
|
|
1698
|
+
if (opt.short !== undefined && opt.short.length !== 1) {
|
|
1699
|
+
throw new CommanderError('ConfigurationError', `option short name must be a single character: "${opt.short}"`, this.#getCommandPath());
|
|
1700
|
+
}
|
|
1487
1701
|
if (opt.required && opt.default !== undefined) {
|
|
1488
1702
|
throw new CommanderError('ConfigurationError', `option "--${opt.long}" cannot be both required and have a default value`, this.#getCommandPath());
|
|
1489
1703
|
}
|
|
@@ -1500,24 +1714,58 @@ class Command {
|
|
|
1500
1714
|
}
|
|
1501
1715
|
}
|
|
1502
1716
|
#validateArgumentConfig(arg) {
|
|
1503
|
-
if (arg.kind
|
|
1504
|
-
|
|
1717
|
+
if (arg.kind !== 'required' &&
|
|
1718
|
+
arg.kind !== 'optional' &&
|
|
1719
|
+
arg.kind !== 'variadic' &&
|
|
1720
|
+
arg.kind !== 'some') {
|
|
1721
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" must specify a valid kind`, this.#getCommandPath());
|
|
1722
|
+
}
|
|
1723
|
+
if (arg.type !== 'string' && arg.type !== 'choice') {
|
|
1724
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" must specify a valid type`, this.#getCommandPath());
|
|
1505
1725
|
}
|
|
1506
|
-
if (arg.kind
|
|
1507
|
-
|
|
1508
|
-
|
|
1726
|
+
if (arg.default !== undefined && arg.kind !== 'optional') {
|
|
1727
|
+
throw new CommanderError('ConfigurationError', `only optional argument "${arg.name}" can have a default value`, this.#getCommandPath());
|
|
1728
|
+
}
|
|
1729
|
+
if (arg.type === 'string' && arg.choices !== undefined) {
|
|
1730
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" of type "string" cannot declare choices`, this.#getCommandPath());
|
|
1731
|
+
}
|
|
1732
|
+
if (arg.type === 'choice') {
|
|
1733
|
+
if (!Array.isArray(arg.choices) || arg.choices.length === 0) {
|
|
1734
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" of type "choice" must declare a non-empty choices array`, this.#getCommandPath());
|
|
1735
|
+
}
|
|
1736
|
+
if (arg.choices.some(choice => typeof choice !== 'string')) {
|
|
1737
|
+
throw new CommanderError('ConfigurationError', `argument "${arg.name}" choices must be string[]`, this.#getCommandPath());
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
if (arg.default !== undefined) {
|
|
1741
|
+
this.#validateArgumentDefaultValue(arg);
|
|
1742
|
+
}
|
|
1743
|
+
if (arg.kind === 'variadic' || arg.kind === 'some') {
|
|
1744
|
+
if (this.#arguments.some(a => a.kind === 'variadic' || a.kind === 'some')) {
|
|
1745
|
+
throw new CommanderError('ConfigurationError', 'only one variadic/some argument is allowed', this.#getCommandPath());
|
|
1509
1746
|
}
|
|
1510
1747
|
}
|
|
1511
1748
|
if (this.#arguments.length > 0) {
|
|
1512
1749
|
const last = this.#arguments[this.#arguments.length - 1];
|
|
1513
|
-
if (last.kind === 'variadic') {
|
|
1514
|
-
throw new CommanderError('ConfigurationError', 'variadic argument must be the last argument', this.#getCommandPath());
|
|
1750
|
+
if (last.kind === 'variadic' || last.kind === 'some') {
|
|
1751
|
+
throw new CommanderError('ConfigurationError', 'variadic/some argument must be the last argument', this.#getCommandPath());
|
|
1515
1752
|
}
|
|
1516
1753
|
}
|
|
1517
1754
|
if (arg.kind === 'required') {
|
|
1518
|
-
const hasOptional = this.#arguments.some(a => a.kind === 'optional' || a.kind === 'variadic');
|
|
1755
|
+
const hasOptional = this.#arguments.some(a => a.kind === 'optional' || a.kind === 'variadic' || a.kind === 'some');
|
|
1519
1756
|
if (hasOptional) {
|
|
1520
|
-
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot come after optional/variadic arguments`, this.#getCommandPath());
|
|
1757
|
+
throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot come after optional/variadic/some arguments`, this.#getCommandPath());
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
#validateArgumentDefaultValue(arg) {
|
|
1762
|
+
if (typeof arg.default !== 'string') {
|
|
1763
|
+
throw new CommanderError('ConfigurationError', `default value for argument "${arg.name}" must match type "${arg.type}"`, this.#getCommandPath());
|
|
1764
|
+
}
|
|
1765
|
+
if (arg.type === 'choice') {
|
|
1766
|
+
const choices = arg.choices ?? [];
|
|
1767
|
+
if (!choices.includes(arg.default)) {
|
|
1768
|
+
throw new CommanderError('ConfigurationError', `default value for argument "${arg.name}" must be one of declared choices`, this.#getCommandPath());
|
|
1521
1769
|
}
|
|
1522
1770
|
}
|
|
1523
1771
|
}
|