cli-kiss 0.2.3 → 0.2.5

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.
@@ -12,9 +12,10 @@ import {
12
12
  positionalRequired,
13
13
  positionalVariadics,
14
14
  ReaderArgs,
15
+ type,
16
+ typeChoice,
15
17
  typeList,
16
18
  typeNumber,
17
- typeString,
18
19
  typeTuple,
19
20
  TypoSupport,
20
21
  usageToStyledLines,
@@ -34,6 +35,13 @@ const rootCommand = commandChained<any, any, any>(
34
35
  operation(
35
36
  {
36
37
  options: {
38
+ choiceOption: optionSingleValue({
39
+ long: "choice-option",
40
+ type: typeChoice("choice", ["unset", "empty", "choice1", "choice2"]),
41
+ description: "choice-option description",
42
+ defaultWhenNotInlined: () => "empty",
43
+ defaultWhenNotDefined: () => "unset",
44
+ }),
37
45
  booleanFlag: optionFlag({
38
46
  short: "b",
39
47
  long: "boolean-flag",
@@ -42,13 +50,12 @@ const rootCommand = commandChained<any, any, any>(
42
50
  },
43
51
  positionals: [
44
52
  positionalRequired({
45
- label: "POS-1",
46
53
  description: "Required positional number 1",
47
- type: typeNumber,
54
+ type: typeNumber("pos-1"),
48
55
  }),
49
56
  ],
50
57
  },
51
- async (context, inputs) => {
58
+ async function (context, inputs) {
52
59
  return { at: "root", context, inputs };
53
60
  },
54
61
  ),
@@ -73,26 +80,24 @@ const rootCommand = commandChained<any, any, any>(
73
80
  stringOption: optionSingleValue({
74
81
  short: "s",
75
82
  long: "string-option",
76
- type: typeString,
77
- default: () => undefined,
78
- label: "COOL-STUFF",
83
+ type: type("cool-stuff"),
84
+ defaultWhenNotDefined: () => undefined,
79
85
  description: "string-option description",
80
86
  }),
81
87
  complexOption: optionRepeatable({
82
88
  long: "complex-option",
83
- type: typeTuple([typeNumber, typeList(typeString)]),
89
+ type: typeTuple([typeNumber(), typeList(type("string"))]),
84
90
  description: "complex-option description",
85
91
  }),
86
92
  },
87
93
  positionals: [
88
94
  positionalRequired({
89
- label: "POS-2",
90
95
  description: "Required positional number 2",
91
- type: typeNumber,
96
+ type: typeNumber("pos-2"),
92
97
  }),
93
98
  ],
94
99
  },
95
- async (context, inputs) => {
100
+ async function (context, inputs) {
96
101
  return { at: "root", context, inputs };
97
102
  },
98
103
  ),
@@ -122,13 +127,12 @@ const rootCommand = commandChained<any, any, any>(
122
127
  options: {},
123
128
  positionals: [
124
129
  positionalRequired({
125
- label: "POS-STRING",
126
130
  description: "Required positional string",
127
- type: typeString,
131
+ type: type("pos-3.1"),
128
132
  }),
129
133
  ],
130
134
  },
131
- async (context, inputs) => {
135
+ async function (context, inputs) {
132
136
  return { at: "sub1", context, inputs };
133
137
  },
134
138
  ),
@@ -148,7 +152,17 @@ const rootCommand = commandChained<any, any, any>(
148
152
  { positional: "40" },
149
153
  { positional: "41" },
150
154
  { subcommand: "sub2" },
151
- { option: { long: "dudu", value: "hello" } },
155
+ { option: { long: "dudu", inlined: "hello" } },
156
+ { positional: "50" },
157
+ ],
158
+ },
159
+ {
160
+ explanation: "Example usage of subcommand 2",
161
+ commandArgs: [
162
+ { positional: "40" },
163
+ { positional: "41" },
164
+ { subcommand: "sub2" },
165
+ { option: { long: "dudu", separated: ["hello"] } },
152
166
  { positional: "50" },
153
167
  ],
154
168
  },
@@ -159,33 +173,30 @@ const rootCommand = commandChained<any, any, any>(
159
173
  options: {
160
174
  duduValue: optionSingleValue({
161
175
  long: "dudu",
162
- type: typeString,
163
- default: () => "duduDefault",
176
+ type: type("dudu-value"),
177
+ defaultWhenNotDefined: () => "duduDefault",
164
178
  hint: "Dudu option hint",
165
179
  description: "Dudu option description",
166
180
  }),
167
181
  },
168
182
  positionals: [
169
183
  positionalRequired({
170
- label: "POS-NUMBER",
171
184
  description: "Required positional number",
172
- type: typeNumber,
185
+ type: typeNumber("pos-3.2"),
173
186
  }),
174
187
  positionalOptional({
175
- label: "OPT-POS",
176
188
  description: "Optional positional string",
177
189
  hint: "Optional positional hint",
178
- type: typeString,
190
+ type: type("pos-4"),
179
191
  default: () => "42",
180
192
  }),
181
193
  positionalVariadics({
182
- label: "VARIADIC",
183
194
  description: "Variadic positionals strings",
184
- type: typeString,
195
+ type: type("pos-5"),
185
196
  }),
186
197
  ],
187
198
  },
188
- async (context, inputs) => {
199
+ async function (context, inputs) {
189
200
  return { at: "sub2", context, inputs };
190
201
  },
191
202
  ),
@@ -194,7 +205,7 @@ const rootCommand = commandChained<any, any, any>(
194
205
  ),
195
206
  );
196
207
 
197
- it("run", async () => {
208
+ it("run", async function () {
198
209
  const usage1 = await getUsage([], rootCommand);
199
210
  const usage2 = await getUsage(["50"], rootCommand);
200
211
  const usage3 = await getUsage(["50", "51"], rootCommand);
@@ -209,107 +220,113 @@ it("run", async () => {
209
220
  rootCommand,
210
221
  );
211
222
 
212
- console.log(usage1.join("\n"));
213
- console.log(usage2.join("\n"));
214
- console.log(usage3.join("\n"));
215
- console.log(usage4.join("\n"));
216
- console.log(usage5.join("\n"));
217
- console.log(usage6.join("\n"));
218
- console.log(usage7.join("\n"));
219
- /*
220
- */
221
-
222
223
  const usageRoot = [
223
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{[REST]...}@darkBlue}+",
224
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<pos-1>}@darkBlue}+ {{[REST]...}@darkBlue}+",
224
225
  "",
225
226
  "{Root command description}+",
226
227
  "{{Root command details.}-}*",
227
228
  "{{Root second line of command details.}-}*",
228
229
  "",
229
230
  "{{Positionals:}@darkGreen}+",
230
- " {{<POS-1>}@darkBlue}+ Required positional number 1",
231
+ " {{<pos-1>}@darkBlue}+ Required positional number 1",
231
232
  "",
232
233
  "{{Options:}@darkGreen}+",
233
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
234
+ " {{--choice-option}@darkCyan}+ {{<choice>}@darkBlue}+ choice-option description",
235
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
234
236
  "",
235
237
  "{{Examples:}@darkGreen}+",
236
- " {{# Example usage of the root command}-}*",
237
- " {{my-cli}@darkCyan}+ {{42}@darkBlue}+ {{-b}@darkCyan}+",
238
+ " {{# Example usage of the root command}-}*",
239
+ " {{my-cli}@darkCyan}+ {{42}@darkBlue}+ {{-b}@darkCyan}+",
238
240
  "",
239
241
  ];
240
242
  const usageMid = [
241
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{<SUBCOMMAND>}@darkBlue}+",
243
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<pos-1>}@darkBlue}+ {{<pos-2>}@darkBlue}+ {{<subcommand>}@darkBlue}+",
242
244
  "",
243
245
  "{Mid command description}+",
244
246
  "{{Mid command details.}-}*",
245
247
  "{{Mid second line of command details.}-}*",
246
248
  "",
247
249
  "{{Positionals:}@darkGreen}+",
248
- " {{<POS-1>}@darkBlue}+ Required positional number 1",
249
- " {{<POS-2>}@darkBlue}+ Required positional number 2",
250
+ " {{<pos-1>}@darkBlue}+ Required positional number 1",
251
+ " {{<pos-2>}@darkBlue}+ Required positional number 2",
250
252
  "",
251
253
  "{{Subcommands:}@darkGreen}+",
252
254
  " {{sub1}@darkCyan}+ Subcommand 1 description",
253
255
  " {{sub2}@darkCyan}+ Subcommand 2 description {{(Subcommand 2 hint)}-}*",
254
256
  "",
255
257
  "{{Options:}@darkGreen}+",
256
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
257
- " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ string-option description",
258
- " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ complex-option description",
258
+ " {{--choice-option}@darkCyan}+ {{<choice>}@darkBlue}+ choice-option description",
259
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
260
+ " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<cool-stuff>}@darkBlue}+ string-option description",
261
+ " {{--complex-option}@darkCyan}+ {{<number,string[,string]...>}@darkBlue}+{{ [*]}-}* complex-option description",
259
262
  "",
260
263
  "{{Examples:}@darkGreen}+",
261
- " {{# Example usage of the mid command}-}*",
262
- " {{my-cli}@darkCyan}+ {{42}@darkBlue}+ {{-b}@darkCyan}+ {{43}@darkBlue}+",
264
+ " {{# Example usage of the mid command}-}*",
265
+ " {{my-cli}@darkCyan}+ {{42}@darkBlue}+ {{-b}@darkCyan}+ {{43}@darkBlue}+",
263
266
  "",
264
267
  ];
265
268
  const usageSub1 = [
266
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub1}@darkCyan}+ {{<POS-STRING>}@darkBlue}+",
269
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<pos-1>}@darkBlue}+ {{<pos-2>}@darkBlue}+ {{sub1}@darkCyan}+ {{<pos-3.1>}@darkBlue}+",
267
270
  "",
