cli-kiss 0.2.6 → 0.2.8

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.
Files changed (39) hide show
  1. package/README.md +62 -4
  2. package/dist/index.d.ts +135 -128
  3. package/dist/index.js +2 -2
  4. package/dist/index.js.map +1 -1
  5. package/docs/.vitepress/config.mts +1 -1
  6. package/docs/.vitepress/theme/Layout.vue +16 -0
  7. package/docs/.vitepress/theme/index.ts +5 -1
  8. package/docs/.vitepress/theme/style.css +5 -1
  9. package/docs/guide/02_commands.md +1 -1
  10. package/docs/guide/03_options.md +11 -11
  11. package/docs/guide/05_input_types.md +9 -10
  12. package/docs/guide/06_run_as_cli.md +1 -1
  13. package/docs/index.md +2 -2
  14. package/docs/public/favicon.ico +0 -0
  15. package/docs/public/logo.png +0 -0
  16. package/package.json +1 -1
  17. package/src/index.ts +1 -0
  18. package/src/lib/Command.ts +50 -30
  19. package/src/lib/Operation.ts +29 -21
  20. package/src/lib/Option.ts +198 -133
  21. package/src/lib/Positional.ts +46 -24
  22. package/src/lib/Reader.ts +194 -207
  23. package/src/lib/Run.ts +19 -8
  24. package/src/lib/Suggest.ts +78 -0
  25. package/src/lib/Type.ts +46 -48
  26. package/src/lib/Typo.ts +72 -47
  27. package/src/lib/Usage.ts +13 -13
  28. package/tests/unit.Reader.commons.ts +92 -116
  29. package/tests/unit.Reader.parsings.ts +14 -26
  30. package/tests/unit.Reader.shortBig.ts +81 -96
  31. package/tests/unit.command.aliases.ts +100 -0
  32. package/tests/unit.command.execute.ts +1 -1
  33. package/tests/unit.command.usage.ts +12 -6
  34. package/tests/unit.fuzzed.alternatives.ts +43 -0
  35. package/tests/unit.runner.colors.ts +11 -35
  36. package/tests/unit.runner.cycle.ts +181 -128
  37. package/tests/unit.runner.errors.ts +26 -19
  38. package/docs/public/hero.png +0 -0
  39. package/tests/unit.Reader.aliases.ts +0 -62
