cli-kiss 0.2.2 → 0.2.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.
@@ -2,6 +2,7 @@ import { it } from "@jest/globals";
2
2
  import {
3
3
  command,
4
4
  Command,
5
+ commandChained,
5
6
  commandWithSubcommands,
6
7
  operation,
7
8
  optionFlag,
@@ -19,10 +20,16 @@ import {
19
20
  usageToStyledLines,
20
21
  } from "../src";
21
22
 
22
- const cmd = commandWithSubcommands<string, any, any>(
23
+ const rootCommand = commandChained<any, any, any>(
23
24
  {
24
25
  description: "Root command description",
25
- details: ["Root command details.", "Second line of root command details."],
26
+ details: ["Root command details.", "Root second line of command details."],
27
+ examples: [
28
+ {
29
+ explanation: "Example usage of the root command",
30
+ commandArgs: [{ positional: "42" }, { option: { short: "b" } }],
31
+ },
32
+ ],
26
33
  },
27
34
  operation(
28
35
  {
@@ -30,20 +37,7 @@ const cmd = commandWithSubcommands<string, any, any>(
30
37
  booleanFlag: optionFlag({
31
38
  short: "b",
32
39
  long: "boolean-flag",
33
- description: "Root boolean-flag description",
34
- }),
35
- stringOption: optionSingleValue({
36
- short: "s",
37
- long: "string-option",
38
- type: typeString,
39
- default: () => undefined,
40
- label: "COOL-STUFF",
41
- description: "Root string-option description",
42
- }),
43
- complexOption: optionRepeatable({
44
- long: "complex-option",
45
- type: typeTuple([typeNumber, typeList(typeString)]),
46
- description: "Root complex-option description",
40
+ description: "boolean-flag description",
47
41
  }),
48
42
  },
49
43
  positionals: [
@@ -52,107 +46,193 @@ const cmd = commandWithSubcommands<string, any, any>(
52
46
  description: "Required positional number 1",
53
47
  type: typeNumber,
54
48
  }),
55
- positionalRequired({
56
- label: "POS-2",
57
- description: "Required positional number 2",
58
- type: typeNumber,
59
- }),
60
49
  ],
61
50
  },
62
- async (context, inputs) => {
51
+ async function (context, inputs) {
63
52
  return { at: "root", context, inputs };
64
53
  },
65
54
  ),
66
- {
67
- sub1: command(
68
- {
69
- description: "Subcommand 1 description",
70
- details: [
71
- "Subcommand 1 details.",
72
- "Second line of subcommand 1 details.",
73
- ],
74
- },
75
- operation(
55
+ commandWithSubcommands<any, any, any>(
56
+ {
57
+ description: "Mid command description",
58
+ details: ["Mid command details.", "Mid second line of command details."],
59
+ examples: [
76
60
  {
77
- options: {},
78
- positionals: [
79
- positionalRequired({
80
- label: "POS-STRING",
81
- description: "Required positional string",
82
- type: typeString,
83
- }),
61
+ explanation: "Example usage of the mid command",
62
+ commandArgs: [
63
+ { positional: "42" },
64
+ { option: { short: "b" } },
65
+ { positional: "43" },
84
66
  ],
85
67
  },
86
- async (context, inputs) => {
87
- return { at: "sub1", context, inputs };
88
- },
89
- ),
90
- ),
91
- sub2: command(
68
+ ],
69
+ },
70
+ operation(
92
71
  {
93
- description: "Subcommand 2 description",
94
- hint: "Subcommand 2 hint",
95
- details: [
96
- "Subcommand 2 details.",
97
- "Second line of subcommand 2 details.",
72
+ options: {
73
+ stringOption: optionSingleValue({
74
+ short: "s",
75
+ long: "string-option",
76
+ type: typeString,
77
+ default: () => undefined,
78
+ label: "COOL-STUFF",
79
+ description: "string-option description",
80
+ }),
81
+ complexOption: optionRepeatable({
82
+ long: "complex-option",
83
+ type: typeTuple([typeNumber, typeList(typeString)]),
84
+ description: "complex-option description",
85
+ }),
86
+ },
87
+ positionals: [
88
+ positionalRequired({
89
+ label: "POS-2",
90
+ description: "Required positional number 2",
91
+ type: typeNumber,
92
+ }),
98
93
  ],
99
94
  },
100
- operation(
95
+ async function (context, inputs) {
96
+ return { at: "root", context, inputs };
97
+ },
98
+ ),
99
+ {
100
+ sub1: command(
101
101
  {
102
- options: {
103
- duduValue: optionSingleValue({
104
- long: "dudu",
105
- type: typeString,
106
- default: () => "duduDefault",
107
- hint: "Dudu option hint",
108
- description: "Dudu option description",
109
- }),
110
- },
111
- positionals: [
112
- positionalRequired({
113
- label: "POS-NUMBER",
114
- description: "Required positional number",
115
- type: typeNumber,
116
- }),
117
- positionalOptional({
118
- label: "OPT-POS",
119
- description: "Optional positional string",
120
- hint: "Optional positional hint",
121
- type: typeString,
122
- default: () => "42",
123
- }),
124
- positionalVariadics({
125
- label: "VARIADIC",
126
- description: "Variadic positionals strings",
127
- type: typeString,
128
- }),
102
+ description: "Subcommand 1 description",
103
+ details: [
104
+ "Subcommand 1 details.",
105
+ "Subcommand 1 second line of details.",
106
+ ],
107
+ examples: [
108
+ {
109
+ explanation: "Example usage of subcommand 1",
110
+ commandArgs: [
111
+ { option: { short: "b" } },
112
+ { positional: "42" },
113
+ { positional: "43" },
114
+ { subcommand: "sub1" },
115
+ { positional: "valid" },
116
+ ],
117
+ },
129
118
  ],
130
119
  },
131
- async (context, inputs) => {
132
- return { at: "sub2", context, inputs };
120
+ operation(
121
+ {
122
+ options: {},
123
+ positionals: [
124
+ positionalRequired({
125
+ label: "POS-STRING",
126
+ description: "Required positional string",
127
+ type: typeString,
128
+ }),
129
+ ],
130
+ },
131
+ async function (context, inputs) {
132
+ return { at: "sub1", context, inputs };
133
+ },
134
+ ),
135
+ ),
136
+ sub2: command(
137
+ {
138
+ description: "Subcommand 2 description",
139
+ hint: "Subcommand 2 hint",
140
+ details: [
141
+ "Subcommand 2 details.",
142
+ "Subcommand 2 second line of details.",
143
+ ],
144
+ examples: [
145
+ {
146
+ explanation: "Example usage of subcommand 2",
147
+ commandArgs: [
148
+ { positional: "40" },
149
+ { positional: "41" },
150
+ { subcommand: "sub2" },
151
+ { option: { long: "dudu", value: "hello" } },
152
+ { positional: "50" },
153
+ ],
154
+ },
155
+ ],
133
156
  },
157
+ operation(
158
+ {
159
+ options: {
160
+ duduValue: optionSingleValue({
161
+ long: "dudu",
162
+ type: typeString,
163
+ default: () => "duduDefault",
164
+ hint: "Dudu option hint",
165
+ description: "Dudu option description",
166
+ }),
167
+ },
168
+ positionals: [
169
+ positionalRequired({
170
+ label: "POS-NUMBER",
171
+ description: "Required positional number",
172
+ type: typeNumber,
173
+ }),
174
+ positionalOptional({
175
+ label: "OPT-POS",
176
+ description: "Optional positional string",
177
+ hint: "Optional positional hint",
178
+ type: typeString,
179
+ default: () => "42",
180
+ }),
181
+ positionalVariadics({
182
+ label: "VARIADIC",
183
+ description: "Variadic positionals strings",
184
+ type: typeString,
185
+ }),
186
+ ],
187
+ },
188
+ async function (context, inputs) {
189
+ return { at: "sub2", context, inputs };
190
+ },
191
+ ),
134
192
  ),
135
- ),
136
- },
193
+ },
194
+ ),
137
195
  );
138
196
 
139
- it("run", async () => {
140
- const usage1 = await getUsage([], cmd);
141
- const usage2 = await getUsage(["50", "51", "sub1"], cmd);
142
- const usage3 = await getUsage(["40", "41", "sub2", "--doesn't-exist"], cmd);
143
-
144
- /*
145
- console.log(usage1.join("\n"));
146
- console.log(usage2.join("\n"));
147
- console.log(usage3.join("\n"));
148
- */
197
+ it("run", async function () {
198
+ const usage1 = await getUsage([], rootCommand);
199
+ const usage2 = await getUsage(["50"], rootCommand);
200
+ const usage3 = await getUsage(["50", "51"], rootCommand);
201
+ const usage4 = await getUsage(["50", "51", "sub1"], rootCommand);
202
+ const usage5 = await getUsage(["50", "51", "sub1", "valid"], rootCommand);
203
+ const usage6 = await getUsage(
204
+ ["40", "41", "sub2", "--doesn't-exist"],
205
+ rootCommand,
206
+ );
207
+ const usage7 = await getUsage(
208
+ ["40", "41", "sub2", "not-a-number"],
209
+ rootCommand,
210
+ );
149
211
 
150
- expect(usage1).toStrictEqual([
151
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{<SUBCOMMAND>}@darkBlue}+",
212
+ const usageRoot = [
213
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{[REST]...}@darkBlue}+",
152
214
  "",
153
215
  "{Root command description}+",
154
216
  "{{Root command details.}-}*",
155
- "{{Second line of root command details.}-}*",
217
+ "{{Root second line of command details.}-}*",
218
+ "",
219
+ "{{Positionals:}@darkGreen}+",
220
+ " {{<POS-1>}@darkBlue}+ Required positional number 1",
221
+ "",
222
+ "{{Options:}@darkGreen}+",
223
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
224
+ "",
225
+ "{{Examples:}@darkGreen}+",
226
+ " {{# Example usage of the root command}-}*",
227
+ " {{my-cli}@darkCyan}+ {{42}@darkBlue}+ {{-b}@darkCyan}+",
228
+ "",
229
+ ];
230
+ const usageMid = [
231
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{<SUBCOMMAND>}@darkBlue}+",
232
+ "",
233
+ "{Mid command description}+",
234
+ "{{Mid command details.}-}*",
235
+ "{{Mid second line of command details.}-}*",
156
236
  "",
157
237
  "{{Positionals:}@darkGreen}+",
158
238
  " {{<POS-1>}@darkBlue}+ Required positional number 1",
@@ -163,17 +243,21 @@ it("run", async () => {
163
243
  " {{sub2}@darkCyan}+ Subcommand 2 description {{(Subcommand 2 hint)}-}*",
164
244
  "",
165
245
  "{{Options:}@darkGreen}+",
166
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* Root boolean-flag description",
167
- " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ Root string-option description",
168
- " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ Root complex-option description",
246
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
247
+ " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ string-option description",
248
+ " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+{{ [*]}-}* complex-option description",
169
249
  "",
170
- ]);
171
- expect(usage2).toStrictEqual([
250
+ "{{Examples:}@darkGreen}+",
251
+ " {{# Example usage of the mid command}-}*",
252
+ " {{my-cli}@darkCyan}+ {{42}@darkBlue}+ {{-b}@darkCyan}+ {{43}@darkBlue}+",
253
+ "",
254
+ ];
255
+ const usageSub1 = [
172
256
  "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub1}@darkCyan}+ {{<POS-STRING>}@darkBlue}+",
173
257
  "",
174
258
  "{Subcommand 1 description}+",
175
259
  "{{Subcommand 1 details.}-}*",
176
- "{{Second line of subcommand 1 details.}-}*",
260
+ "{{Subcommand 1 second line of details.}-}*",
177
261
  "",
178
262
  "{{Positionals:}@darkGreen}+",
179
263
  " {{<POS-1>}@darkBlue}+ Required positional number 1",
@@ -181,17 +265,21 @@ it("run", async () => {
181
265
  " {{<POS-STRING>}@darkBlue}+ Required positional string",
182
266
  "",
183
267
  "{{Options:}@darkGreen}+",
184
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* Root boolean-flag description",
185
- " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ Root string-option description",
186
- " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ Root complex-option description",
268
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
269
+ " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ string-option description",
270
+ " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+{{ [*]}-}* complex-option description",
271
+ "",
272
+ "{{Examples:}@darkGreen}+",
273
+ " {{# Example usage of subcommand 1}-}*",
274
+ " {{my-cli}@darkCyan}+ {{-b}@darkCyan}+ {{42}@darkBlue}+ {{43}@darkBlue}+ {{sub1}@darkCyan}+ {{valid}@darkBlue}+",
187
275
  "",
188
- ]);
189
- expect(usage3).toStrictEqual([
276
+ ];
277
+ const usageSub2 = [
190
278
  "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub2}@darkCyan}+ {{<POS-NUMBER>}@darkBlue}+ {{[OPT-POS]}@darkBlue}+ {{[VARIADIC]...}@darkBlue}+",
191
279
  "",
192
280
  "{Subcommand 2 description}+ {{(Subcommand 2 hint)}-}*",
193
281
  "{{Subcommand 2 details.}-}*",
194
- "{{Second line of subcommand 2 details.}-}*",
282
+ "{{Subcommand 2 second line of details.}-}*",
195
283
  "",
196
284
  "{{Positionals:}@darkGreen}+",
197
285
  " {{<POS-1>}@darkBlue}+ Required positional number 1",
@@ -201,12 +289,34 @@ it("run", async () => {
201
289
  " {{[VARIADIC]...}@darkBlue}+ Variadic positionals strings",
202
290
  "",
203
291
  "{{Options:}@darkGreen}+",
204
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* Root boolean-flag description",
205
- " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ Root string-option description",
206
- " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ Root complex-option description",
207
- " {{--dudu}@darkCyan}+ {{<STRING>}@darkBlue}+ Dudu option description {{(Dudu option hint)}-}*",
292
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
293
+ " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ string-option description",
294
+ " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+{{ [*]}-}* complex-option description",
295
+ " {{--dudu}@darkCyan}+ {{<STRING>}@darkBlue}+ Dudu option description {{(Dudu option hint)}-}*",
208
296
  "",
209
- ]);
297
+ "{{Examples:}@darkGreen}+",
298
+ " {{# Example usage of subcommand 2}-}*",
299
+ " {{my-cli}@darkCyan}+ {{40}@darkBlue}+ {{41}@darkBlue}+ {{sub2}@darkCyan}+ {{--dudu}@darkCyan}+{{=}-}*{{hello}@darkBlue}+ {{50}@darkBlue}+",
300
+ "",
301
+ ];
302
+
303
+ /*
304
+ console.log(usage1.join("\n"));
305
+ console.log(usage2.join("\n"));
306
+ console.log(usage3.join("\n"));
307
+ console.log(usage4.join("\n"));
308
+ console.log(usage5.join("\n"));
309
+ console.log(usage6.join("\n"));
310
+ console.log(usage7.join("\n"));
311
+ */
312
+
313
+ expect(usage1).toStrictEqual(usageRoot);
314
+ expect(usage2).toStrictEqual(usageMid);
315
+ expect(usage3).toStrictEqual(usageMid);
316
+ expect(usage4).toStrictEqual(usageSub1);
317
+ expect(usage5).toStrictEqual(usageSub1);
318
+ expect(usage6).toStrictEqual(usageSub2);
319
+ expect(usage7).toStrictEqual(usageSub2);
210
320
  });
211
321
 
212
322
  async function getUsage<Context, Result>(
@@ -214,10 +324,21 @@ async function getUsage<Context, Result>(
214
324
  command: Command<Context, Result>,
215
325
  ) {
216
326
  const readerArgs = new ReaderArgs(args);
217
- const commandFactory = command.createFactory(readerArgs);
327
+ const commandDecoder = command.consumeAndMakeDecoder(readerArgs);
328
+ /*
329
+ try {
330
+ const interpreter = commandDecoder.decodeAndMakeInterpreter();
331
+ const result = await interpreter.executeWithContext(
332
+ "" as unknown as Context,
333
+ );
334
+ console.log(result);
335
+ } catch (error) {
336
+ console.log(TypoSupport.tty().computeStyledErrorMessage(error));
337
+ }
338
+ */
218
339
  return usageToStyledLines({
219
340
  cliName: "my-cli",
220
- commandUsage: commandFactory.generateUsage(),
341
+ usage: commandDecoder.generateUsage(),
221
342
  typoSupport: TypoSupport.mock(),
222
343
  });
223
344
  }
@@ -10,16 +10,13 @@ import {
10
10
  positionalRequired,
11
11
  positionalVariadics,
12
12
  runAndExit,
13
- typeConverted,
13
+ typeMapped,
14
14
  typeOneOf,
15
15
  typeString,
16
16
  typeUrl,
17
17
  } from "../src";
18
18
 
19
- // TODO - unit test for chained commands
20
- // TODO - unit test for errors styling
21
-
22
- it("run", async () => {
19
+ it("run", async function () {
23
20
  const rootUsage = [
24
21
  "Usage: my-cli <REQUIRED1> <SUBCOMMAND>",
25
22
  "",
@@ -33,7 +30,7 @@ it("run", async () => {
33
30
  "",
34
31
  "Options:",
35
32
  " --flag[=no] Option flag description",
36
- " --repeatable <STRING> Option repeatable description",
33
+ " --repeatable <STRING> [*] Option repeatable description",
37
34
  " --single-value <NUMBER-ENUM> Option single value description",
38
35
  "",
39
36
  ].join("\n");
@@ -50,9 +47,9 @@ it("run", async () => {
50
47
  "",
51
48
  "Options:",
52
49
  " --flag[=no] Option flag description",
53
- " --repeatable <STRING> Option repeatable description",
50
+ " --repeatable <STRING> [*] Option repeatable description",
54
51
  " --single-value <NUMBER-ENUM> Option single value description",
55
- " --url <URL> Option url description",
52
+ " --url <URL> [*] Option url description",
56
53
  "",
57
54
  ].join("\n");
58
55
 
@@ -104,7 +101,7 @@ it("run", async () => {
104
101
  await testCase(
105
102
  ["--invalid1", "--invalid2", "required1", "--invalid3"],
106
103
  [],
107
- [rootUsage, "Error: --invalid1: Unexpected unknown option"],
104
+ [rootUsage, "Error: Unexpected unknown option: --invalid1"],
108
105
  1,
109
106
  );
110
107
  await testCase(
@@ -138,13 +135,13 @@ it("run", async () => {
138
135
  await testCase(
139
136
  ["--url", "https://example.com"],
140
137
  [],
141
- [rootUsage, "Error: --url: Unexpected unknown option"],
138
+ [rootUsage, "Error: Unexpected unknown option: --url"],
142
139
  1,
143
140
  );
144
141
  await testCase(
145
142
  ["required1", "--url", "https://example.com"],
146
143
  [],
147
- [rootUsage, "Error: --url: Unexpected unknown option"],
144
+ [rootUsage, "Error: Unexpected unknown option: --url"],
148
145
  1,
149
146
  );
150
147
  await testCase(
@@ -190,13 +187,13 @@ it("run", async () => {
190
187
  await testCase(
191
188
  ["--invalid", "required1", "subcommand", "required2"],
192
189
  [],
193
- [rootUsage, "Error: --invalid: Unexpected unknown option"],
190
+ [rootUsage, "Error: Unexpected unknown option: --invalid"],
194
191
  1,
195
192
  );
196
193
  await testCase(
197
194
  ["required1", "subcommand", "required2", "--nope"],
198
195
  [],
199
- [subcommandUsage, "Error: --nope: Unexpected unknown option"],
196
+ [subcommandUsage, "Error: Unexpected unknown option: --nope"],
200
197
  1,
201
198
  );
202
199
  await testCase(
@@ -218,7 +215,31 @@ it("run", async () => {
218
215
  1,
219
216
  );
220
217
 
221
- // Test invalid positional type value
218
+ // Test invalid positional type value (should keep parsing best-effort even on invalid values)
219
+ await testCase(
220
+ ["invalid"],
221
+ [],
222
+ [rootUsage, "Error: <SUBCOMMAND>: Is required, but was not provided"],
223
+ 1,
224
+ );
225
+ await testCase(
226
+ ["invalid", "subcommand"],
227
+ [],
228
+ [
229
+ subcommandUsage,
230
+ 'Error: <REQUIRED1>: STRING-ENUM: Invalid value: "invalid" (expected one of: "required1" | "required1-bis")',
231
+ ],
232
+ 1,
233
+ );
234
+ await testCase(
235
+ ["invalid", "subcommand", "required2"],
236
+ [],
237
+ [
238
+ subcommandUsage,
239
+ 'Error: <REQUIRED1>: STRING-ENUM: Invalid value: "invalid" (expected one of: "required1" | "required1-bis")',
240
+ ],
241
+ 1,
242
+ );
222
243
  await testCase(
223
244
  ["required1", "subcommand", "invalid"],
224
245
  [],
@@ -228,6 +249,15 @@ it("run", async () => {
228
249
  ],
229
250
  1,
230
251
  );
252
+ await testCase(
253
+ ["invalid", "subcommand", "invalid"],
254
+ [],
255
+ [
256
+ subcommandUsage,
257
+ 'Error: <REQUIRED1>: STRING-ENUM: Invalid value: "invalid" (expected one of: "required1" | "required1-bis")',
258
+ ],
259
+ 1,
260
+ );
231
261
 
232
262
  // Test root command option invalid values (must not block parsing)
233
263
  await testCase(
@@ -253,7 +283,7 @@ it("run", async () => {
253
283
  await testCase(
254
284
  ["--url", "not-a-url", "required1", "subcommand", "required2"],
255
285
  [],
256
- [rootUsage, "Error: --url: Unexpected unknown option"],
286
+ [rootUsage, "Error: Unexpected unknown option: --url"],
257
287
  1,
258
288
  );
259
289
  await testCase(
@@ -326,7 +356,7 @@ async function testCase(
326
356
  }),
327
357
  optionSingleValue: optionSingleValue({
328
358
  long: "single-value",
329
- type: typeConverted(typeOneOf("STRING-ENUM", ["42", "43"]), {
359
+ type: typeMapped(typeOneOf("STRING-ENUM", ["42", "43"]), {
330
360
  content: "NUMBER-ENUM",
331
361
  decoder: (value) => Number(value),
332
362
  }),
@@ -336,13 +366,13 @@ async function testCase(
336
366
  },
337
367
  positionals: [
338
368
  positionalRequired({
339
- type: typeString,
369
+ type: typeOneOf("STRING-ENUM", ["required1", "required1-bis"]),
340
370
  label: "REQUIRED1",
341
371
  description: "Required1 positional description",
342
372
  }),
343
373
  ],
344
374
  },
345
- async () => {
375
+ async function () {
346
376
  console.log("Has executed root command");
347
377
  },
348
378
  ),
@@ -377,7 +407,7 @@ async function testCase(
377
407
  }),
378
408
  ],
379
409
  },
380
- async () => {
410
+ async function () {
381
411
  console.log("Has executed subcommand");
382
412
  },
383
413
  ),
@@ -405,7 +435,7 @@ function makeMocked<P, R>(returns: Array<R>) {
405
435
  const history = new Array<P>();
406
436
  return {
407
437
  history,
408
- call: (p: P) => {
438
+ call(p: P) {
409
439
  history.push(p);
410
440
  if (history.length > returns.length) {
411
441
  throw new Error(
@@ -10,15 +10,31 @@ import {
10
10
  typeUrl,
11
11
  } from "../src";
12
12
 
13
- it("run", async () => {
13
+ it("run", async function () {
14
14
  await testCase(
15
15
  ["hello"],
16
16
  '{{Error:}@darkRed}+ Unexpected argument: {{"hello"}@darkYellow}+',
17
17
  );
18
+ await testCase(
19
+ ["--nope"],
20
+ "{{Error:}@darkRed}+ Unexpected unknown option: {{--nope}@darkYellow}+",
21
+ );
18
22
  await testCase(
19
23
  ["--flag", "--flag"],
20
24
  "{{Error:}@darkRed}+ {{--flag}@darkCyan}+: Must not be set multiple times",
21
25
  );
26
+ await testCase(
27
+ ["--flag", "--no-flag"],
28
+ "{{Error:}@darkRed}+ {{--flag}@darkCyan}+: Must not be set in combination with: {{--no-flag}@darkCyan}+",
29
+ );
30
+ await testCase(
31
+ ["--no-flag", "--no-flag"],
32
+ "{{Error:}@darkRed}+ {{--no-flag}@darkCyan}+: Must not be set multiple times",
33
+ );
34
+ await testCase(
35
+ ["--single-value=a", "--single-value=b"],
36
+ "{{Error:}@darkRed}+ {{--single-value}@darkCyan}+: Requires a single value, but got multiple",
37
+ );
22
38
  await testCase(
23
39
  ["--flag=invalid"],
24
40
  '{{Error:}@darkRed}+ {{--flag}@darkCyan}+: {{<BOOLEAN>}@darkBlue}+: {{Boolean}@darkMagenta}+: Invalid value: {{"invalid"}@darkYellow}+',
@@ -59,7 +75,7 @@ async function testCase(args: Array<string>, error: string) {
59
75
  },
60
76
  positionals: [],
61
77
  },
62
- async () => {},
78
+ async function () {},
63
79
  ),
64
80
  );
65
81
  console.log = onLogStdOut.call;
@@ -79,7 +95,7 @@ function makeMocked<P, R>(returns: Array<R>) {
79
95
  const history = new Array<P>();
80
96
  return {
81
97
  history,
82
- call: (p: P) => {
98
+ call(p: P) {
83
99
  history.push(p);
84
100
  if (history.length > returns.length) {
85
101
  throw new Error(