268
271
  "{Subcommand 1 description}+",
269
272
  "{{Subcommand 1 details.}-}*",
270
273
  "{{Subcommand 1 second line of details.}-}*",
271
274
  "",
272
275
  "{{Positionals:}@darkGreen}+",
273
- " {{<POS-1>}@darkBlue}+ Required positional number 1",
274
- " {{<POS-2>}@darkBlue}+ Required positional number 2",
275
- " {{<POS-STRING>}@darkBlue}+ Required positional string",
276
+ " {{<pos-1>}@darkBlue}+ Required positional number 1",
277
+ " {{<pos-2>}@darkBlue}+ Required positional number 2",
278
+ " {{<pos-3.1>}@darkBlue}+ Required positional string",
276
279
  "",
277
280
  "{{Options:}@darkGreen}+",
278
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
279
- " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ string-option description",
280
- " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ complex-option description",
281
+ " {{--choice-option}@darkCyan}+ {{<choice>}@darkBlue}+ choice-option description",
282
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
283
+ " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<cool-stuff>}@darkBlue}+ string-option description",
284
+ " {{--complex-option}@darkCyan}+ {{<number,string[,string]...>}@darkBlue}+{{ [*]}-}* complex-option description",
281
285
  "",
282
286
  "{{Examples:}@darkGreen}+",
