cli-kiss 0.2.6 → 0.2.7
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/README.md +62 -4
- package/dist/index.d.ts +29 -12
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/guide/02_commands.md +1 -1
- package/docs/guide/03_options.md +3 -3
- package/docs/guide/06_run_as_cli.md +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/lib/Command.ts +21 -7
- package/src/lib/Operation.ts +1 -1
- package/src/lib/Option.ts +28 -32
- package/src/lib/Positional.ts +2 -1
- package/src/lib/Reader.ts +31 -12
- package/src/lib/Run.ts +2 -2
- package/src/lib/Similarity.ts +41 -0
- package/src/lib/Type.ts +28 -29
- package/src/lib/Typo.ts +35 -13
- package/src/lib/Usage.ts +1 -1
- package/tests/unit.command.execute.ts +1 -1
- package/tests/unit.command.usage.ts +4 -4
- package/tests/unit.fuzzed.alternatives.ts +34 -0
- package/tests/unit.runner.colors.ts +9 -8
- package/tests/unit.runner.cycle.ts +103 -22
- package/tests/unit.runner.errors.ts +6 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { it } from "@jest/globals";
|
|
2
|
+
import { similaritySort } from "../src/lib/Similarity";
|
|
3
|
+
|
|
4
|
+
it("run", async function () {
|
|
5
|
+
expect(
|
|
6
|
+
orderBySimilarity("--inst", ["--flag", "--blah", "--install"]),
|
|
7
|
+
).toStrictEqual(["--install", "--flag", "--blah"]);
|
|
8
|
+
|
|
9
|
+
expect(
|
|
10
|
+
orderBySimilarity("instlal", ["install", "dudu", "--blah"]),
|
|
11
|
+
).toStrictEqual(["install", "--blah", "dudu"]);
|
|
12
|
+
|
|
13
|
+
expect(
|
|
14
|
+
orderBySimilarity("cat", ["cats", "catz", "cut", "kat", "hello", "world"]),
|
|
15
|
+
).toStrictEqual(["cats", "catz", "cut", "kat", "hello", "world"]);
|
|
16
|
+
|
|
17
|
+
expect(orderBySimilarity("cat", ["cut", "kat"])).toStrictEqual([
|
|
18
|
+
"cut",
|
|
19
|
+
"kat",
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
expect(orderBySimilarity("acb", ["abc", "ac", "ab"])).toStrictEqual([
|
|
23
|
+
"abc",
|
|
24
|
+
"ac",
|
|
25
|
+
"ab",
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function orderBySimilarity(reference: string, candidates: Array<string>) {
|
|
30
|
+
return similaritySort(
|
|
31
|
+
reference,
|
|
32
|
+
candidates.map((key) => ({ key, value: key })),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -32,9 +32,10 @@ const usageTty = usageToStyledLines({
|
|
|
32
32
|
typoSupport: TypoSupport.tty(),
|
|
33
33
|
}).join("\n");
|
|
34
34
|
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const unknownOptionNone =
|
|
36
|
+
'Error: Unknown option: "--color": did you mean: --help, --version ?';
|
|
37
|
+
const unknownOptionMock =
|
|
38
|
+
'{{Error:}@darkRed}+ Unknown option: {{"--color"}@darkYellow}+: did you mean: {{--help}@darkCyan}+, {{--version}@darkCyan}+ ?';
|
|
38
39
|
|
|
39
40
|
it("run", async function () {
|
|
40
41
|
await withEnv("FORCE_COLOR", "false", async () => {
|
|
@@ -82,21 +83,21 @@ it("run", async function () {
|
|
|
82
83
|
[],
|
|
83
84
|
[
|
|
84
85
|
usageMock,
|
|
85
|
-
'{{Error:}@darkRed}+ {{--color}@darkCyan}+: {{<color-mode>}@darkBlue}+:
|
|
86
|
+
'{{Error:}@darkRed}+ {{--color}@darkCyan}+: {{<color-mode>}@darkBlue}+: Unknown value: {{"42"}@darkYellow}+: did you mean: {{"auto"}@darkYellow}+, {{"always"}@darkYellow}+, {{"never"}@darkYellow}+ ?',
|
|
86
87
|
],
|
|
87
88
|
1,
|
|
88
89
|
);
|
|
89
90
|
});
|
|
90
91
|
|
|
91
92
|
await withEnv("MOCK_COLOR", "1", async () => {
|
|
92
|
-
await testAllFlagsFailures("env", usageMock,
|
|
93
|
+
await testAllFlagsFailures("env", usageMock, unknownOptionMock);
|
|
93
94
|
});
|
|
94
95
|
await withEnv("FORCE_COLOR", "0", async () => {
|
|
95
|
-
await testAllFlagsFailures("env", usageNone,
|
|
96
|
+
await testAllFlagsFailures("env", usageNone, unknownOptionNone);
|
|
96
97
|
});
|
|
97
98
|
|
|
98
|
-
await testAllFlagsFailures("mock", usageMock,
|
|
99
|
-
await testAllFlagsFailures("never", usageNone,
|
|
99
|
+
await testAllFlagsFailures("mock", usageMock, unknownOptionMock);
|
|
100
|
+
await testAllFlagsFailures("never", usageNone, unknownOptionNone);
|
|
100
101
|
});
|
|
101
102
|
});
|
|
102
103
|
|
|
@@ -29,9 +29,9 @@ it("run", async function () {
|
|
|
29
29
|
" subcommand Subcommand Description",
|
|
30
30
|
"",
|
|
31
31
|
"Options:",
|
|
32
|
-
" --flag[=no] Option flag description",
|
|
33
|
-
" --repeatable <string> [*] Option repeatable description",
|
|
34
|
-
" --single-value <enum(number)> Option single value description",
|
|
32
|
+
" -ff, --flag[=no] Option flag description",
|
|
33
|
+
" -r, --repeatable <string> [*] Option repeatable description",
|
|
34
|
+
" -s, --single-value <enum(number)> Option single value description",
|
|
35
35
|
"",
|
|
36
36
|
].join("\n");
|
|
37
37
|
const subcommandUsage = [
|
|
@@ -46,10 +46,10 @@ it("run", async function () {
|
|
|
46
46
|
" [variadic]... Variadics positional description",
|
|
47
47
|
"",
|
|
48
48
|
"Options:",
|
|
49
|
-
" --flag[=no] Option flag description",
|
|
50
|
-
" --repeatable <string> [*] Option repeatable description",
|
|
51
|
-
" --single-value <enum(number)> Option single value description",
|
|
52
|
-
" --url <url> [*] Option url description",
|
|
49
|
+
" -ff, --flag[=no] Option flag description",
|
|
50
|
+
" -r, --repeatable <string> [*] Option repeatable description",
|
|
51
|
+
" -s, --single-value <enum(number)> Option single value description",
|
|
52
|
+
" -u, --url <url> [*] Option url description",
|
|
53
53
|
"",
|
|
54
54
|
].join("\n");
|
|
55
55
|
|
|
@@ -115,13 +115,19 @@ it("run", async function () {
|
|
|
115
115
|
await testCase(
|
|
116
116
|
["--invalid1", "--invalid2", "required1", "--invalid3"],
|
|
117
117
|
[],
|
|
118
|
-
[
|
|
118
|
+
[
|
|
119
|
+
rootUsage,
|
|
120
|
+
'Error: Unknown option: "--invalid1": did you mean: --single-value, --help, --version ?',
|
|
121
|
+
],
|
|
119
122
|
1,
|
|
120
123
|
);
|
|
121
124
|
await testCase(
|
|
122
125
|
["required1", "unknown", "-wut", "--flag", "--single-value"],
|
|
123
126
|
[],
|
|
124
|
-
[
|
|
127
|
+
[
|
|
128
|
+
rootUsage,
|
|
129
|
+
'Error: <subcommand>: Unknown name: "unknown": did you mean: subcommand ?',
|
|
130
|
+
],
|
|
125
131
|
1,
|
|
126
132
|
);
|
|
127
133
|
|
|
@@ -149,13 +155,19 @@ it("run", async function () {
|
|
|
149
155
|
await testCase(
|
|
150
156
|
["--url", "https://example.com"],
|
|
151
157
|
[],
|
|
152
|
-
[
|
|
158
|
+
[
|
|
159
|
+
rootUsage,
|
|
160
|
+
'Error: Unknown option: "--url": did you mean: --help, -r, --version ?',
|
|
161
|
+
],
|
|
153
162
|
1,
|
|
154
163
|
);
|
|
155
164
|
await testCase(
|
|
156
165
|
["required1", "--url", "https://example.com"],
|
|
157
166
|
[],
|
|
158
|
-
[
|
|
167
|
+
[
|
|
168
|
+
rootUsage,
|
|
169
|
+
'Error: Unknown option: "--url": did you mean: --help, -r, --version ?',
|
|
170
|
+
],
|
|
159
171
|
1,
|
|
160
172
|
);
|
|
161
173
|
await testCase(
|
|
@@ -207,13 +219,19 @@ it("run", async function () {
|
|
|
207
219
|
await testCase(
|
|
208
220
|
["--invalid", "required1", "subcommand", "required2"],
|
|
209
221
|
[],
|
|
210
|
-
[
|
|
222
|
+
[
|
|
223
|
+
rootUsage,
|
|
224
|
+
'Error: Unknown option: "--invalid": did you mean: --single-value, --help, --flag ?',
|
|
225
|
+
],
|
|
211
226
|
1,
|
|
212
227
|
);
|
|
213
228
|
await testCase(
|
|
214
229
|
["required1", "subcommand", "required2", "--nope"],
|
|
215
230
|
[],
|
|
216
|
-
[
|
|
231
|
+
[
|
|
232
|
+
subcommandUsage,
|
|
233
|
+
'Error: Unknown option: "--nope": did you mean: --help, --flag, --repeatable ?',
|
|
234
|
+
],
|
|
217
235
|
1,
|
|
218
236
|
);
|
|
219
237
|
await testCase(
|
|
@@ -247,7 +265,7 @@ it("run", async function () {
|
|
|
247
265
|
[],
|
|
248
266
|
[
|
|
249
267
|
subcommandUsage,
|
|
250
|
-
'Error: <required1>:
|
|
268
|
+
'Error: <required1>: Unknown value: "invalid": did you mean: "required1-bis", "required1" ?',
|
|
251
269
|
],
|
|
252
270
|
1,
|
|
253
271
|
);
|
|
@@ -256,7 +274,7 @@ it("run", async function () {
|
|
|
256
274
|
[],
|
|
257
275
|
[
|
|
258
276
|
subcommandUsage,
|
|
259
|
-
'Error: <required1>:
|
|
277
|
+
'Error: <required1>: Unknown value: "invalid": did you mean: "required1-bis", "required1" ?',
|
|
260
278
|
],
|
|
261
279
|
1,
|
|
262
280
|
);
|
|
@@ -265,7 +283,7 @@ it("run", async function () {
|
|
|
265
283
|
[],
|
|
266
284
|
[
|
|
267
285
|
subcommandUsage,
|
|
268
|
-
'Error: <required2>:
|
|
286
|
+
'Error: <required2>: Unknown value: "invalid": did you mean: "required2-bis", "required2" ?',
|
|
269
287
|
],
|
|
270
288
|
1,
|
|
271
289
|
);
|
|
@@ -274,7 +292,7 @@ it("run", async function () {
|
|
|
274
292
|
[],
|
|
275
293
|
[
|
|
276
294
|
subcommandUsage,
|
|
277
|
-
'Error: <required1>:
|
|
295
|
+
'Error: <required1>: Unknown value: "invalid": did you mean: "required1-bis", "required1" ?',
|
|
278
296
|
],
|
|
279
297
|
1,
|
|
280
298
|
);
|
|
@@ -285,7 +303,7 @@ it("run", async function () {
|
|
|
285
303
|
[],
|
|
286
304
|
[
|
|
287
305
|
subcommandUsage,
|
|
288
|
-
'Error: --single-value: <enum(number)>: from: enum(string):
|
|
306
|
+
'Error: --single-value: <enum(number)>: from: enum(string): Unknown value: "dodo": did you mean: "42", "43" ?',
|
|
289
307
|
],
|
|
290
308
|
1,
|
|
291
309
|
);
|
|
@@ -294,7 +312,7 @@ it("run", async function () {
|
|
|
294
312
|
[],
|
|
295
313
|
[
|
|
296
314
|
subcommandUsage,
|
|
297
|
-
'Error: --single-value: <enum(number)>: from: enum(string):
|
|
315
|
+
'Error: --single-value: <enum(number)>: from: enum(string): Unknown value: "44": did you mean: "42", "43" ?',
|
|
298
316
|
],
|
|
299
317
|
1,
|
|
300
318
|
);
|
|
@@ -303,7 +321,10 @@ it("run", async function () {
|
|
|
303
321
|
await testCase(
|
|
304
322
|
["--url", "not-a-url", "required1", "subcommand", "required2"],
|
|
305
323
|
[],
|
|
306
|
-
[
|
|
324
|
+
[
|
|
325
|
+
rootUsage,
|
|
326
|
+
'Error: Unknown option: "--url": did you mean: --help, -r, --version ?',
|
|
327
|
+
],
|
|
307
328
|
1,
|
|
308
329
|
);
|
|
309
330
|
await testCase(
|
|
@@ -340,6 +361,62 @@ it("run", async function () {
|
|
|
340
361
|
[subcommandUsage, "Error: --single-value: Must not be set multiple times"],
|
|
341
362
|
1,
|
|
342
363
|
);
|
|
364
|
+
|
|
365
|
+
// Test suggestions
|
|
366
|
+
await testCase(
|
|
367
|
+
["required1", "subcommand", "required2", "-f"],
|
|
368
|
+
[],
|
|
369
|
+
[
|
|
370
|
+
subcommandUsage,
|
|
371
|
+
'Error: Unknown option: "-f": did you mean: -ff, -r, -s ?',
|
|
372
|
+
],
|
|
373
|
+
1,
|
|
374
|
+
);
|
|
375
|
+
await testCase(
|
|
376
|
+
["required1", "subcommand", "required2", "-flag"],
|
|
377
|
+
[],
|
|
378
|
+
[
|
|
379
|
+
subcommandUsage,
|
|
380
|
+
'Error: Unknown option: "-flag": did you mean: --flag, -ff, --single-value ?',
|
|
381
|
+
],
|
|
382
|
+
1,
|
|
383
|
+
);
|
|
384
|
+
await testCase(
|
|
385
|
+
["required1", "subcommand", "required2", "--uri"],
|
|
386
|
+
[],
|
|
387
|
+
[
|
|
388
|
+
subcommandUsage,
|
|
389
|
+
'Error: Unknown option: "--uri": did you mean: --url, --version, -r ?',
|
|
390
|
+
],
|
|
391
|
+
1,
|
|
392
|
+
);
|
|
393
|
+
await testCase(
|
|
394
|
+
["required1", "subcommand", "required2", "--single-"],
|
|
395
|
+
[],
|
|
396
|
+
[
|
|
397
|
+
subcommandUsage,
|
|
398
|
+
'Error: Unknown option: "--single-": did you mean: --single-value, --help, --flag ?',
|
|
399
|
+
],
|
|
400
|
+
1,
|
|
401
|
+
);
|
|
402
|
+
await testCase(
|
|
403
|
+
["required-bis1", "subcommand", "required2"],
|
|
404
|
+
[],
|
|
405
|
+
[
|
|
406
|
+
subcommandUsage,
|
|
407
|
+
'Error: <required1>: Unknown value: "required-bis1": did you mean: "required1-bis", "required1" ?',
|
|
408
|
+
],
|
|
409
|
+
1,
|
|
410
|
+
);
|
|
411
|
+
await testCase(
|
|
412
|
+
["required1", "subcomm"],
|
|
413
|
+
[],
|
|
414
|
+
[
|
|
415
|
+
rootUsage,
|
|
416
|
+
'Error: <subcommand>: Unknown name: "subcomm": did you mean: subcommand ?',
|
|
417
|
+
],
|
|
418
|
+
1,
|
|
419
|
+
);
|
|
343
420
|
});
|
|
344
421
|
|
|
345
422
|
async function testCase(
|
|
@@ -364,22 +441,25 @@ async function testCase(
|
|
|
364
441
|
options: {
|
|
365
442
|
optionFlag: optionFlag({
|
|
366
443
|
long: "flag",
|
|
444
|
+
short: "ff",
|
|
367
445
|
description: "Option flag description",
|
|
368
446
|
}),
|
|
369
447
|
optionRepeatable: optionRepeatable({
|
|
370
448
|
long: "repeatable",
|
|
449
|
+
short: "r",
|
|
371
450
|
type: type(),
|
|
372
451
|
description: "Option repeatable description",
|
|
373
452
|
}),
|
|
374
453
|
optionSingleValue: optionSingleValue({
|
|
375
454
|
long: "single-value",
|
|
455
|
+
short: "s",
|
|
376
456
|
type: typeConverted(
|
|
377
457
|
"enum(number)",
|
|
378
458
|
typeChoice("enum(string)", ["42", "43"]),
|
|
379
459
|
(value) => Number(value),
|
|
380
460
|
),
|
|
381
461
|
description: "Option single value description",
|
|
382
|
-
|
|
462
|
+
defaultIfNotSpecified: () => 42,
|
|
383
463
|
}),
|
|
384
464
|
},
|
|
385
465
|
positionals: [
|
|
@@ -401,8 +481,9 @@ async function testCase(
|
|
|
401
481
|
options: {
|
|
402
482
|
optionExtra: optionRepeatable({
|
|
403
483
|
long: "url",
|
|
484
|
+
short: "u",
|
|
404
485
|
description: "Option url description",
|
|
405
|
-
type: typeUrl(
|
|
486
|
+
type: typeUrl(),
|
|
406
487
|
}),
|
|
407
488
|
},
|
|
408
489
|
positionals: [
|
|
@@ -17,7 +17,11 @@ it("run", async function () {
|
|
|
17
17
|
);
|
|
18
18
|
await testCase(
|
|
19
19
|
["--nope"],
|
|
20
|
-
|
|
20
|
+
'{{Error:}@darkRed}+ Unknown option: {{"--nope"}@darkYellow}+: did you mean: {{--help}@darkCyan}+, {{--flag}@darkCyan}+, {{--repeatable}@darkCyan}+ ?',
|
|
21
|
+
);
|
|
22
|
+
await testCase(
|
|
23
|
+
["--repeat"],
|
|
24
|
+
'{{Error:}@darkRed}+ Unknown option: {{"--repeat"}@darkYellow}+: did you mean: {{--repeatable}@darkCyan}+, {{--help}@darkCyan}+, {{--flag}@darkCyan}+ ?',
|
|
21
25
|
);
|
|
22
26
|
await testCase(
|
|
23
27
|
["--flag", "--flag"],
|
|
@@ -58,7 +62,7 @@ async function testCase(args: Array<string>, error: string) {
|
|
|
58
62
|
optionSingleValue: optionSingleValue({
|
|
59
63
|
long: "single-value",
|
|
60
64
|
type: typeUrl("location"),
|
|
61
|
-
|
|
65
|
+
defaultIfNotSpecified: () => undefined,
|
|
62
66
|
}),
|
|
63
67
|
optionRepeatable: optionRepeatable({
|
|
64
68
|
long: "repeatable",
|