@guanghechen/commander 4.7.1 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## 4.7.1
4
11
 
5
12
  ### Patch Changes
@@ -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
- const padding = ' '.repeat(maxSigLen - sig.length + 2);
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
- const padding = ' '.repeat(maxNameLen - name.length + 2);
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
- const padding = ' '.repeat(maxSigLen - sig.length + 2);
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
- const padding = ' '.repeat(maxNameLen - name.length + 2);
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 = Number(rawValue);
1341
- if (Number.isNaN(num)) {
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 requiredCount = argumentDefs.filter(a => a.kind === 'required').length;
1352
- if (rawArgs.length < requiredCount) {
1353
- const missing = argumentDefs
1354
- .filter(a => a.kind === 'required')
1355
- .slice(rawArgs.length)
1356
- .map(a => a.name);
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,41 +1557,59 @@ 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 hasVariadic = argumentDefs.some(a => a.kind === 'variadic');
1380
- if (!hasVariadic && index < rawArgs.length) {
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
- return def.coerce(raw);
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
- if (def.type === 'number') {
1395
- const n = Number(raw);
1396
- if (Number.isNaN(n)) {
1397
- throw new CommanderError('InvalidType', `invalid number "${raw}" for argument "${def.name}"`, this.#getCommandPath());
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 raw;
1612
+ return value;
1402
1613
  }
1403
1614
  #hasUserOption(long) {
1404
1615
  return this.#options.some(option => option.long === long);
@@ -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 === 'required' && arg.default !== undefined) {
1504
- throw new CommanderError('ConfigurationError', `required argument "${arg.name}" cannot have a default value`, this.#getCommandPath());
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 === 'variadic') {
1507
- if (this.#arguments.some(a => a.kind === 'variadic')) {
1508
- throw new CommanderError('ConfigurationError', 'only one variadic argument is allowed', this.#getCommandPath());
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
  }