@guanghechen/commander 4.7.2 → 4.7.4
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 +16 -0
- package/lib/cjs/browser.cjs +136 -19
- package/lib/cjs/index.cjs +176 -1028
- package/lib/cjs/node.cjs +205 -51
- package/lib/esm/browser.mjs +136 -19
- package/lib/esm/index.mjs +175 -1021
- package/lib/esm/node.mjs +205 -51
- package/lib/types/browser.d.ts +6 -3
- package/lib/types/index.d.ts +15 -191
- package/lib/types/node.d.ts +6 -3
- package/package.json +3 -3
package/lib/esm/browser.mjs
CHANGED
|
@@ -292,6 +292,30 @@ function parsePrimitiveNumber(rawValue) {
|
|
|
292
292
|
}
|
|
293
293
|
return value;
|
|
294
294
|
}
|
|
295
|
+
function normalizeSubcommandNameForDistance(name) {
|
|
296
|
+
return camelToKebabCase(name).toLowerCase();
|
|
297
|
+
}
|
|
298
|
+
function levenshteinDistance(left, right) {
|
|
299
|
+
if (left === right) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
if (left.length === 0) {
|
|
303
|
+
return right.length;
|
|
304
|
+
}
|
|
305
|
+
if (right.length === 0) {
|
|
306
|
+
return left.length;
|
|
307
|
+
}
|
|
308
|
+
let prev = Array.from({ length: right.length + 1 }, (_, i) => i);
|
|
309
|
+
for (let i = 0; i < left.length; i += 1) {
|
|
310
|
+
const current = [i + 1];
|
|
311
|
+
for (let j = 0; j < right.length; j += 1) {
|
|
312
|
+
const substitutionCost = left[i] === right[j] ? 0 : 1;
|
|
313
|
+
current[j + 1] = Math.min(current[j] + 1, prev[j + 1] + 1, prev[j] + substitutionCost);
|
|
314
|
+
}
|
|
315
|
+
prev = current;
|
|
316
|
+
}
|
|
317
|
+
return prev[right.length];
|
|
318
|
+
}
|
|
295
319
|
function tokenizeLongOption(arg, commandPath) {
|
|
296
320
|
const eqIdx = arg.indexOf('=');
|
|
297
321
|
const namePart = eqIdx !== -1 ? arg.slice(0, eqIdx) : arg;
|
|
@@ -694,7 +718,10 @@ class Command {
|
|
|
694
718
|
const kebabLong = camelToKebabCase(opt.long);
|
|
695
719
|
let sig = opt.short ? `-${opt.short}, ` : ' ';
|
|
696
720
|
sig += `--${kebabLong}`;
|
|
697
|
-
if (opt.args
|
|
721
|
+
if (opt.args === 'optional') {
|
|
722
|
+
sig += ' [value]';
|
|
723
|
+
}
|
|
724
|
+
else if (opt.args !== 'none') {
|
|
698
725
|
sig += ' <value>';
|
|
699
726
|
}
|
|
700
727
|
let desc = opt.desc;
|
|
@@ -835,7 +862,15 @@ class Command {
|
|
|
835
862
|
return ` ${outputLabel} ${desc}`;
|
|
836
863
|
}
|
|
837
864
|
getCompletionMeta() {
|
|
838
|
-
const
|
|
865
|
+
const optionMap = new Map();
|
|
866
|
+
for (const option of this.#resolveOptionPolicy().mergedOptions) {
|
|
867
|
+
optionMap.set(option.long, option);
|
|
868
|
+
}
|
|
869
|
+
optionMap.set('help', BUILTIN_HELP_OPTION);
|
|
870
|
+
if (this.#supportsBuiltinVersion()) {
|
|
871
|
+
optionMap.set('version', BUILTIN_VERSION_OPTION);
|
|
872
|
+
}
|
|
873
|
+
const allOptions = Array.from(optionMap.values());
|
|
839
874
|
const options = [];
|
|
840
875
|
const argumentsMeta = [];
|
|
841
876
|
for (const opt of allOptions) {
|
|
@@ -843,7 +878,8 @@ class Command {
|
|
|
843
878
|
long: opt.long,
|
|
844
879
|
short: opt.short,
|
|
845
880
|
desc: opt.desc,
|
|
846
|
-
|
|
881
|
+
type: opt.type,
|
|
882
|
+
args: opt.args,
|
|
847
883
|
choices: opt.choices?.map(choice => String(choice)),
|
|
848
884
|
});
|
|
849
885
|
}
|
|
@@ -1313,6 +1349,14 @@ class Command {
|
|
|
1313
1349
|
consumed.push(tokens[i]);
|
|
1314
1350
|
}
|
|
1315
1351
|
}
|
|
1352
|
+
else if (opt.args === 'optional') {
|
|
1353
|
+
if (!token.resolved.includes('=') &&
|
|
1354
|
+
i + 1 < tokens.length &&
|
|
1355
|
+
tokens[i + 1].type === 'none') {
|
|
1356
|
+
i += 1;
|
|
1357
|
+
consumed.push(tokens[i]);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1316
1360
|
else if (opt.args === 'variadic') {
|
|
1317
1361
|
if (!token.resolved.includes('=')) {
|
|
1318
1362
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
@@ -1338,6 +1382,12 @@ class Command {
|
|
|
1338
1382
|
consumed.push(tokens[i]);
|
|
1339
1383
|
}
|
|
1340
1384
|
}
|
|
1385
|
+
else if (opt.args === 'optional') {
|
|
1386
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1387
|
+
i += 1;
|
|
1388
|
+
consumed.push(tokens[i]);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1341
1391
|
else if (opt.args === 'variadic') {
|
|
1342
1392
|
while (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1343
1393
|
i += 1;
|
|
@@ -1379,6 +1429,7 @@ class Command {
|
|
|
1379
1429
|
leafLocalOpts[opt.long] = leafParsedOpts[opt.long];
|
|
1380
1430
|
}
|
|
1381
1431
|
}
|
|
1432
|
+
leafCommand.#assertUnknownSubcommand(ctx.sources.user.argv);
|
|
1382
1433
|
const rawArgStrings = [...argTokens.map(t => t.original), ...restArgs];
|
|
1383
1434
|
const { args, rawArgs } = leafCommand.#parseArguments(rawArgStrings);
|
|
1384
1435
|
const parseCtx = {
|
|
@@ -1461,6 +1512,23 @@ class Command {
|
|
|
1461
1512
|
i += 1;
|
|
1462
1513
|
continue;
|
|
1463
1514
|
}
|
|
1515
|
+
if (opt.args === 'optional') {
|
|
1516
|
+
const eqIdx = token.resolved.indexOf('=');
|
|
1517
|
+
if (eqIdx !== -1) {
|
|
1518
|
+
opts[opt.long] = this.#convertValue(opt, token.resolved.slice(eqIdx + 1));
|
|
1519
|
+
i += 1;
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
if (i + 1 < tokens.length && tokens[i + 1].type === 'none') {
|
|
1523
|
+
opts[opt.long] = this.#convertValue(opt, tokens[i + 1].original);
|
|
1524
|
+
i += 1;
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
opts[opt.long] = undefined;
|
|
1528
|
+
}
|
|
1529
|
+
i += 1;
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1464
1532
|
if (opt.args === 'variadic') {
|
|
1465
1533
|
const values = Array.isArray(opts[opt.long]) ? opts[opt.long] : [];
|
|
1466
1534
|
const eqIdx = token.resolved.indexOf('=');
|
|
@@ -1480,7 +1548,7 @@ class Command {
|
|
|
1480
1548
|
i += 1;
|
|
1481
1549
|
}
|
|
1482
1550
|
for (const opt of allOptions) {
|
|
1483
|
-
if (opt.required && opts
|
|
1551
|
+
if (opt.required && !Object.prototype.hasOwnProperty.call(opts, opt.long)) {
|
|
1484
1552
|
throw new CommanderError('MissingRequired', `missing required option "--${camelToKebabCase(opt.long)}"`, this.#getCommandPath());
|
|
1485
1553
|
}
|
|
1486
1554
|
}
|
|
@@ -1517,6 +1585,9 @@ class Command {
|
|
|
1517
1585
|
#parseArguments(rawArgs) {
|
|
1518
1586
|
const argumentDefs = this.#arguments;
|
|
1519
1587
|
const args = {};
|
|
1588
|
+
if (argumentDefs.length === 0 && rawArgs.length > 0) {
|
|
1589
|
+
throw new CommanderError('UnexpectedArgument', `unexpected argument "${rawArgs[0]}"`, this.#getCommandPath());
|
|
1590
|
+
}
|
|
1520
1591
|
const missing = [];
|
|
1521
1592
|
let remaining = rawArgs.length;
|
|
1522
1593
|
for (const def of argumentDefs) {
|
|
@@ -1557,25 +1628,23 @@ class Command {
|
|
|
1557
1628
|
}
|
|
1558
1629
|
if (def.kind === 'some') {
|
|
1559
1630
|
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
1631
|
args[def.name] = rest.map(raw => this.#convertArgument(def, raw));
|
|
1564
1632
|
index = rawArgs.length;
|
|
1565
1633
|
break;
|
|
1566
1634
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
if (
|
|
1635
|
+
if (def.kind === 'optional') {
|
|
1636
|
+
const raw = rawArgs[index];
|
|
1637
|
+
if (raw === undefined) {
|
|
1570
1638
|
args[def.name] = def.default ?? undefined;
|
|
1571
1639
|
continue;
|
|
1572
1640
|
}
|
|
1573
|
-
throw new CommanderError('MissingRequiredArgument', `missing required argument(s): ${def.name}`, this.#getCommandPath());
|
|
1574
|
-
}
|
|
1575
|
-
else {
|
|
1576
1641
|
args[def.name] = this.#convertArgument(def, raw);
|
|
1577
1642
|
index += 1;
|
|
1643
|
+
continue;
|
|
1578
1644
|
}
|
|
1645
|
+
const raw = rawArgs[index];
|
|
1646
|
+
args[def.name] = this.#convertArgument(def, raw);
|
|
1647
|
+
index += 1;
|
|
1579
1648
|
}
|
|
1580
1649
|
const hasRestArgument = argumentDefs.some(a => a.kind === 'variadic' || a.kind === 'some');
|
|
1581
1650
|
if (!hasRestArgument && index < rawArgs.length) {
|
|
@@ -1609,6 +1678,50 @@ class Command {
|
|
|
1609
1678
|
}
|
|
1610
1679
|
return value;
|
|
1611
1680
|
}
|
|
1681
|
+
#assertUnknownSubcommand(userTailArgv) {
|
|
1682
|
+
if (this.#subcommandsList.length === 0) {
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
const token = userTailArgv[0];
|
|
1686
|
+
if (token === undefined || token.startsWith('-') || token === 'help') {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (this.#findSubcommandEntry(token) !== undefined) {
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
const hints = [];
|
|
1693
|
+
if (this.#arguments.length === 0) {
|
|
1694
|
+
hints.push(`Hint: command "${this.#getCommandPath()}" does not accept positional arguments.`);
|
|
1695
|
+
}
|
|
1696
|
+
const candidate = this.#resolveDidYouMeanSubcommandName(token);
|
|
1697
|
+
if (candidate !== undefined) {
|
|
1698
|
+
hints.push(`Hint: did you mean "${candidate}"?`);
|
|
1699
|
+
}
|
|
1700
|
+
const details = hints.length > 0 ? `\n${hints.join('\n')}` : '';
|
|
1701
|
+
throw new CommanderError('UnknownSubcommand', `unknown subcommand "${token}" for command "${this.#getCommandPath()}"${details}`, this.#getCommandPath());
|
|
1702
|
+
}
|
|
1703
|
+
#resolveDidYouMeanSubcommandName(token) {
|
|
1704
|
+
const source = normalizeSubcommandNameForDistance(token);
|
|
1705
|
+
let minDistance = Number.POSITIVE_INFINITY;
|
|
1706
|
+
let bestName;
|
|
1707
|
+
let isUniqueBest = false;
|
|
1708
|
+
for (const entry of this.#subcommandsList) {
|
|
1709
|
+
const target = normalizeSubcommandNameForDistance(entry.name);
|
|
1710
|
+
const distance = levenshteinDistance(source, target);
|
|
1711
|
+
if (distance < minDistance) {
|
|
1712
|
+
minDistance = distance;
|
|
1713
|
+
bestName = entry.name;
|
|
1714
|
+
isUniqueBest = true;
|
|
1715
|
+
}
|
|
1716
|
+
else if (distance === minDistance) {
|
|
1717
|
+
isUniqueBest = false;
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
if (minDistance <= 2 && isUniqueBest) {
|
|
1721
|
+
return bestName;
|
|
1722
|
+
}
|
|
1723
|
+
return undefined;
|
|
1724
|
+
}
|
|
1612
1725
|
#hasUserOption(long) {
|
|
1613
1726
|
return this.#options.some(option => option.long === long);
|
|
1614
1727
|
}
|
|
@@ -1652,11 +1765,9 @@ class Command {
|
|
|
1652
1765
|
return optionPolicyMap;
|
|
1653
1766
|
}
|
|
1654
1767
|
#mustGetOptionPolicy(optionPolicyMap, cmd) {
|
|
1655
|
-
const policy = optionPolicyMap.get(cmd);
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
}
|
|
1659
|
-
throw new CommanderError('ConfigurationError', `missing option policy for command "${cmd.#getCommandPath()}"`, this.#getCommandPath());
|
|
1768
|
+
const policy = optionPolicyMap.get(cmd) ?? cmd.#resolveOptionPolicy();
|
|
1769
|
+
optionPolicyMap.set(cmd, policy);
|
|
1770
|
+
return policy;
|
|
1660
1771
|
}
|
|
1661
1772
|
#validateMergedShortOptions(chain, optionPolicyMap) {
|
|
1662
1773
|
const mergedByLong = new Map();
|
|
@@ -1685,7 +1796,10 @@ class Command {
|
|
|
1685
1796
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" must have args: 'none'`, this.#getCommandPath());
|
|
1686
1797
|
}
|
|
1687
1798
|
if ((opt.type === 'string' || opt.type === 'number') && opt.args === 'none') {
|
|
1688
|
-
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required' or 'variadic'`, this.#getCommandPath());
|
|
1799
|
+
throw new CommanderError('ConfigurationError', `${opt.type} option "--${opt.long}" must have args: 'required', 'optional', or 'variadic'`, this.#getCommandPath());
|
|
1800
|
+
}
|
|
1801
|
+
if (opt.type === 'number' && opt.args === 'optional') {
|
|
1802
|
+
throw new CommanderError('ConfigurationError', `number option "--${opt.long}" does not support args: 'optional'`, this.#getCommandPath());
|
|
1689
1803
|
}
|
|
1690
1804
|
if (opt.long.startsWith('no')) {
|
|
1691
1805
|
throw new CommanderError('ConfigurationError', `option long name cannot start with "no": "${opt.long}"`, this.#getCommandPath());
|
|
@@ -1702,6 +1816,9 @@ class Command {
|
|
|
1702
1816
|
if (opt.type === 'boolean' && opt.required) {
|
|
1703
1817
|
throw new CommanderError('ConfigurationError', `boolean option "--${opt.long}" cannot be required`, this.#getCommandPath());
|
|
1704
1818
|
}
|
|
1819
|
+
if (opt.required && opt.args !== 'required') {
|
|
1820
|
+
throw new CommanderError('ConfigurationError', `required option "--${opt.long}" must use args: 'required'`, this.#getCommandPath());
|
|
1821
|
+
}
|
|
1705
1822
|
}
|
|
1706
1823
|
#checkOptionUniqueness(opt) {
|
|
1707
1824
|
if (this.#options.some(o => o.long === opt.long)) {
|