283
- " {{# Example usage of subcommand 1}-}*",
284
- " {{my-cli}@darkCyan}+ {{-b}@darkCyan}+ {{42}@darkBlue}+ {{43}@darkBlue}+ {{sub1}@darkCyan}+ {{valid}@darkBlue}+",
287
+ " {{# Example usage of subcommand 1}-}*",
288
+ " {{my-cli}@darkCyan}+ {{-b}@darkCyan}+ {{42}@darkBlue}+ {{43}@darkBlue}+ {{sub1}@darkCyan}+ {{valid}@darkBlue}+",
285
289
  "",
286
290
  ];
287
291
  const usageSub2 = [
288
- "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<POS-1>}@darkBlue}+ {{<POS-2>}@darkBlue}+ {{sub2}@darkCyan}+ {{<POS-NUMBER>}@darkBlue}+ {{[OPT-POS]}@darkBlue}+ {{[VARIADIC]...}@darkBlue}+",
292
+ "{{Usage:}@darkMagenta}+ {{my-cli}@darkCyan}+ {{<pos-1>}@darkBlue}+ {{<pos-2>}@darkBlue}+ {{sub2}@darkCyan}+ {{<pos-3.2>}@darkBlue}+ {{[pos-4]}@darkBlue}+ {{[pos-5]...}@darkBlue}+",
289
293
  "",
290
294
  "{Subcommand 2 description}+ {{(Subcommand 2 hint)}-}*",
291
295
  "{{Subcommand 2 details.}-}*",
292
296
  "{{Subcommand 2 second line of details.}-}*",
293
297
  "",
294
298
  "{{Positionals:}@darkGreen}+",
295
- " {{<POS-1>}@darkBlue}+ Required positional number 1",
296
- " {{<POS-2>}@darkBlue}+ Required positional number 2",
297
- " {{<POS-NUMBER>}@darkBlue}+ Required positional number",
298
- " {{[OPT-POS]}@darkBlue}+ Optional positional string {{(Optional positional hint)}-}*",
299
- " {{[VARIADIC]...}@darkBlue}+ Variadic positionals strings",
299
+ " {{<pos-1>}@darkBlue}+ Required positional number 1",
300
+ " {{<pos-2>}@darkBlue}+ Required positional number 2",
301
+ " {{<pos-3.2>}@darkBlue}+ Required positional number",
302
+ " {{[pos-4]}@darkBlue}+ Optional positional string {{(Optional positional hint)}-}*",
303
+ " {{[pos-5]...}@darkBlue}+ Variadic positionals strings",
300
304
  "",
301
305
  "{{Options:}@darkGreen}+",
302
- " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
303
- " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<COOL-STUFF>}@darkBlue}+ string-option description",
304
- " {{--complex-option}@darkCyan}+ {{<NUMBER,STRING[,STRING]...>}@darkBlue}+ complex-option description",
305
- " {{--dudu}@darkCyan}+ {{<STRING>}@darkBlue}+ Dudu option description {{(Dudu option hint)}-}*",
306
+ " {{--choice-option}@darkCyan}+ {{<choice>}@darkBlue}+ choice-option description",
307
+ " {{-b}@darkCyan}+, {{--boolean-flag}@darkCyan}+{{[=no]}-}* boolean-flag description",
308
+ " {{-s}@darkCyan}+, {{--string-option}@darkCyan}+ {{<cool-stuff>}@darkBlue}+ string-option description",
309
+ " {{--complex-option}@darkCyan}+ {{<number,string[,string]...>}@darkBlue}+{{ [*]}-}* complex-option description",
310
+ " {{--dudu}@darkCyan}+ {{<dudu-value>}@darkBlue}+ Dudu option description {{(Dudu option hint)}-}*",
306
311
  "",
307
312
  "{{Examples:}@darkGreen}+",
308
- " {{# Example usage of subcommand 2}-}*",
309
- " {{my-cli}@darkCyan}+ {{40}@darkBlue}+ {{41}@darkBlue}+ {{sub2}@darkCyan}+ {{--dudu}@darkCyan}+{{=}-}*{{hello}@darkBlue}+ {{50}@darkBlue}+",
313
+ " {{# Example usage of subcommand 2}-}*",
314
+ " {{my-cli}@darkCyan}+ {{40}@darkBlue}+ {{41}@darkBlue}+ {{sub2}@darkCyan}+ {{--dudu}@darkCyan}+{{=}-}*{{hello}@darkBlue}+ {{50}@darkBlue}+",
315
+ " {{# Example usage of subcommand 2}-}*",
316
+ " {{my-cli}@darkCyan}+ {{40}@darkBlue}+ {{41}@darkBlue}+ {{sub2}@darkCyan}+ {{--dudu}@darkCyan}+ {{hello}@darkBlue}+ {{50}@darkBlue}+",
310
317
  "",
311
318
  ];
