@guanghechen/commander 4.7.2 → 4.7.3
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 +7 -0
- package/lib/cjs/browser.cjs +125 -17
- package/lib/cjs/index.cjs +176 -1028
- package/lib/cjs/node.cjs +177 -39
- package/lib/esm/browser.mjs +125 -17
- package/lib/esm/index.mjs +175 -1021
- package/lib/esm/node.mjs +177 -39
- package/lib/types/browser.d.ts +2 -1
- package/lib/types/index.d.ts +15 -191
- package/lib/types/node.d.ts +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/lib/cjs/browser.cjs
CHANGED
|
@@ -294,6 +294,30 @@ function parsePrimitiveNumber(rawValue) {
|
|
|
294
294
|
}
|
|
295
295
|
return value;
|
|
296
296
|
}
|
|
297
|
+
function normalizeSubcommandNameForDistance(name) {
|
|
298
|
+
return camelToKebabCase(name).toLowerCase();
|
|
299
|
+
}
|
|
300
|
+
function levenshteinDistance(left, right) {
|
|
301
|
+
if (left === right) {
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
if (left.length === 0) {
|
|
305
|
+
return right.length;
|
|
306
|
+
}
|
|
307
|
+
if (right.length === 0) {
|
|
308
|
+
return left.length;
|
|
309
|
+
}
|
|
310
|
+
let prev = Array.from({ length: right.length + 1 }, (_, i) => i);
|
|
311
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
312
|
+
const current = [i + 1];
|
|
313
|
+
for (let j = 0; j < right.length; j += 1) {
|
|
314
|
+
const substitutionCost = left[i] === right[j] ? 0 : 1;
|
|
315
|
+
current[j + 1] = Math.min(current[j] + 1, prev[j + 1] + 1, prev[j] + substitutionCost);
|
|
316
|
+
}
|
|
317
|
+
prev = current;
|
|
318
|
+
}
|
|
319
|
+
return prev[right.length];
|
|
320
|
+
}
|
|
297
321
|
function tokenizeLongOption(arg, commandPath) {
|
|
298
322
|
const eqIdx = arg.indexOf('=');
|
|
299
323
|
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
@@ -696,7 +720,10 @@ class Command {
|
|
|
696
720
|
const kebabLong = camelToKebabCase(opt.long);
|
|
697
721
|
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
698
722
|
sig += `--${kebabLong}`;
|
|
699
|
-
if (opt.args
|
|
723
|
+
if (opt.args === 'optional') {
|
|
724
|
+
sig += ' [value]';
|
|
725
|
+
}
|
|
726
|
+
else if (opt.args !== 'none') {
|
|
700
727
|
sig += ' <value>';
|
|
701
728
|
}
|
|
702
729
|
let desc = opt.desc;
|
|
@@ -1315,6 +1342,14 @@ class Command {
|
|
|
1315
1342
|
consumed.push(tokens[i]);
|
|
1316
1343
|
}
|
|
1317
1344
|
}
|
|
1345
|
+
else if (opt.args === 'optional') {
|
|
1346
|
+
if (!token.resolved.includes('=') &&
|
|
1347
|
+
i + 1 < tokens.length &&
|
|
1348
|
+
tokens[i + 1].type === 'none') {
|
|
1349
|
+
i += 1;
|
|
1350
|
+
consumed.push(tokens[i]);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1318
1353
|
else if (opt.args === 'variadic') {
|
|
1319
1354
|
if (!token.resolved.includes('=')) {
|
|
1320
1355
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
@@ -1340,6 +1375,12 @@ class Command {
|
|
|
1340
1375
|
consumed.push(tokens[i]);
|
|
1341
1376
|
}
|
|
1342
1377
|
}
|
|
1378
|
+
else if (opt.args === 'optional') {
|
|
1379
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1380
|
+
i += 1;
|
|
1381
|
+
consumed.push(tokens[i]);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1343
1384
|
else if (opt.args === 'variadic') {
|
|
1344
1385
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1345
1386
|
i += 1;
|
|
@@ -1381,6 +1422,7 @@ class Command {
|
|
|
1381
1422
|
leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
|
|
1382
1423
|
}
|
|
1383
1424
|
}
|
|
1425
|
+
leafCommand.#assertUnknownSubcommand(ctx.sources.user.argv);
|
|
1384
1426
|
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
1385
1427
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
1386
1428
|
const parseCtx = {
|
|
@@ -1463,6 +1505,23 @@ class Command {
|
|
|
1463
1505
|
i += 1;
|
|
1464
1506
|
continue;
|
|
1465
1507
|
}
|
|
1508
|
+
if (opt.args === 'optional') {
|
|
1509
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1510
|
+
if (eqIdx !== -1) {
|
|
1511
|
+
opts[opt.long] = this.#convertValue(opt, token.resolved.slice(eqIdx + 1));
|
|
1512
|
+
i += 1;
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1516
|
+
opts[opt.long] = this.#convertValue(opt, tokens[i + 1].original);
|
|
1517
|
+
i += 1;
|
|
1518
|
+
}
|
|
1519
|
+
else {
|
|
1520
|
+
opts[opt.long] = undefined;
|
|
1521
|
+
}
|
|
1522
|
+
i += 1;
|
|
1523
|
+
continue;
|
|
1524
|
+
}
|
|
1466
1525
|
if (opt.args === 'variadic') {
|
|
1467
1526
|
const values = Array.isArray(opts[opt.long]) ? opts[opt.long] : [];
|
|
1468
1527
|
const eqIdx = token.resolved.indexOf('=');
|
|
@@ -1482,7 +1541,7 @@ class Command {
|
|
|
1482
1541
|
i += 1;
|
|
1483
1542
|
}
|
|
1484
1543
|
for (const opt of allOptions) {
|
|
1485
|
-
if (opt.required && opts
|
|
1544
|
+
if (opt.required && !Object.prototype.hasOwnProperty.call(opts, opt.long)) {
|
|
1486
1545
|
throw new CommanderError('MissingRequired', `missing required option "--${camelToKebabCase(opt.long)}"`, this.#getCommandPath());
|
|
1487
1546
|
}
|
|
1488
1547
|
}
|
|
@@ -1519,6 +1578,9 @@ class Command {
|
|
|
1519
1578
|
#parseArguments(rawArgs) {
|
|
1520
1579
|
const argumentDefs = this.#arguments;
|
|
1521
1580
|
const args = {};
|
|
1581
|
+
if (argumentDefs.length === 0 && rawArgs.length > 0) {
|
|
1582
|
+
throw new CommanderError('UnexpectedArgument', `unexpected argument "${rawArgs[0]}"`, this.#getCommandPath());
|
|
1583
|
+
}
|
|
1522
1584
|
const missing = [];
|
|
1523
1585
|
let remaining = rawArgs.length;
|
|
1524
1586
|
for (const def of argumentDefs) {
|
|
@@ -1559,25 +1621,23 @@ class Command {
|
|
|
1559
1621
|
}
|
|
1560
1622
|
if (def.kind === 'some') {
|
|
1561
1623
|
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
1624
|
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1566
1625
|
index = rawArgs.length;
|
|
1567
1626
|
break;
|
|
1568
1627
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
if (
|
|
1628
|
+
if (def.kind === 'optional') {
|
|
1629
|
+
const raw = rawArgs[index];
|
|
1630
|
+
if (raw === undefined) {
|
|
1572
1631
|
args[def.name] = def.default ?? undefined;
|
|
1573
1632
|
continue;
|
|
1574
1633
|
}
|
|
1575
|
-
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${def.name}`, this.#getCommandPath());
|
|
1576
|
-
}
|
|
1577
|
-
else {
|
|
1578
1634
|
args[def.name] = this.#convertArgument(def, raw);
|
|
1579
1635
|
index += 1;
|
|
1636
|
+
continue;
|
|
1580
1637
|
}
|
|
1638
|
+
const raw = rawArgs[index];
|
|
1639
|
+
args[def.name] = this.#convertArgument(def, raw);
|
|
1640
|
+
index += 1;
|
|
1581
1641
|
}
|
|
1582
1642
|
const hasRestArgument = argumentDefs.some(a => a.kind === 'variadic' || a.kind === 'some');
|
|
1583
1643
|
if (!hasRestArgument && index < rawArgs.length) {
|
|
@@ -1611,6 +1671,50 @@ class Command {
|
|
|
1611
1671
|
}
|
|
1612
1672
|
return value;
|
|
1613
1673
|
}
|
|
1674
|
+
#assertUnknownSubcommand(userTailArgv) {
|
|
1675
|
+
if (this.#subcommandsList.length === 0) {
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const token = userTailArgv[0];
|
|
1679
|
+
if (token === undefined || token.startsWith('-') || token === 'help') {
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (this.#findSubcommandEntry(token) !== undefined) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const hints = [];
|
|
1686
|
+
if (this.#arguments.length === 0) {
|
|
1687
|
+
hints.push(`Hint: command "${this.#getCommandPath()}" does not accept positional arguments.`);
|
|
1688
|
+
}
|
|
1689
|
+
const candidate = this.#resolveDidYouMeanSubcommandName(token);
|
|
1690
|
+
if (candidate !== undefined) {
|
|
1691
|
+
hints.push(`Hint: did you mean "${candidate}"?`);
|
|
1692
|
+
}
|
|
1693
|
+
const details = hints.length > 0 ? `\n${hints.join('\n')}` : '';
|
|
1694
|
+
throw new CommanderError('UnknownSubcommand', `unknown subcommand "${token}" for command "${this.#getCommandPath()}"${details}`, this.#getCommandPath());
|
|
1695
|
+
}
|
|
1696
|
+
#resolveDidYouMeanSubcommandName(token) {
|
|
1697
|
+
const source = normalizeSubcommandNameForDistance(token);
|
|
1698
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
1699
|
+
let bestName;
|
|
1700
|
+
let isUniqueBest = false;
|
|
1701
|
+
for (const entry of this.#subcommandsList) {
|
|
1702
|
+
const target = normalizeSubcommandNameForDistance(entry.name);
|
|
1703
|
+
const distance = levenshteinDistance(source, target);
|
|
1704
|
+
if (distance < minDistance) {
|
|
1705
|
+
minDistance = distance;
|
|
1706
|
+
bestName = entry.name;
|
|
1707
|
+
isUniqueBest = true;
|
|
1708
|
+
}
|
|
1709
|
+
else if (distance === minDistance) {
|
|
1710
|
+
isUniqueBest = false;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
if (minDistance <= 2 && isUniqueBest) {
|
|
1714
|
+
return bestName;
|
|
1715
|
+
}
|
|
1716
|
+
return undefined;
|
|
1717
|
+
}
|
|
1614
1718
|
#hasUserOption(long) {
|
|
1615
1719
|
return this.#options.some(option => option.long === long);
|
|
1616
1720
|
}
|
|
@@ -1654,11 +1758,9 @@ class Command {
|
|
|
1654
1758
|
return optionPolicyMap;
|
|
1655
1759
|
}
|
|
1656
1760
|
#mustGetOptionPolicy(optionPolicyMap, cmd) {
|
|
1657
|
-
const policy = optionPolicyMap.get(cmd);
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
}
|
|
1661
|
-
throw new CommanderError('ConfigurationError', `missing option policy for command "${cmd.#getCommandPath()}"`, this.#getCommandPath());
|
|
1761
|
+
const policy = optionPolicyMap.get(cmd) ?? cmd.#resolveOptionPolicy();
|
|
1762
|
+
optionPolicyMap.set(cmd, policy);
|
|
1763
|
+
return policy;
|
|
1662
1764
|
}
|
|
1663
1765
|
#validateMergedShortOptions(chain, optionPolicyMap) {
|
|
1664
1766
|
const mergedByLong = new Map();
|
|
@@ -1687,7 +1789,10 @@ class Command {
|
|
|
1687
1789
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
1688
1790
|
}
|
|
1689
1791
|
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
1690
|
-
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required' or 'variadic'`, this.#getCommandPath());
|
|
1792
|
+
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required', 'optional', or 'variadic'`, this.#getCommandPath());
|
|
1793
|
+
}
|
|
1794
|
+
if (opt.type === 'number' && opt.args === 'optional') {
|
|
1795
|
+
throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, this.#getCommandPath());
|
|
1691
1796
|
}
|
|
1692
1797
|
if (opt.long.startsWith('no')) {
|
|
1693
1798
|
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, this.#getCommandPath());
|
|
@@ -1704,6 +1809,9 @@ class Command {
|
|
|
1704
1809
|
if (opt.type === 'boolean' && opt.required) {
|
|
1705
1810
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
1706
1811
|
}
|
|
1812
|
+
if (opt.required && opt.args !== 'required') {
|
|
1813
|
+
throw new CommanderError('ConfigurationError', `required option "--${opt.long}" must use args: 'required'`, this.#getCommandPath());
|
|
1814
|
+
}
|
|
1707
1815
|
}
|
|
1708
1816
|
#checkOptionUniqueness(opt) {
|
|
1709
1817
|
if (this.#options.some(o => o.long === opt.long)) {
|