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