312
319
 
320
+ /*
321
+ console.log(usage1.join("\n"));
322
+ console.log(usage2.join("\n"));
323
+ console.log(usage3.join("\n"));
324
+ console.log(usage4.join("\n"));
325
+ console.log(usage5.join("\n"));
326
+ console.log(usage6.join("\n"));
327
+ console.log(usage7.join("\n"));
328
+ */
329
+
313
330
  expect(usage1).toStrictEqual(usageRoot);
314
331
  expect(usage2).toStrictEqual(usageMid);
315
332
  expect(usage3).toStrictEqual(usageMid);
@@ -325,20 +342,9 @@ async function getUsage<Context, Result>(
325
342
  ) {
326
343
  const readerArgs = new ReaderArgs(args);
327
344
  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
- */
339
345
  return usageToStyledLines({
340
346
  cliName: "my-cli",
341
- commandUsage: commandDecoder.generateUsage(),
342
- typoSupport: TypoSupport.tty(),
347
+ usage: commandDecoder.generateUsage(),
348
+ typoSupport: TypoSupport.mock(),
343
349
  });
344
350
  }
@@ -0,0 +1,197 @@
1
+ import { it } from "@jest/globals";
2
+ import {
3
+ command,
4
+ operation,
5
+ optionRepeatable,
6
+ positionalVariadics,
7
+ runAndExit,
8
+ type,
9
+ TypoSupport,
10
+ usageToStyledLines,
11
+ } from "../src";
12
+
13
+ const cliName = "my-cli";
14
+
15
+ it("run", async function () {
16
+ const usage = {
17
+ segments: [{ positional: "[string]..." }],
18
+ information: { description: "Description" },
19
+ subcommands: [],
20
+ options: [{ long: "option", label: "<string>", annotation: " [*]" }],
21
+ positionals: [{ label: "[string]...", description: "Variadics" }],
22
+ };
23
+ const usageNone = usageToStyledLines({
24
+ cliName,
25
+ usage,
26
+ typoSupport: TypoSupport.none(),
27
+ }).join("\n");
28
+ const usageMock = usageToStyledLines({
29
+ cliName,
30
+ usage,
31
+ typoSupport: TypoSupport.mock(),
32
+ }).join("\n");
33
+ const usageTty = usageToStyledLines({
34
+ cliName,
35
+ usage,
36
+ typoSupport: TypoSupport.tty(),
37
+ }).join("\n");
38
+
39
+ await testCase("flag", ["--color=auto", "--help"], [usageTty], [], 0);
40
+ await testCase("flag", ["--color=always", "--help"], [usageTty], [], 0);
41
+ await testCase("flag", ["--color=never", "--help"], [usageNone], [], 0);
42
+ await testCase("flag", ["--color=mock", "--help"], [usageMock], [], 0);
43
+ await testCase("flag", ["--help"], [usageTty], [], 0);
44
+ await testCase("env", ["--help"], [usageTty], [], 0);
45
+ await testCase("always", ["--help"], [usageTty], [], 0);
46
+ await testCase("never", ["--help"], [usageNone], [], 0);
47
+ await testCase("mock", ["--help"], [usageMock], [], 0);
48
+
49
+ process.env["NO_COLOR"] = "";
50
+ await testCase("flag", ["--color=auto", "--help"], [usageNone], [], 0);
51
+ await testCase("flag", ["--color=always", "--help"], [usageTty], [], 0);
52
+ await testCase("flag", ["--color=never", "--help"], [usageNone], [], 0);
53
+ await testCase("flag", ["--color=mock", "--help"], [usageMock], [], 0);
54
+ await testCase("flag", ["--help"], [usageNone], [], 0);
55
+ await testCase("env", ["--help"], [usageNone], [], 0);
56
+ await testCase("always", ["--help"], [usageTty], [], 0);
57
+ await testCase("never", ["--help"], [usageNone], [], 0);
58
+ await testCase("mock", ["--help"], [usageMock], [], 0);
59
+ delete process.env["NO_COLOR"];
60
+
61
+ process.env["FORCE_COLOR"] = "";
62
+ await testCase("flag", ["--color=auto", "--help"], [usageTty], [], 0);
63
+ await testCase("flag", ["--color=always", "--help"], [usageTty], [], 0);
64
+ await testCase("flag", ["--color=never", "--help"], [usageNone], [], 0);
65
+ await testCase("flag", ["--color=mock", "--help"], [usageMock], [], 0);
66
+ await testCase("flag", ["--help"], [usageTty], [], 0);
67
+ await testCase("env", ["--help"], [usageTty], [], 0);
68
+ await testCase("always", ["--help"], [usageTty], [], 0);
69
+ await testCase("never", ["--help"], [usageNone], [], 0);
70
+ await testCase("mock", ["--help"], [usageMock], [], 0);
71
+ delete process.env["FORCE_COLOR"];
72
+
73
+ process.env["FORCE_COLOR"] = "0";
74
+ await testCase("flag", ["--color=auto", "--help"], [usageNone], [], 0);
75
+ await testCase("flag", ["--color=always", "--help"], [usageTty], [], 0);
76
+ await testCase("flag", ["--color=never", "--help"], [usageNone], [], 0);
77
+ await testCase("flag", ["--color=mock", "--help"], [usageMock], [], 0);
78
+ await testCase("flag", ["--help"], [usageNone], [], 0);
79
+ await testCase("env", ["--help"], [usageNone], [], 0);
80
+ await testCase("always", ["--help"], [usageTty], [], 0);
81
+ await testCase("never", ["--help"], [usageNone], [], 0);
82
+ await testCase("mock", ["--help"], [usageMock], [], 0);
83
+ delete process.env["FORCE_COLOR"];
84
+
85
+ process.env["MOCK_COLOR"] = "";
86
+ await testCase("flag", ["--color=auto", "--help"], [usageMock], [], 0);
87
+ await testCase("flag", ["--color=always", "--help"], [usageTty], [], 0);
88
+ await testCase("flag", ["--color=never", "--help"], [usageNone], [], 0);
89
+ await testCase("flag", ["--color=mock", "--help"], [usageMock], [], 0);
90
+ await testCase("flag", ["--help"], [usageMock], [], 0);
91
+ await testCase("env", ["--help"], [usageMock], [], 0);
92
+ await testCase("always", ["--help"], [usageTty], [], 0);
93
+ await testCase("never", ["--help"], [usageNone], [], 0);
94
+ await testCase("mock", ["--help"], [usageMock], [], 0);
95
+ delete process.env["MOCK_COLOR"];
96
+
97
+ process.env["MOCK_COLOR"] = "";
98
+ await testCase(
99
+ "flag",
100
+ ["--color=42"],
101
+ [],
102
+ [
103
+ usageMock,
104
+ '{{Error:}@darkRed}+ {{--color}@darkCyan}+: {{<color-mode>}@darkBlue}+: Invalid value: {{"42"}@darkYellow}+ (expected one of: {{"auto"}@darkYellow}+ | {{"always"}@darkYellow}+ | {{"never"}@darkYellow}+...)',
105
+ ],
106
+ 1,
107
+ );
108
+ delete process.env["MOCK_COLOR"];
109
+
110
+ const unexpected1 = "Error: Unexpected unknown option: --color";
111
+ await testCase("never", ["--color=auto"], [], [usageNone, unexpected1], 1);
112
+ await testCase("never", ["--color=always"], [], [usageNone, unexpected1], 1);
113
+ await testCase("never", ["--color=never"], [], [usageNone, unexpected1], 1);
114
+ await testCase("never", ["--color=mock"], [], [usageNone, unexpected1], 1);
115
+ process.env["FORCE_COLOR"] = "0";
116
+ await testCase("env", ["--color=auto"], [], [usageNone, unexpected1], 1);
117
+ await testCase("env", ["--color=always"], [], [usageNone, unexpected1], 1);
118
+ await testCase("env", ["--color=never"], [], [usageNone, unexpected1], 1);
119
+ await testCase("env", ["--color=mock"], [], [usageNone, unexpected1], 1);
120
+ delete process.env["FORCE_COLOR"];
121
+
122
+ const unexpected2 =
123
+ "{{Error:}@darkRed}+ Unexpected unknown option: {{--color}@darkYellow}+";
124
+ await testCase("mock", ["--color=auto"], [], [usageMock, unexpected2], 1);
125
+ await testCase("mock", ["--color=always"], [], [usageMock, unexpected2], 1);
126
+ await testCase("mock", ["--color=never"], [], [usageMock, unexpected2], 1);
127
+ await testCase("mock", ["--color=mock"], [], [usageMock, unexpected2], 1);
128
+ });
129
+
130
+ async function testCase(
131
+ colorSetup: "flag" | "env" | "always" | "never" | "mock",
132
+ cliArgs: Array<string>,
133
+ expectStdOut: Array<string>,
134
+ expectStdErr: Array<string>,
135
+ expectExit: number,
136
+ ) {
137
+ const onLogStdOut = makeMocked<string, void>([
138
+ null as unknown as void,
139
+ null as unknown as void,
140
+ ]);
141
+ const onLogStdErr = makeMocked<string, void>([
142
+ null as unknown as void,
143
+ null as unknown as void,
144
+ ]);
145
+ const onExit = makeMocked<number, never>([null as never]);
146
+ const cmd = command<null, void>(
147
+ { description: "Description" },
148
+ operation(
149
+ {
150
+ options: {
151
+ value: optionRepeatable({ long: "option", type: type() }),
152
+ },
153
+ positionals: [
154
+ positionalVariadics({ type: type(), description: "Variadics" }),
155
+ ],
156
+ },
157
+ async function (_, { options: { value }, positionals: [rest] }) {
158
+ console.log(
159
+ `Has executed: ${JSON.stringify(value)}, ${JSON.stringify(rest)}`,
160
+ );
161
+ },
162
+ ),
163
+ );
164
+ console.log = onLogStdOut.call;
165
+ console.error = onLogStdErr.call;
166
+ await runAndExit(cliName, cliArgs, null, cmd, {
167
+ buildVersion: "1.0.0",
168
+ onExit: onExit.call,
169
+ colorSetup,
170
+ });
171
+ expect({
172
+ stdOut: onLogStdOut.history,
173
+ stdErr: onLogStdErr.history,
174
+ }).toEqual({
175
+ stdOut: expectStdOut,
176
+ stdErr: expectStdErr,
177
+ });
178
+ expect(onExit.history).toEqual([expectExit]);
179
+ }
180
+
181
+ function makeMocked<P, R>(returns: Array<R>) {
182
+ const history = new Array<P>();
183
+ return {
184
+ history,
185
+ call(p: P) {
186
+ history.push(p);
187
+ if (history.length > returns.length) {
188
+ throw new Error(
189
+ `Mocked function called more times than expected. History: ${JSON.stringify(
190
+ history,
191
+ )}, returns: ${JSON.stringify(returns)}`,
192
+ );
193
+ }
194
+ return returns[history.length - 1]!;
195
+ },
196
+ };
197
+ }