@@ -1,4 +1,4 @@
1
- import { it } from "@jest/globals";
1
+ import { expect, it } from "@jest/globals";
2
2
  import {
3
3
  command,
4
4
  commandWithSubcommands,
@@ -18,110 +18,102 @@ import {
18
18
 
19
19
  it("run", async function () {
20
20
  const rootUsage = [
21
- "Usage: my-cli <required1> <subcommand>",
21
+ "Usage: my-cli <req1> <subcommand>",
22
22
  "",
23
23
  "Root Description",
24
24
  "",
25
25
  "Positionals:",
26
- " <required1> Required1 positional description",
26
+ " <req1> Required1 positional description",
27
27
  "",
28
28
  "Subcommands:",
29
- " subcommand Subcommand Description",
29
+ " sub 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
- const subcommandUsage = [
38
- "Usage: my-cli <required1> subcommand <required2> [optional] [variadic]...",
37
+ const subUsage = [
38
+ "Usage: my-cli <req1> sub <req2> [optional] [variadic]...",
39
39
  "",
40
40
  "Subcommand Description",
41
41
  "",
42
42
  "Positionals:",
43
- " <required1> Required1 positional description",
44
- " <required2> Required2 positional description",
43
+ " <req1> Required1 positional description",
44
+ " <req2> Required2 positional description",
45
45
  " [optional] Optional positional description",
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
 
56
56
  // Test that everything could work normally
57
57
  await testCase(
58
- ["required1", "subcommand", "required2"],
59
- ["Has executed root command", "Has executed subcommand"],
58
+ ["req1", "sub", "req2"],
59
+ ["Has executed root command", "Has executed sub"],
60
60
  [],
61
61
  0,
62
62
  );
63
63
 
64
64
  // Check that version flag takes precedence over execution
65
65
  await testCase(["--version"], ["my-cli 1.0.0"], [], 0);
66
- await testCase(["required1", "--version"], ["my-cli 1.0.0"], [], 0);
67
- await testCase(
68
- ["required1", "subcommand", "--version"],
69
- ["my-cli 1.0.0"],
70
- [],
71
- 0,
72
- );
73
- await testCase(
74
- ["required1", "subcommand", "required2", "--version"],
75
- ["my-cli 1.0.0"],
76
- [],
77
- 0,
78
- );
66
+ await testCase(["req1", "--version"], ["my-cli 1.0.0"], [], 0);
67
+ await testCase(["req1", "sub", "--version"], ["my-cli 1.0.0"], [], 0);
68
+ await testCase(["req1", "sub", "req2", "--version"], ["my-cli 1.0.0"], [], 0);
79
69
 
80
70
  // Check that help flag takes precedence over execution
81
71
  await testCase(["--help"], [rootUsage], [], 0);
82
- await testCase(["required1", "--help"], [rootUsage], [], 0);
83
- await testCase(
84
- ["required1", "subcommand", "--help"],
85
- [subcommandUsage],
86
- [],
87
- 0,
88
- );
89
- await testCase(
90
- ["required1", "subcommand", "required2", "--help"],
91
- [subcommandUsage],
92
- [],
93
- 0,
94
- );
72
+ await testCase(["req1", "--help"], [rootUsage], [], 0);
73
+ await testCase(["req1", "sub", "--help"], [subUsage], [], 0);
74
+ await testCase(["req1", "sub", "req2", "--help"], [subUsage], [], 0);
95
75
 
96
76
  // Help takes precedence over version
97
77
  await testCase(["--version", "--help"], [rootUsage], [], 0);
98
78
  await testCase(["--help", "--version"], [rootUsage], [], 0);
99
79
 
80
+ // Help still works after failed parsing (should show usage for the right command)
81
+ await testCase(["--invalid", "--help"], [rootUsage], [], 0);
82
+ await testCase(["invalid", "--help"], [rootUsage], [], 0);
83
+ await testCase(["req1", "sub", "--invalid", "--help"], [subUsage], [], 0);
84
+ await testCase(["req1", "sub", "invalid", "--help"], [subUsage], [], 0);
85
+
100
86
  // Weird help/version values inputs
101
87
  await testCase(
102
88
  ["--help=invalid"],
103
89
  [],
104
- [rootUsage, `Error: --help: value: Not a boolean: "invalid"`],
90
+ [rootUsage, `Error: --help: value: Not a boolean: "invalid".`],
105
91
  1,
106
92
  );
107
93
  await testCase(
108
94
  ["--version=invalid"],
109
95
  [],
110
- [rootUsage, `Error: --version: value: Not a boolean: "invalid"`],
96
+ [rootUsage, `Error: --version: value: Not a boolean: "invalid".`],
111
97
  1,
112
98
  );
113
99
 
114
100
  // Test multiple errors at once (first one should show only)
115
101
  await testCase(
116
- ["--invalid1", "--invalid2", "required1", "--invalid3"],
102
+ ["--invalid1", "--invalid2", "req1", "--invalid3"],
117
103
  [],
118
- [rootUsage, "Error: Unexpected unknown option: --invalid1"],
104
+ [
105
+ rootUsage,
106
+ 'Error: Unknown option: "--invalid1". Did you mean: --single-value, --help, --version, ... ?',
107
+ ],
119
108
  1,
120
109
  );
121
110
  await testCase(
122
- ["required1", "unknown", "-wut", "--flag", "--single-value"],
111
+ ["req1", "unknown", "-wut", "--flag", "--single-value"],
123
112
  [],
124
- [rootUsage, 'Error: <subcommand>: Invalid value: "unknown"'],
113
+ [
114
+ rootUsage,
115
+ 'Error: <subcommand>: Unknown name: "unknown". Did you mean: sub ?',
116
+ ],
125
117
  1,
126
118
  );
127
119
 
@@ -129,109 +121,130 @@ it("run", async function () {
129
121
  await testCase(
130
122
  [],
131
123
  [],
132
- [rootUsage, "Error: <required1>: Is required, but was not provided"],
124
+ [
125
+ rootUsage,
126
+ "Error: <req1>: Is required, but was not provided. (Required1 positional description)",
127
+ ],
133
128
  1,
134
129
  );
135
130
  await testCase(
136
- ["required1"],
131
+ ["req1"],
137
132
  [],
138
- [rootUsage, "Error: <subcommand>: Is required, but was not provided"],
133
+ [rootUsage, "Error: <subcommand>: Missing argument. Did you mean: sub ?"],
139
134
  1,
140
135
  );
141
136
  await testCase(
142
- ["required1", "subcommand"],
137
+ ["req1", "sub"],
143
138
  [],
144
- [subcommandUsage, "Error: <required2>: Is required, but was not provided"],
139
+ [
140
+ subUsage,
141
+ "Error: <req2>: Is required, but was not provided. (Required2 positional description)",
142
+ ],
145
143
  1,
146
144
  );
147
145
 
148
- // Test that flags become available when subcommand is known
146
+ // Test that flags become available when sub is known
149
147
  await testCase(
150
148
  ["--url", "https://example.com"],
151
149
  [],
152
- [rootUsage, "Error: Unexpected unknown option: --url"],
150
+ [
151
+ rootUsage,
152
+ 'Error: Unknown option: "--url". Did you mean: --help, -r, --version, ... ?',
153
+ ],
153
154
  1,
154
155
  );
155
156
  await testCase(
156
- ["required1", "--url", "https://example.com"],
157
+ ["req1", "--url", "https://example.com"],
157
158
  [],
158
- [rootUsage, "Error: Unexpected unknown option: --url"],
159
+ [
160
+ rootUsage,
161
+ 'Error: Unknown option: "--url". Did you mean: --help, -r, --version, ... ?',
162
+ ],
159
163
  1,
160
164
  );
161
165
  await testCase(
162
- ["required1", "subcommand", "--url", "https://example.com"],
166
+ ["req1", "sub", "--url", "https://example.com"],
163
167
  [],
164
- [subcommandUsage, "Error: <required2>: Is required, but was not provided"],
168
+ [
169
+ subUsage,
170
+ "Error: <req2>: Is required, but was not provided. (Required2 positional description)",
171
+ ],
165
172
  1,
166
173
  );
167
174
  await testCase(
168
- ["required1", "subcommand", "required2", "--url", "https://example.com"],
169
- ["Has executed root command", "Has executed subcommand"],
175
+ ["req1", "sub", "req2", "--url", "https://example.com"],
176
+ ["Has executed root command", "Has executed sub"],
170
177
  [],
171
178
  0,
172
179
  );
173
180
 
174
181
  // Test option as flag parsing cases
175
182
  await testCase(
176
- ["--flag", "--flag", "required1", "subcommand", "required2"],
183
+ ["--flag", "--flag", "req1", "sub", "req2"],
177
184
  [],
178
- [subcommandUsage, "Error: --flag: Must not be set multiple times"],
185
+ [subUsage, "Error: --flag: Must not be set multiple times."],
179
186
  1,
180
187
  );
181
188
  await testCase(
182
- ["--flag=42", "required1", "subcommand", "required2"],
189
+ ["--flag=42", "req1", "sub", "req2"],
183
190
  [],
184
- [subcommandUsage, 'Error: --flag: value: Not a boolean: "42"'],
191
+ [subUsage, 'Error: --flag: value: Not a boolean: "42".'],
185
192
  1,
186
193
  );
187
194
  await testCase(
188
- ["--flag=no", "required1", "subcommand", "required2"],
189
- ["Has executed root command", "Has executed subcommand"],
195
+ ["--flag=no", "req1", "sub", "req2"],
196
+ ["Has executed root command", "Has executed sub"],
190
197
  [],
191
198
  0,
192
199
  );
193
200
  await testCase(
194
- ["--flag=yes", "required1", "subcommand", "required2"],
195
- ["Has executed root command", "Has executed subcommand"],
201
+ ["--flag=yes", "req1", "sub", "req2"],
202
+ ["Has executed root command", "Has executed sub"],
196
203
  [],
197
204
  0,
198
205
  );
199
206
  await testCase(
200
- ["--flag", "required1", "subcommand", "required2"],
201
- ["Has executed root command", "Has executed subcommand"],
207
+ ["--flag", "req1", "sub", "req2"],
208
+ ["Has executed root command", "Has executed sub"],
202
209
  [],
203
210
  0,
204
211
  );
205
212
 
206
213
  // Test option parsing errors
207
214
  await testCase(
208
- ["--invalid", "required1", "subcommand", "required2"],
215
+ ["--invalid", "req1", "sub", "req2"],
209
216
  [],
210
- [rootUsage, "Error: Unexpected unknown option: --invalid"],
217
+ [
218
+ rootUsage,
219
+ 'Error: Unknown option: "--invalid". Did you mean: --single-value, --help, --flag, ... ?',
220
+ ],
211
221
  1,
212
222
  );
213
223
  await testCase(
214
- ["required1", "subcommand", "required2", "--nope"],
224
+ ["req1", "sub", "req2", "--nope"],
215
225
  [],
216
- [subcommandUsage, "Error: Unexpected unknown option: --nope"],
226
+ [
227
+ subUsage,
228
+ 'Error: Unknown option: "--nope". Did you mean: --help, --flag, --repeatable, ... ?',
229
+ ],
217
230
  1,
218
231
  );
219
232
  await testCase(
220
- ["required1", "subcommand", "required2", "--url"],
233
+ ["req1", "sub", "req2", "--url"],
221
234
  [],
222
- [subcommandUsage, "Error: --url: Requires a value, but got end of input"],
235
+ [subUsage, "Error: --url: Requires a value, but got end of input."],
223
236
  1,
224
237
  );
225
238
  await testCase(
226
- ["required1", "subcommand", "required2", "--url", "--", "url"],
239
+ ["req1", "sub", "req2", "--url", "--", "url"],
227
240
  [],
228
- [subcommandUsage, 'Error: --url: Requires a value before "--"'],
241
+ [subUsage, 'Error: --url: Requires a value but got: "--".'],
229
242
  1,
230
243
  );
231
244
  await testCase(
232
- ["required1", "subcommand", "required2", "--url", "--url"],
245
+ ["req1", "sub", "req2", "--url", "--url"],
233
246
  [],
234
- [subcommandUsage, 'Error: --url: Requires a value, but got: "--url"'],
247
+ [subUsage, 'Error: --url: Requires a value, but got: "--url".'],
235
248
  1,
236
249
  );
237
250
 
@@ -239,105 +252,141 @@ it("run", async function () {
239
252
  await testCase(
240
253
  ["invalid"],
241
254
  [],
242
- [rootUsage, "Error: <subcommand>: Is required, but was not provided"],
255
+ [rootUsage, "Error: <subcommand>: Missing argument. Did you mean: sub ?"],
243
256
  1,
244
257
  );
245
258
  await testCase(
246
- ["invalid", "subcommand"],
259
+ ["invalid", "sub"],
247
260
  [],
248
261
  [
249
- subcommandUsage,
250
- 'Error: <required1>: Invalid value: "invalid" (expected one of: "required1" | "required1-bis")',
262
+ subUsage,
263
+ 'Error: <req1>: Unknown value: "invalid". Did you mean: "req1-bis", "req1" ?',
251
264
  ],
252
265
  1,
253
266
  );
254
267
  await testCase(
255
- ["invalid", "subcommand", "required2"],
268
+ ["invalid", "sub", "req2"],
256
269
  [],
257
270
  [
258
- subcommandUsage,
259
- 'Error: <required1>: Invalid value: "invalid" (expected one of: "required1" | "required1-bis")',
271
+ subUsage,
272
+ 'Error: <req1>: Unknown value: "invalid". Did you mean: "req1-bis", "req1" ?',
260
273
  ],
261
274
  1,
262
275
  );
263
276
  await testCase(
264
- ["required1", "subcommand", "invalid"],
277
+ ["req1", "sub", "invalid"],
265
278
  [],
266
279
  [
267
- subcommandUsage,
268
- 'Error: <required2>: Invalid value: "invalid" (expected one of: "required2" | "required2-bis")',
280
+ subUsage,
281
+ 'Error: <req2>: Unknown value: "invalid". Did you mean: "req2-bis", "req2" ?',
269
282
  ],
270
283
  1,
271
284
  );
272
285
  await testCase(
273
- ["invalid", "subcommand", "invalid"],
286
+ ["invalid", "sub", "invalid"],
274
287
  [],
275
288
  [
276
- subcommandUsage,
277
- 'Error: <required1>: Invalid value: "invalid" (expected one of: "required1" | "required1-bis")',
289
+ subUsage,
290
+ 'Error: <req1>: Unknown value: "invalid". Did you mean: "req1-bis", "req1" ?',
278
291
  ],
279
292
  1,
280
293
  );
281
294
 
282
295
  // Test root command option invalid values (must not block parsing)
283
296
  await testCase(
284
- ["--single-value=dodo", "required1", "subcommand", "required2"],
297
+ ["--single-value=dodo", "req1", "sub", "req2"],
285
298
  [],
286
299
  [
287
- subcommandUsage,
288
- 'Error: --single-value: <enum(number)>: from: enum(string): Invalid value: "dodo" (expected one of: "42" | "43")',
300
+ subUsage,
301
+ 'Error: --single-value: <enum(number)>: from: enum(string): Unknown value: "dodo". Did you mean: "42", "43" ?',
289
302
  ],
290
303
  1,
291
304
  );
292
305
  await testCase(
293
- ["required1", "subcommand", "required2", "--single-value=44"],
306
+ ["req1", "sub", "req2", "--single-value=44"],
294
307
  [],
295
308
  [
296
- subcommandUsage,
297
- 'Error: --single-value: <enum(number)>: from: enum(string): Invalid value: "44" (expected one of: "42" | "43")',
309
+ subUsage,
310
+ 'Error: --single-value: <enum(number)>: from: enum(string): Unknown value: "44". Did you mean: "42", "43" ?',
298
311
  ],
299
312
  1,
300
313
  );
301
314
 
302
- // Test subcommand-only option failures
315
+ // Test sub-only option failures
303
316
  await testCase(
304
- ["--url", "not-a-url", "required1", "subcommand", "required2"],
317
+ ["--url", "not-a-url", "req1", "sub", "req2"],
305
318
  [],
306
- [rootUsage, "Error: Unexpected unknown option: --url"],
319
+ [
320
+ rootUsage,
321
+ 'Error: Unknown option: "--url". Did you mean: --help, -r, --version, ... ?',
322
+ ],
307
323
  1,
308
324
  );
309
325
  await testCase(
310
- ["required1", "subcommand", "required2", "--url", "not-a-url"],
326
+ ["req1", "sub", "req2", "--url", "not-a-url"],
311
327
  [],
312
- [subcommandUsage, 'Error: --url: <url>: Not an URL: "not-a-url"'],
328
+ [subUsage, 'Error: --url: <url>: Not an URL: "not-a-url".'],
313
329
  1,
314
330
  );
315
331
 
316
332
  // Test option multiple value parsing cases
317
333
  await testCase(
318
- [
319
- "required1",
320
- "subcommand",
321
- "required2",
322
- "--repeatable=42",
323
- "--repeatable",
324
- "43",
325
- ],
326
- ["Has executed root command", "Has executed subcommand"],
334
+ ["req1", "sub", "req2", "--repeatable=42", "--repeatable", "43"],
335
+ ["Has executed root command", "Has executed sub"],
327
336
  [],
328
337
  0,
329
338
  );
330
339
  await testCase(
340
+ ["req1", "sub", "req2", "--single-value=42", "--single-value", "43"],
341
+ [],
342
+ [subUsage, "Error: --single-value: Must not be set multiple times."],
343
+ 1,
344
+ );
345
+
346
+ // Test suggestions
347
+ await testCase(
348
+ ["req1", "sub", "req2", "-f"],
349
+ [],
350
+ [subUsage, 'Error: Unknown option: "-f". Did you mean: -ff, -r, -s, ... ?'],
351
+ 1,
352
+ );
353
+ await testCase(
354
+ ["req1", "sub", "req2", "-flag"],
355
+ [],
356
+ [subUsage, 'Error: Unknown option: "-flag". Did you mean: --flag ?'],
357
+ 1,
358
+ );
359
+ await testCase(
360
+ ["req1", "sub", "req2", "--uri"],
361
+ [],
362
+ [subUsage, 'Error: Unknown option: "--uri". Did you mean: --url ?'],
363
+ 1,
364
+ );
365
+ await testCase(
366
+ ["req1", "sub", "req2", "--single-"],
367
+ [],
331
368
  [
332
- "required1",
333
- "subcommand",
334
- "required2",
335
- "--single-value=42",
336
- "--single-value",
337
- "43",
369
+ subUsage,
370
+ 'Error: Unknown option: "--single-". Did you mean: --single-value ?',
338
371
  ],
372
+ 1,
373
+ );
374
+ await testCase(
375
+ ["1req", "sub", "req2"],
339
376
  [],
340
- [subcommandUsage, "Error: --single-value: Must not be set multiple times"],
377
+ [
378
+ subUsage,
379
+ 'Error: <req1>: Unknown value: "1req". Did you mean: "req1", "req1-bis" ?',
380
+ ],
381
+ 1,
382
+ );
383
+ await testCase(
384
+ ["req1", "subcomm"],
385
+ [],
386
+ [
387
+ rootUsage,
388
+ 'Error: <subcommand>: Unknown name: "subcomm". Did you mean: sub ?',
389
+ ],
341
390
  1,
342
391
  );
343
392
  });
@@ -364,27 +413,30 @@ async function testCase(
364
413
  options: {
365
414
  optionFlag: optionFlag({
366
415
  long: "flag",
416
+ short: "ff",
367
417
  description: "Option flag description",
368
418
  }),
369
419
  optionRepeatable: optionRepeatable({
370
420
  long: "repeatable",
421
+ short: "r",
371
422
  type: type(),
372
423
  description: "Option repeatable description",
373
424
  }),
374
425
  optionSingleValue: optionSingleValue({
375
426
  long: "single-value",
427
+ short: "s",
376
428
  type: typeConverted(
377
429
  "enum(number)",
378
430
  typeChoice("enum(string)", ["42", "43"]),
379
431
  (value) => Number(value),
380
432
  ),
381
433
  description: "Option single value description",
382
- defaultWhenNotDefined: () => 42,
434
+ fallbackValueIfAbsent: () => 42,
383
435
  }),
384
436
  },
385
437
  positionals: [
386
438
  positionalRequired({
387
- type: typeChoice("required1", ["required1", "required1-bis"]),
439
+ type: typeChoice("req1", ["req1", "req1-bis"]),
388
440
  description: "Required1 positional description",
389
441
  }),
390
442
  ],
@@ -394,20 +446,21 @@ async function testCase(
394
446
  },
395
447
  ),
396
448
  {
397
- subcommand: command(
449
+ sub: command(
398
450
  { description: "Subcommand Description" },
399
451
  operation(
400
452
  {
401
453
  options: {
402
454
  optionExtra: optionRepeatable({
403
455
  long: "url",
456
+ short: "u",
404
457
  description: "Option url description",
405
- type: typeUrl("url"),
458
+ type: typeUrl(),
406
459
  }),
407
460
  },
408
461
  positionals: [
409
462
  positionalRequired({
410
- type: typeChoice("required2", ["required2", "required2-bis"]),
463
+ type: typeChoice("req2", ["req2", "req2-bis"]),
411
464
  description: "Required2 positional description",
412
465
  }),
413
466
  positionalOptional({
@@ -422,7 +475,7 @@ async function testCase(
422
475
  ],
423
476
  },
424
477
  async function (_, _inputs) {
425
- console.log(`Has executed subcommand`);
478
+ console.log(`Has executed sub`);
426
479
  },
427
480
  ),
428
481
  ),
@@ -1,4 +1,4 @@
1
- import { it } from "@jest/globals";
1
+ import { expect, it } from "@jest/globals";
2
2
  import {
3
3
  command,
4
4
  operation,
@@ -13,35 +13,43 @@ import {
13
13
  it("run", async function () {
14
14
  await testCase(
15
15
  ["hello"],
16
- '{{Error:}@darkRed}+ Unexpected argument: {{"hello"}@darkYellow}+',
16
+ '{{Error:}@darkRed}+ Unexpected argument: {{"hello"}@darkYellow}+.', // TODO - should we recommend help here ?
17
17
  );
18
18
  await testCase(
19
19
  ["--nope"],
20
- "{{Error:}@darkRed}+ Unexpected unknown option: {{--nope}@darkYellow}+",
20
+ '{{Error:}@darkRed}+ Unknown option: {{"--nope"}@darkYellow}+. Did you mean: {{--help}@darkCyan}+, {{--flag}@darkCyan}+, {{--repeatable}@darkCyan}+, {{...}-}* ?',
21
21
  );
22
22
  await testCase(
23
- ["--flag", "--flag"],
24
- "{{Error:}@darkRed}+ {{--flag}@darkCyan}+: Must not be set multiple times",
25
- );
26
- await testCase(
27
- ["--flag=invalid"],
28
- '{{Error:}@darkRed}+ {{--flag}@darkCyan}+: {{value}@darkMagenta}+: Not a boolean: {{"invalid"}@darkYellow}+',
23
+ ["--repeat"],
24
+ '{{Error:}@darkRed}+ Unknown option: {{"--repeat"}@darkYellow}+. Did you mean: {{--repeatable}@darkCyan}+ ?',
29
25
  );
30
26
  await testCase(
31
27
  ["--single-value=a", "--single-value=b"],
32
- "{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: Must not be set multiple times",
28
+ "{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: Must not be set multiple times.",
33
29
  );
34
30
  await testCase(
35
31
  ["--single-value"],
36
- "{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: Requires a value, but got end of input",
32
+ "{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: Requires a value, but got end of input.",
33
+ );
34
+ await testCase(
35
+ [],
36
+ "{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: {{<location>}@darkBlue}+: Is required, but was not set.",
37
37
  );
38
38
  await testCase(
39
39
  ["--single-value=invalid"],
40
- '{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: {{<location>}@darkBlue}+: Not an URL: {{"invalid"}@darkYellow}+',
40
+ '{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: {{<location>}@darkBlue}+: Not an URL: {{"invalid"}@darkYellow}+.',
41
41
  );
42
42
  await testCase(
43
43
  ["--repeatable=invalid"],
44
- '{{Error:}@darkRed}+ {{--repeatable}@darkCyan}+: {{<index>}@darkBlue}+: Not a number: {{"invalid"}@darkYellow}+',
44
+ '{{Error:}@darkRed}+ {{--repeatable}@darkCyan}+: {{<index>}@darkBlue}+: Not a number: {{"invalid"}@darkYellow}+.',
45
+ );
46
+ await testCase(
47
+ ["--flag", "-f"],
48
+ "{{Error:}@darkRed}+ {{--flag}@darkCyan}+, {{-f}@darkCyan}+: Must not be set multiple times.",
49
+ );
50
+ await testCase(
51
+ ["--flag=invalid"],
52
+ '{{Error:}@darkRed}+ {{--flag}@darkCyan}+: {{value}@darkMagenta}+: Not a boolean: {{"invalid"}@darkYellow}+.',
45
53
  );
46
54
  });
47
55
 
@@ -54,16 +62,15 @@ async function testCase(args: Array<string>, error: string) {
54
62
  operation(
55
63
  {
56
64
  options: {
57
- optionFlag: optionFlag({ long: "flag" }),
58
- optionSingleValue: optionSingleValue({
59
- long: "single-value",
60
- type: typeUrl("location"),
61
- defaultWhenNotDefined: () => undefined,
62
- }),
65
+ optionFlag: optionFlag({ long: "flag", short: "f" }),
63
66
  optionRepeatable: optionRepeatable({
64
67
  long: "repeatable",
65
68
  type: typeNumber("index"),
66
69
  }),
70
+ optionSingleValue: optionSingleValue({
71
+ long: "single-value",
72
+ type: typeUrl("location"),
73
+ }),
67
74
  },
68
75
  positionals: [],
69
76
  },
Binary file