@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.
@@ -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
- const padding = ' '.repeat(maxSigLen - sig.length + 2);
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
- const padding = ' '.repeat(maxNameLen - name.length + 2);
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
- const padding = ' '.repeat(maxSigLen - sig.length + 2);
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
- const padding = ' '.repeat(maxNameLen - name.length + 2);
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 = Number(rawValue);
1339
- if (Number.isNaN(num)) {
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 requiredCount = argumentDefs.filter(a => a.kind === 'required').length;
1350
- if (rawArgs.length < requiredCount) {
1351
- const missing = argumentDefs
1352
- .filter(a => a.kind === 'required')
1353
- .slice(rawArgs.length)
1354
- .map(a => a.name);
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 hasVariadic = argumentDefs.some(a => a.kind === 'variadic');
1378
- if (!hasVariadic && index < rawArgs.length) {
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
- return def.coerce(raw);
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
- if (def.type === 'number') {
1393
- const n = Number(raw);
1394
- if (Number.isNaN(n)) {
1395
- throw new CommanderError('InvalidType', `invalid number "${raw}" for argument "${def.name}"`, this.#getCommandPath());
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 raw;
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.#parent === undefined && this.#version !== undefined && this.#builtin.option.version;
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 === 'required' && arg.default !== undefined) {
1502
- throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot have a default value`, this.#getCommandPath());
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 === 'variadic') {
1505
- if (this.#arguments.some(a => a.kind === 'variadic')) {
1506
- throw new CommanderError('ConfigurationError', 'only one variadic argument is allowed', this.#getCommandPath());
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
  }