@flowscripter/dynamic-cli-framework 1.0.0

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 (133) hide show
  1. package/.github/workflows/check-bun-dependencies.yml +9 -0
  2. package/.github/workflows/lint-pr-message.yml +11 -0
  3. package/.github/workflows/release-bun-library.yml +8 -0
  4. package/.github/workflows/validate-bun-library-pr.yml +8 -0
  5. package/LICENSE +21 -0
  6. package/README.md +1475 -0
  7. package/bun.lock +63 -0
  8. package/index.ts +89 -0
  9. package/package.json +23 -0
  10. package/src/api/CLI.ts +17 -0
  11. package/src/api/CLIConfig.ts +19 -0
  12. package/src/api/Context.ts +25 -0
  13. package/src/api/RunResult.ts +142 -0
  14. package/src/api/argument/Argument.ts +36 -0
  15. package/src/api/argument/ArgumentValueTypes.ts +87 -0
  16. package/src/api/argument/ComplexOption.ts +31 -0
  17. package/src/api/argument/GlobalCommandArgument.ts +17 -0
  18. package/src/api/argument/Option.ts +32 -0
  19. package/src/api/argument/Positional.ts +27 -0
  20. package/src/api/argument/SubCommandArgument.ts +20 -0
  21. package/src/api/command/Command.ts +22 -0
  22. package/src/api/command/GlobalCommand.ts +33 -0
  23. package/src/api/command/GlobalModifierCommand.ts +12 -0
  24. package/src/api/command/GroupCommand.ts +20 -0
  25. package/src/api/command/SubCommand.ts +41 -0
  26. package/src/api/command/UsageExample.ts +21 -0
  27. package/src/api/service/ServiceProvider.ts +52 -0
  28. package/src/api/service/core/AsciiBannerGeneratorService.ts +35 -0
  29. package/src/api/service/core/KeyValueService.ts +28 -0
  30. package/src/api/service/core/PrinterService.ts +266 -0
  31. package/src/api/service/core/ShutdownService.ts +12 -0
  32. package/src/api/service/core/SyntaxHighlighterService.ts +42 -0
  33. package/src/cli/BaseCLI.ts +312 -0
  34. package/src/cli/DefaultRuntimeCLI.ts +54 -0
  35. package/src/command/MultiCommandCliHelpCommand.ts +394 -0
  36. package/src/command/SingleCommandCliHelpCommand.ts +183 -0
  37. package/src/command/UsageCommand.ts +35 -0
  38. package/src/command/VersionCommand.ts +22 -0
  39. package/src/launcher.ts +127 -0
  40. package/src/runtime/DefaultContext.ts +32 -0
  41. package/src/runtime/argument/ArgumentTypeGuards.ts +8 -0
  42. package/src/runtime/command/CommandTypeGuards.ts +30 -0
  43. package/src/runtime/command/CommandValidator.ts +549 -0
  44. package/src/runtime/parser.ts +219 -0
  45. package/src/runtime/registry/CommandRegistry.ts +121 -0
  46. package/src/runtime/registry/DefaultCommandRegistry.ts +318 -0
  47. package/src/runtime/registry/DefaultServiceProviderRegistry.ts +26 -0
  48. package/src/runtime/registry/ServiceProviderRegistry.ts +11 -0
  49. package/src/runtime/runner.ts +697 -0
  50. package/src/runtime/scanner.ts +417 -0
  51. package/src/runtime/values/ValuePopulationResult.ts +41 -0
  52. package/src/runtime/values/argumentValueMerge.ts +201 -0
  53. package/src/runtime/values/argumentValueValidation.ts +610 -0
  54. package/src/runtime/values/globalCommandValuePopulation.ts +96 -0
  55. package/src/runtime/values/subCommandValuePopulation.ts +680 -0
  56. package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +45 -0
  57. package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +38 -0
  58. package/src/service/asciiBannerGenerator/standard.flf.json +3 -0
  59. package/src/service/banner/BannerServiceProvider.ts +99 -0
  60. package/src/service/banner/command/NoBannerCommand.ts +42 -0
  61. package/src/service/configuration/ConfigurationServiceProvider.ts +571 -0
  62. package/src/service/configuration/DefaultKeyValueService.ts +60 -0
  63. package/src/service/configuration/command/ConfigCommand.ts +43 -0
  64. package/src/service/configuration/command/DumpConfigCommand.ts +47 -0
  65. package/src/service/printer/DefaultPrinterService.ts +256 -0
  66. package/src/service/printer/PrinterServiceProvider.ts +61 -0
  67. package/src/service/printer/command/DarkModeCommand.ts +43 -0
  68. package/src/service/printer/command/LogLevelCommand.ts +68 -0
  69. package/src/service/printer/command/NoColorCommand.ts +43 -0
  70. package/src/service/printer/terminal/Ansi.ts +10 -0
  71. package/src/service/printer/terminal/Color.ts +14 -0
  72. package/src/service/printer/terminal/Progress.ts +373 -0
  73. package/src/service/printer/terminal/Spinner.ts +100 -0
  74. package/src/service/printer/terminal/Styler.ts +5 -0
  75. package/src/service/printer/terminal/Terminal.ts +8 -0
  76. package/src/service/printer/terminal/Theme.ts +34 -0
  77. package/src/service/printer/terminal/TtyStyler.ts +39 -0
  78. package/src/service/printer/terminal/TtyTerminal.ts +65 -0
  79. package/src/service/shutdown/DefaultShutdownService.ts +9 -0
  80. package/src/service/shutdown/ShutdownServiceProvider.ts +55 -0
  81. package/src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts +52 -0
  82. package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +51 -0
  83. package/src/util/configHelper.ts +95 -0
  84. package/src/util/envVarHelper.ts +338 -0
  85. package/src/util/helpHelper.ts +751 -0
  86. package/src/util/logger.ts +84 -0
  87. package/src/util/runnerHelper.ts +112 -0
  88. package/tests/cli/BaseCLI.test.ts +344 -0
  89. package/tests/command/MultiCommandCliHelpCommand.test.ts +650 -0
  90. package/tests/command/SingleCommandCliHelpCommand.test.ts +384 -0
  91. package/tests/command/UsageCommand.test.ts +53 -0
  92. package/tests/command/VersionCommand.test.ts +36 -0
  93. package/tests/fixtures/CLIConfig.ts +9 -0
  94. package/tests/fixtures/Command.ts +229 -0
  95. package/tests/fixtures/CommandRegistry.ts +8 -0
  96. package/tests/fixtures/ConfigurationServiceProvider.ts +28 -0
  97. package/tests/fixtures/Context.ts +33 -0
  98. package/tests/fixtures/ServiceProviderRegistry.ts +16 -0
  99. package/tests/fixtures/StreamString.ts +37 -0
  100. package/tests/fixtures/util.ts +41 -0
  101. package/tests/runtime/DefaultContext.test.ts +20 -0
  102. package/tests/runtime/argument/ArgumentTypeGuards.test.ts +49 -0
  103. package/tests/runtime/command/CommandTypeGuards.test.ts +43 -0
  104. package/tests/runtime/command/CommandValidator.test.ts +816 -0
  105. package/tests/runtime/parser.test.ts +766 -0
  106. package/tests/runtime/registry/DefaultCommandRegistry.test.ts +166 -0
  107. package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +42 -0
  108. package/tests/runtime/runner.test.ts +1298 -0
  109. package/tests/runtime/scanner.test.ts +1113 -0
  110. package/tests/runtime/values/argumentValueMerge.test.ts +314 -0
  111. package/tests/runtime/values/argumentValueValidation.test.ts +1020 -0
  112. package/tests/runtime/values/globalCommandValuePopulation.test.ts +662 -0
  113. package/tests/runtime/values/subCommandValuePopulation.test.ts +2658 -0
  114. package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +22 -0
  115. package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +70 -0
  116. package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +22 -0
  117. package/tests/service/asciiBannerGenerator/small.flf.json +3 -0
  118. package/tests/service/configuration/ConfigurationServiceProvider.test.ts +249 -0
  119. package/tests/service/configuration/DefaultKeyValueService.test.ts +65 -0
  120. package/tests/service/launchBanner/BannerServiceProvider.test.ts +49 -0
  121. package/tests/service/launchBanner/__snapshots__/BannerServiceProvider.test.ts.snap +3 -0
  122. package/tests/service/launchBanner/command/NoBannerCommand.test.ts +21 -0
  123. package/tests/service/printer/DefaultPrinterService.test.ts +409 -0
  124. package/tests/service/printer/PrinterServiceProvider.test.ts +29 -0
  125. package/tests/service/printer/command/LogLevelCommand.test.ts +58 -0
  126. package/tests/service/printer/terminal/Progress.test.ts +40 -0
  127. package/tests/service/printer/terminal/Spinner.test.ts +57 -0
  128. package/tests/service/shutdown/ShutdownServiceProvider.test.ts +13 -0
  129. package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +123 -0
  130. package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +41 -0
  131. package/tests/util/configHelper.test.ts +275 -0
  132. package/tests/util/envVarHelper.test.ts +318 -0
  133. package/tests/util/logger.test.ts +10 -0
@@ -0,0 +1,1113 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ getGlobalCommandWithShortAlias,
4
+ getGlobalModifierCommand,
5
+ getGroupCommand,
6
+ getSubCommandWithComplexOptions,
7
+ getSubCommandWithOptionAndPositional,
8
+ } from "../fixtures/Command.ts";
9
+ import {
10
+ scanForGlobalModifierCommandClauses,
11
+ scanForNonModifierCommandClause,
12
+ type ScanResult,
13
+ } from "../../src/runtime/scanner.ts";
14
+ import type GlobalModifierCommand from "../../src/api/command/GlobalModifierCommand.ts";
15
+ import type GroupCommand from "../../src/api/command/GroupCommand.ts";
16
+ import type SubCommand from "../../src/api/command/SubCommand.ts";
17
+ import type GlobalCommand from "../../src/api/command/GlobalCommand.ts";
18
+ import type Command from "../../src/api/command/Command.ts";
19
+
20
+ function expectScanResult(result: ScanResult, expected: ScanResult) {
21
+ expect(result.unusedArgSequences).toEqual(expected.unusedArgSequences);
22
+ expect(
23
+ result.nonModifierCommandClause,
24
+ ).toEqual(
25
+ expected.nonModifierCommandClause!,
26
+ );
27
+ expect(
28
+ result.globalModifierCommandClauses,
29
+ ).toEqual(
30
+ expected.globalModifierCommandClauses!,
31
+ );
32
+ }
33
+ describe("scanner Tests", () => {
34
+ test("Global modifier command scanned", () => {
35
+ const globalModifierCommand = getGlobalModifierCommand(
36
+ "modifier",
37
+ "m",
38
+ true,
39
+ true,
40
+ );
41
+ const globalModifierCommandsByName: ReadonlyMap<
42
+ string,
43
+ GlobalModifierCommand
44
+ > = new Map([[globalModifierCommand.name, globalModifierCommand]]);
45
+ const globalModifierCommandsByShortAlias: ReadonlyMap<
46
+ string,
47
+ GlobalModifierCommand
48
+ > = new Map([[globalModifierCommand.shortAlias!, globalModifierCommand]]);
49
+
50
+ const expected: ScanResult = {
51
+ globalModifierCommandClauses: [{
52
+ command: globalModifierCommand,
53
+ potentialArgs: ["g", "bar"],
54
+ }],
55
+ unusedArgSequences: [],
56
+ };
57
+
58
+ let scanResult = scanForGlobalModifierCommandClauses(
59
+ [[
60
+ "--modifier",
61
+ "g",
62
+ "bar",
63
+ ]],
64
+ globalModifierCommandsByName,
65
+ globalModifierCommandsByShortAlias,
66
+ );
67
+ expectScanResult(scanResult, expected);
68
+
69
+ scanResult = scanForGlobalModifierCommandClauses(
70
+ [["--modifier=g", "bar"]],
71
+ globalModifierCommandsByName,
72
+ globalModifierCommandsByShortAlias,
73
+ );
74
+ expectScanResult(scanResult, expected);
75
+
76
+ scanResult = scanForGlobalModifierCommandClauses(
77
+ [["-m", "g", "bar"]],
78
+ globalModifierCommandsByName,
79
+ globalModifierCommandsByShortAlias,
80
+ );
81
+ expectScanResult(scanResult, expected);
82
+
83
+ scanResult = scanForGlobalModifierCommandClauses(
84
+ [["-m=g", "bar"]],
85
+ globalModifierCommandsByName,
86
+ globalModifierCommandsByShortAlias,
87
+ );
88
+ expectScanResult(scanResult, expected);
89
+
90
+ scanResult = scanForGlobalModifierCommandClauses(
91
+ [[], [
92
+ "--modifier",
93
+ "g",
94
+ "bar",
95
+ ]],
96
+ globalModifierCommandsByName,
97
+ globalModifierCommandsByShortAlias,
98
+ );
99
+ expectScanResult(scanResult, expected);
100
+
101
+ scanResult = scanForGlobalModifierCommandClauses(
102
+ [[
103
+ "--modifier",
104
+ "g",
105
+ "bar",
106
+ ], []],
107
+ globalModifierCommandsByName,
108
+ globalModifierCommandsByShortAlias,
109
+ );
110
+ expectScanResult(scanResult, expected);
111
+ });
112
+
113
+ test("Two global modifier commands scanned", () => {
114
+ const globalModifierCommand1 = getGlobalModifierCommand(
115
+ "modifier1",
116
+ "1",
117
+ true,
118
+ true,
119
+ );
120
+ const globalModifierCommand2 = getGlobalModifierCommand(
121
+ "modifier2",
122
+ "2",
123
+ true,
124
+ true,
125
+ );
126
+ const globalModifierCommandsByName: ReadonlyMap<
127
+ string,
128
+ GlobalModifierCommand
129
+ > = new Map([
130
+ [globalModifierCommand1.name, globalModifierCommand1],
131
+ [globalModifierCommand2.name, globalModifierCommand2],
132
+ ]);
133
+ const globalModifierCommandsByShortAlias: ReadonlyMap<
134
+ string,
135
+ GlobalModifierCommand
136
+ > = new Map([
137
+ [globalModifierCommand1.shortAlias!, globalModifierCommand1],
138
+ [globalModifierCommand2.shortAlias!, globalModifierCommand2],
139
+ ]);
140
+
141
+ let expected: ScanResult = {
142
+ globalModifierCommandClauses: [{
143
+ command: globalModifierCommand1,
144
+ potentialArgs: ["g", "bar"],
145
+ }, {
146
+ command: globalModifierCommand2,
147
+ potentialArgs: ["g", "bar"],
148
+ }],
149
+ unusedArgSequences: [],
150
+ };
151
+
152
+ let scanResult = scanForGlobalModifierCommandClauses(
153
+ [[
154
+ "--modifier1",
155
+ "g",
156
+ "bar",
157
+ "--modifier2",
158
+ "g",
159
+ "bar",
160
+ ]],
161
+ globalModifierCommandsByName,
162
+ globalModifierCommandsByShortAlias,
163
+ );
164
+ expectScanResult(scanResult, expected);
165
+
166
+ scanResult = scanForGlobalModifierCommandClauses(
167
+ [[
168
+ "--modifier1=g",
169
+ "bar",
170
+ "--modifier2=g",
171
+ "bar",
172
+ ]],
173
+ globalModifierCommandsByName,
174
+ globalModifierCommandsByShortAlias,
175
+ );
176
+ expectScanResult(scanResult, expected);
177
+
178
+ scanResult = scanForGlobalModifierCommandClauses(
179
+ [[
180
+ "-1",
181
+ "g",
182
+ "bar",
183
+ "-2",
184
+ "g",
185
+ "bar",
186
+ ]],
187
+ globalModifierCommandsByName,
188
+ globalModifierCommandsByShortAlias,
189
+ );
190
+ expectScanResult(scanResult, expected);
191
+
192
+ scanResult = scanForGlobalModifierCommandClauses(
193
+ [[
194
+ "-1=g",
195
+ "bar",
196
+ "-2=g",
197
+ "bar",
198
+ ]],
199
+ globalModifierCommandsByName,
200
+ globalModifierCommandsByShortAlias,
201
+ );
202
+ expectScanResult(scanResult, expected);
203
+
204
+ scanResult = scanForGlobalModifierCommandClauses(
205
+ [[], [
206
+ "-1=g",
207
+ "bar",
208
+ "-2=g",
209
+ "bar",
210
+ ]],
211
+ globalModifierCommandsByName,
212
+ globalModifierCommandsByShortAlias,
213
+ );
214
+ expectScanResult(scanResult, expected);
215
+
216
+ expected = {
217
+ globalModifierCommandClauses: [{
218
+ command: globalModifierCommand1,
219
+ potentialArgs: ["g", "bar"],
220
+ }, {
221
+ command: globalModifierCommand2,
222
+ potentialArgs: ["g", "bar"],
223
+ }],
224
+ unusedArgSequences: [],
225
+ };
226
+
227
+ scanResult = scanForGlobalModifierCommandClauses(
228
+ [[
229
+ "-1=g",
230
+ "bar",
231
+ ], [
232
+ "-2=g",
233
+ "bar",
234
+ ]],
235
+ globalModifierCommandsByName,
236
+ globalModifierCommandsByShortAlias,
237
+ );
238
+ expectScanResult(scanResult, expected);
239
+ });
240
+
241
+ test("Unused leading args whilst scanning for global modifier command", () => {
242
+ const globalModifierCommand = getGlobalModifierCommand(
243
+ "modifier",
244
+ "m",
245
+ true,
246
+ true,
247
+ );
248
+ const globalModifierCommandsByName: ReadonlyMap<
249
+ string,
250
+ GlobalModifierCommand
251
+ > = new Map([[globalModifierCommand.name, globalModifierCommand]]);
252
+ const globalModifierCommandsByShortAlias: ReadonlyMap<
253
+ string,
254
+ GlobalModifierCommand
255
+ > = new Map([[globalModifierCommand.shortAlias!, globalModifierCommand]]);
256
+
257
+ let scanResult = scanForGlobalModifierCommandClauses(
258
+ [[
259
+ "hello",
260
+ "--world",
261
+ "subCommand",
262
+ "bar",
263
+ "--goo",
264
+ "g",
265
+ "--modifier",
266
+ "g",
267
+ "bar",
268
+ ]],
269
+ globalModifierCommandsByName,
270
+ globalModifierCommandsByShortAlias,
271
+ );
272
+ expectScanResult(scanResult, {
273
+ globalModifierCommandClauses: [{
274
+ command: globalModifierCommand,
275
+ potentialArgs: ["g", "bar"],
276
+ }],
277
+ unusedArgSequences: [[
278
+ "hello",
279
+ "--world",
280
+ "subCommand",
281
+ "bar",
282
+ "--goo",
283
+ "g",
284
+ ]],
285
+ });
286
+
287
+ scanResult = scanForGlobalModifierCommandClauses(
288
+ [[
289
+ "hello",
290
+ "--world",
291
+ "--modifier",
292
+ "g",
293
+ "bar",
294
+ "--globalCommand",
295
+ "bar",
296
+ "--goo",
297
+ "g",
298
+ ]],
299
+ globalModifierCommandsByName,
300
+ globalModifierCommandsByShortAlias,
301
+ );
302
+ expectScanResult(scanResult, {
303
+ globalModifierCommandClauses: [{
304
+ command: globalModifierCommand,
305
+ potentialArgs: ["g", "bar", "--globalCommand", "bar", "--goo", "g"],
306
+ }],
307
+ unusedArgSequences: [["hello", "--world"]],
308
+ });
309
+ scanResult = scanForGlobalModifierCommandClauses(
310
+ [[
311
+ "hello",
312
+ "--world",
313
+ ], [
314
+ "--modifier",
315
+ "g",
316
+ "bar",
317
+ "--globalCommand",
318
+ "bar",
319
+ "--goo",
320
+ "g",
321
+ ]],
322
+ globalModifierCommandsByName,
323
+ globalModifierCommandsByShortAlias,
324
+ );
325
+ expectScanResult(scanResult, {
326
+ globalModifierCommandClauses: [{
327
+ command: globalModifierCommand,
328
+ potentialArgs: ["g", "bar", "--globalCommand", "bar", "--goo", "g"],
329
+ }],
330
+ unusedArgSequences: [["hello", "--world"]],
331
+ });
332
+ });
333
+
334
+ test("Sub-command scanned", () => {
335
+ const subCommand = getSubCommandWithOptionAndPositional();
336
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([[
337
+ subCommand.name,
338
+ subCommand,
339
+ ]]);
340
+ let scanResult = scanForNonModifierCommandClause([[
341
+ "subCommand",
342
+ "bar",
343
+ "--goo",
344
+ "g",
345
+ ]], nonModifierCommandsByName);
346
+ expectScanResult(scanResult, {
347
+ nonModifierCommandClause: {
348
+ command: subCommand,
349
+ potentialArgs: ["bar", "--goo", "g"],
350
+ },
351
+ unusedArgSequences: [],
352
+ });
353
+
354
+ scanResult = scanForNonModifierCommandClause([[], [
355
+ "subCommand",
356
+ "bar",
357
+ "--goo",
358
+ "g",
359
+ ]], nonModifierCommandsByName);
360
+
361
+ expectScanResult(scanResult, {
362
+ nonModifierCommandClause: {
363
+ command: subCommand,
364
+ potentialArgs: ["bar", "--goo", "g"],
365
+ },
366
+ unusedArgSequences: [],
367
+ });
368
+
369
+ scanResult = scanForNonModifierCommandClause([[
370
+ "subCommand",
371
+ "bar",
372
+ "--goo",
373
+ "g",
374
+ ]], nonModifierCommandsByName);
375
+
376
+ expectScanResult(scanResult, {
377
+ nonModifierCommandClause: {
378
+ command: subCommand,
379
+ potentialArgs: ["bar", "--goo", "g"],
380
+ },
381
+ unusedArgSequences: [],
382
+ });
383
+ });
384
+
385
+ test("Global command scanned", () => {
386
+ const globalCommand = getGlobalCommandWithShortAlias("globalCommand", "g");
387
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([[
388
+ globalCommand.name,
389
+ globalCommand,
390
+ ]]);
391
+ const globalCommandsByShortAlias: ReadonlyMap<string, GlobalCommand> =
392
+ new Map([[globalCommand.shortAlias!, globalCommand]]);
393
+
394
+ let expected: ScanResult = {
395
+ nonModifierCommandClause: {
396
+ command: globalCommand,
397
+ potentialArgs: ["g", "bar"],
398
+ },
399
+ unusedArgSequences: [],
400
+ };
401
+
402
+ let scanResult = scanForNonModifierCommandClause(
403
+ [
404
+ ["--globalCommand", "g", "bar"],
405
+ ],
406
+ nonModifierCommandsByName,
407
+ globalCommandsByShortAlias,
408
+ );
409
+ expectScanResult(scanResult, expected);
410
+
411
+ scanResult = scanForNonModifierCommandClause(
412
+ [["--globalCommand=g", "bar"]],
413
+ nonModifierCommandsByName,
414
+ globalCommandsByShortAlias,
415
+ );
416
+ expectScanResult(scanResult, expected);
417
+
418
+ scanResult = scanForNonModifierCommandClause(
419
+ [["-g", "g", "bar"]],
420
+ nonModifierCommandsByName,
421
+ globalCommandsByShortAlias,
422
+ );
423
+ expectScanResult(scanResult, expected);
424
+
425
+ scanResult = scanForNonModifierCommandClause(
426
+ [["-g=g", "bar"]],
427
+ nonModifierCommandsByName,
428
+ globalCommandsByShortAlias,
429
+ );
430
+ expectScanResult(scanResult, expected);
431
+
432
+ expected = {
433
+ nonModifierCommandClause: {
434
+ command: globalCommand,
435
+ potentialArgs: ["g", "bar"],
436
+ },
437
+ unusedArgSequences: [],
438
+ };
439
+
440
+ scanResult = scanForNonModifierCommandClause(
441
+ [["-g=g", "bar"]],
442
+ nonModifierCommandsByName,
443
+ globalCommandsByShortAlias,
444
+ );
445
+ expectScanResult(scanResult, expected);
446
+
447
+ scanResult = scanForNonModifierCommandClause(
448
+ [["-g=g", "bar"]],
449
+ nonModifierCommandsByName,
450
+ globalCommandsByShortAlias,
451
+ );
452
+ expectScanResult(scanResult, expected);
453
+ });
454
+
455
+ test("Group command scanned", () => {
456
+ const subCommand = getSubCommandWithOptionAndPositional();
457
+ const groupCommand = getGroupCommand("group", [subCommand]);
458
+ const groupAndMemberCommandsByJoinedName: ReadonlyMap<
459
+ string,
460
+ { groupCommand: GroupCommand; command: SubCommand }
461
+ > = new Map([[`${groupCommand.name}:${subCommand.name}`, {
462
+ groupCommand,
463
+ command: subCommand,
464
+ }]]);
465
+ let expected: ScanResult = {
466
+ nonModifierCommandClause: {
467
+ groupCommand,
468
+ command: subCommand,
469
+ potentialArgs: ["bar", "--goo", "g"],
470
+ },
471
+ unusedArgSequences: [],
472
+ };
473
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
474
+ [groupCommand.name, groupCommand],
475
+ ]);
476
+
477
+ let scanResult = scanForNonModifierCommandClause(
478
+ [[
479
+ "group",
480
+ "subCommand",
481
+ "bar",
482
+ "--goo",
483
+ "g",
484
+ ]],
485
+ nonModifierCommandsByName,
486
+ new Map(),
487
+ groupAndMemberCommandsByJoinedName,
488
+ );
489
+ expectScanResult(scanResult, expected);
490
+
491
+ scanResult = scanForNonModifierCommandClause(
492
+ [[
493
+ "group:subCommand",
494
+ "bar",
495
+ "--goo",
496
+ "g",
497
+ ]],
498
+ new Map(),
499
+ new Map(),
500
+ groupAndMemberCommandsByJoinedName,
501
+ );
502
+ expectScanResult(scanResult, expected);
503
+
504
+ expected = {
505
+ nonModifierCommandClause: {
506
+ groupCommand,
507
+ command: subCommand,
508
+ potentialArgs: ["bar", "--goo", "g"],
509
+ },
510
+ unusedArgSequences: [],
511
+ };
512
+
513
+ scanResult = scanForNonModifierCommandClause(
514
+ [[
515
+ "group:subCommand",
516
+ "bar",
517
+ "--goo",
518
+ "g",
519
+ ]],
520
+ new Map(),
521
+ new Map(),
522
+ groupAndMemberCommandsByJoinedName,
523
+ );
524
+ expectScanResult(scanResult, expected);
525
+ });
526
+
527
+ test("Unused leading args whilst scanning for non-modifier command", () => {
528
+ const globalCommand = getGlobalCommandWithShortAlias("globalCommand", "g");
529
+ const subCommand = getSubCommandWithOptionAndPositional();
530
+
531
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
532
+ [globalCommand.name, globalCommand as Command],
533
+ [subCommand.name, subCommand as Command],
534
+ ]);
535
+ const globalCommandsByShortAlias: ReadonlyMap<string, GlobalCommand> =
536
+ new Map([[globalCommand.shortAlias!, globalCommand]]);
537
+
538
+ let scanResult = scanForNonModifierCommandClause(
539
+ [[
540
+ "hello",
541
+ "--world",
542
+ "subCommand",
543
+ "bar",
544
+ "--goo",
545
+ "g",
546
+ "--modifier",
547
+ "g",
548
+ "bar",
549
+ ]],
550
+ nonModifierCommandsByName,
551
+ globalCommandsByShortAlias,
552
+ );
553
+ expectScanResult(scanResult, {
554
+ nonModifierCommandClause: {
555
+ command: subCommand,
556
+ potentialArgs: ["bar", "--goo", "g", "--modifier", "g", "bar"],
557
+ },
558
+ unusedArgSequences: [["hello", "--world"]],
559
+ });
560
+
561
+ scanResult = scanForNonModifierCommandClause(
562
+ [[
563
+ "hello",
564
+ "--world",
565
+ "--globalCommand",
566
+ "bar",
567
+ "--goo",
568
+ "g",
569
+ "--modifier",
570
+ "g",
571
+ "bar",
572
+ ]],
573
+ nonModifierCommandsByName,
574
+ globalCommandsByShortAlias,
575
+ );
576
+ expectScanResult(scanResult, {
577
+ nonModifierCommandClause: {
578
+ command: globalCommand,
579
+ potentialArgs: ["bar", "--goo", "g", "--modifier", "g", "bar"],
580
+ },
581
+ unusedArgSequences: [["hello", "--world"]],
582
+ });
583
+ scanResult = scanForNonModifierCommandClause(
584
+ [[
585
+ "hello",
586
+ "--world",
587
+ "--globalCommand",
588
+ "bar",
589
+ "--goo",
590
+ "g",
591
+ ], ["--modifier", "g", "bar"]],
592
+ nonModifierCommandsByName,
593
+ globalCommandsByShortAlias,
594
+ );
595
+ expectScanResult(scanResult, {
596
+ nonModifierCommandClause: {
597
+ command: globalCommand,
598
+ potentialArgs: ["bar", "--goo", "g"],
599
+ },
600
+ unusedArgSequences: [["hello", "--world"], ["--modifier", "g", "bar"]],
601
+ });
602
+ });
603
+
604
+ test("Sub-command with complex options scanned", () => {
605
+ const subCommand = getSubCommandWithComplexOptions();
606
+
607
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
608
+ [subCommand.name, subCommand],
609
+ ]);
610
+
611
+ let scanResult = scanForNonModifierCommandClause([[
612
+ "subCommand",
613
+ "-a.b.g=foo1",
614
+ "-a.b.d=1",
615
+ "-a.b.g=foo2",
616
+ "-a.b.d=2",
617
+ "-a.b.d=3",
618
+ "-e.g=bar",
619
+ "-e.d=1",
620
+ ]], nonModifierCommandsByName);
621
+ expectScanResult(scanResult, {
622
+ nonModifierCommandClause: {
623
+ command: subCommand,
624
+ potentialArgs: [
625
+ "-a.b.g=foo1",
626
+ "-a.b.d=1",
627
+ "-a.b.g=foo2",
628
+ "-a.b.d=2",
629
+ "-a.b.d=3",
630
+ "-e.g=bar",
631
+ "-e.d=1",
632
+ ],
633
+ },
634
+ unusedArgSequences: [],
635
+ });
636
+ scanResult = scanForNonModifierCommandClause([[
637
+ "subCommand",
638
+ "--alpha.beta.gamma=foo1",
639
+ "-a.b.d=1",
640
+ "-a.b.g=foo2",
641
+ "-a.b.d=2",
642
+ "-a.b.d=3",
643
+ "-e.g=bar",
644
+ "-e.d=1",
645
+ ]], nonModifierCommandsByName);
646
+ expectScanResult(scanResult, {
647
+ nonModifierCommandClause: {
648
+ command: subCommand,
649
+ potentialArgs: [
650
+ "--alpha.beta.gamma=foo1",
651
+ "-a.b.d=1",
652
+ "-a.b.g=foo2",
653
+ "-a.b.d=2",
654
+ "-a.b.d=3",
655
+ "-e.g=bar",
656
+ "-e.d=1",
657
+ ],
658
+ },
659
+ unusedArgSequences: [],
660
+ });
661
+ scanResult = scanForNonModifierCommandClause([[
662
+ "subCommand",
663
+ "-e.g=bar",
664
+ "-e.d=1",
665
+ "-a.b[1].g=foo2",
666
+ "--alpha.beta[0].gamma=foo1",
667
+ "-a.b[1].d[1]=2",
668
+ "-a.b[0].d=1",
669
+ "-a.b[1].d[0]=3",
670
+ ]], nonModifierCommandsByName);
671
+ expectScanResult(scanResult, {
672
+ nonModifierCommandClause: {
673
+ command: subCommand,
674
+ potentialArgs: [
675
+ "-e.g=bar",
676
+ "-e.d=1",
677
+ "-a.b[1].g=foo2",
678
+ "--alpha.beta[0].gamma=foo1",
679
+ "-a.b[1].d[1]=2",
680
+ "-a.b[0].d=1",
681
+ "-a.b[1].d[0]=3",
682
+ ],
683
+ },
684
+ unusedArgSequences: [],
685
+ });
686
+ });
687
+
688
+ test("Sub-command member not scanned if parent not specified", () => {
689
+ const subCommand = getSubCommandWithOptionAndPositional();
690
+ const groupCommand = getGroupCommand("group", [subCommand]);
691
+ const groupAndMemberCommandsByJoinedName: ReadonlyMap<
692
+ string,
693
+ { groupCommand: GroupCommand; command: SubCommand }
694
+ > = new Map([[`${groupCommand.name}:${subCommand.name}`, {
695
+ groupCommand,
696
+ command: subCommand,
697
+ }]]);
698
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
699
+ [groupCommand.name, groupCommand],
700
+ ]);
701
+
702
+ const scanResult = scanForNonModifierCommandClause(
703
+ [[
704
+ "subCommand",
705
+ "foo",
706
+ "--goo",
707
+ "g",
708
+ ]],
709
+ nonModifierCommandsByName,
710
+ new Map(),
711
+ groupAndMemberCommandsByJoinedName,
712
+ );
713
+ expectScanResult(scanResult, {
714
+ unusedArgSequences: [["subCommand", "foo", "--goo", "g"]],
715
+ });
716
+ });
717
+
718
+ test("Global command normalising (tricky scenario 1)", () => {
719
+ const globalCommand = getGlobalCommandWithShortAlias("globalCommand", "g");
720
+
721
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
722
+ [globalCommand.name, globalCommand],
723
+ ]);
724
+ const globalCommandsByShortAlias: ReadonlyMap<string, GlobalCommand> =
725
+ new Map([[globalCommand.shortAlias!, globalCommand]]);
726
+
727
+ const expected: ScanResult = {
728
+ nonModifierCommandClause: {
729
+ command: globalCommand,
730
+ potentialArgs: ["g=2", "bar=2"],
731
+ },
732
+ unusedArgSequences: [],
733
+ };
734
+
735
+ let scanResult = scanForNonModifierCommandClause(
736
+ [[
737
+ "--globalCommand",
738
+ "g=2",
739
+ "bar=2",
740
+ ]],
741
+ nonModifierCommandsByName,
742
+ globalCommandsByShortAlias,
743
+ );
744
+ expectScanResult(scanResult, expected);
745
+
746
+ scanResult = scanForNonModifierCommandClause(
747
+ [[
748
+ "--globalCommand=g=2",
749
+ "bar=2",
750
+ ]],
751
+ nonModifierCommandsByName,
752
+ globalCommandsByShortAlias,
753
+ );
754
+ expectScanResult(scanResult, expected);
755
+
756
+ scanResult = scanForNonModifierCommandClause(
757
+ [[
758
+ "-g",
759
+ "g=2",
760
+ "bar=2",
761
+ ]],
762
+ nonModifierCommandsByName,
763
+ globalCommandsByShortAlias,
764
+ );
765
+ expectScanResult(scanResult, expected);
766
+
767
+ scanResult = scanForNonModifierCommandClause(
768
+ [[
769
+ "-g=g=2",
770
+ "bar=2",
771
+ ]],
772
+ nonModifierCommandsByName,
773
+ globalCommandsByShortAlias,
774
+ );
775
+ expectScanResult(scanResult, expected);
776
+ });
777
+
778
+ test("Global command normalising (tricky scenario 2)", () => {
779
+ const globalCommand = getGlobalCommandWithShortAlias("globalCommand", "g");
780
+
781
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
782
+ [globalCommand.name, globalCommand],
783
+ ]);
784
+ const globalCommandsByShortAlias: ReadonlyMap<string, GlobalCommand> =
785
+ new Map([[globalCommand.shortAlias!, globalCommand]]);
786
+
787
+ const expected: ScanResult = {
788
+ nonModifierCommandClause: {
789
+ command: globalCommand,
790
+ potentialArgs: ["g="],
791
+ },
792
+ unusedArgSequences: [],
793
+ };
794
+
795
+ const scanResult = scanForNonModifierCommandClause(
796
+ [[
797
+ "--globalCommand",
798
+ "g=",
799
+ ]],
800
+ nonModifierCommandsByName,
801
+ globalCommandsByShortAlias,
802
+ );
803
+ expectScanResult(scanResult, expected);
804
+ });
805
+
806
+ test("Two sub-commands specified (illegal scenario)", () => {
807
+ const subCommand = getSubCommandWithOptionAndPositional();
808
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
809
+ [subCommand.name, subCommand],
810
+ ]);
811
+
812
+ const scanResult = scanForNonModifierCommandClause([[
813
+ "subCommand",
814
+ "bar",
815
+ "--goo",
816
+ "g",
817
+ "subCommand",
818
+ "bar",
819
+ ]], nonModifierCommandsByName);
820
+ expectScanResult(scanResult, {
821
+ nonModifierCommandClause: {
822
+ command: subCommand,
823
+ potentialArgs: ["bar", "--goo", "g", "subCommand", "bar"],
824
+ },
825
+ unusedArgSequences: [],
826
+ });
827
+ });
828
+
829
+ test("Unknown group member command rejected", () => {
830
+ const subCommand = getSubCommandWithOptionAndPositional();
831
+ const groupCommand = getGroupCommand("group", [subCommand]);
832
+ const groupAndMemberCommandsByJoinedName: ReadonlyMap<
833
+ string,
834
+ { groupCommand: GroupCommand; command: SubCommand }
835
+ > = new Map([[`${groupCommand.name}:${subCommand.name}`, {
836
+ groupCommand,
837
+ command: subCommand,
838
+ }]]);
839
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
840
+ [groupCommand.name, groupCommand],
841
+ ]);
842
+
843
+ let scanResult = scanForNonModifierCommandClause(
844
+ [[
845
+ "group",
846
+ "blah",
847
+ "bar",
848
+ "--goo",
849
+ "g",
850
+ ]],
851
+ nonModifierCommandsByName,
852
+ new Map(),
853
+ groupAndMemberCommandsByJoinedName,
854
+ );
855
+ expectScanResult(scanResult, {
856
+ unusedArgSequences: [["group", "blah", "bar", "--goo", "g"]],
857
+ });
858
+
859
+ scanResult = scanForNonModifierCommandClause(
860
+ [[
861
+ "group:blah",
862
+ "bar",
863
+ "--goo",
864
+ "g",
865
+ ]],
866
+ nonModifierCommandsByName,
867
+ new Map(),
868
+ groupAndMemberCommandsByJoinedName,
869
+ );
870
+ expectScanResult(scanResult, {
871
+ unusedArgSequences: [["group:blah", "bar", "--goo", "g"]],
872
+ });
873
+ });
874
+
875
+ test("Two group commands scanned (illegal scenario)", () => {
876
+ const subCommand = getSubCommandWithOptionAndPositional();
877
+ const groupCommand1 = getGroupCommand("group1", [subCommand]);
878
+ const groupCommand2 = getGroupCommand("group2", [subCommand]);
879
+ const groupAndMemberCommandsByJoinedName: ReadonlyMap<
880
+ string,
881
+ { groupCommand: GroupCommand; command: SubCommand }
882
+ > = new Map([
883
+ [`${groupCommand1.name}:${subCommand.name}`, {
884
+ groupCommand: groupCommand1,
885
+ command: subCommand,
886
+ }],
887
+ [`${groupCommand2.name}:${subCommand.name}`, {
888
+ groupCommand: groupCommand2,
889
+ command: subCommand,
890
+ }],
891
+ ]);
892
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
893
+ [groupCommand1.name, groupCommand1],
894
+ [groupCommand2.name, groupCommand2],
895
+ ]);
896
+
897
+ let scanResult = scanForNonModifierCommandClause(
898
+ [[
899
+ "group1",
900
+ "subCommand",
901
+ "bar",
902
+ "--goo",
903
+ "g",
904
+ "group2",
905
+ "subCommand",
906
+ "bar",
907
+ "--goo",
908
+ "g",
909
+ ]],
910
+ nonModifierCommandsByName,
911
+ new Map(),
912
+ groupAndMemberCommandsByJoinedName,
913
+ );
914
+ expectScanResult(scanResult, {
915
+ nonModifierCommandClause: {
916
+ groupCommand: groupCommand1,
917
+ command: subCommand,
918
+ potentialArgs: [
919
+ "bar",
920
+ "--goo",
921
+ "g",
922
+ "group2",
923
+ "subCommand",
924
+ "bar",
925
+ "--goo",
926
+ "g",
927
+ ],
928
+ },
929
+ unusedArgSequences: [],
930
+ });
931
+
932
+ scanResult = scanForNonModifierCommandClause(
933
+ [[
934
+ "group1",
935
+ "subCommand",
936
+ "bar",
937
+ "--goo",
938
+ "g",
939
+ "group2:command",
940
+ "bar",
941
+ "--goo",
942
+ "g",
943
+ ]],
944
+ new Map(),
945
+ new Map(),
946
+ groupAndMemberCommandsByJoinedName,
947
+ );
948
+ expectScanResult(scanResult, {
949
+ nonModifierCommandClause: {
950
+ groupCommand: groupCommand1,
951
+ command: subCommand,
952
+ potentialArgs: [
953
+ "bar",
954
+ "--goo",
955
+ "g",
956
+ "group2:command",
957
+ "bar",
958
+ "--goo",
959
+ "g",
960
+ ],
961
+ },
962
+ unusedArgSequences: [],
963
+ });
964
+ });
965
+
966
+ test("Global command and sub-command scanned (illegal scenario)", () => {
967
+ const globalCommand = getGlobalCommandWithShortAlias("globalCommand", "g");
968
+ const subCommand = getSubCommandWithOptionAndPositional();
969
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
970
+ [subCommand.name, subCommand as Command],
971
+ [globalCommand.name, globalCommand as Command],
972
+ ]);
973
+ const globalCommandsByShortAlias: ReadonlyMap<string, GlobalCommand> =
974
+ new Map([[globalCommand.shortAlias!, globalCommand]]);
975
+
976
+ let scanResult = scanForNonModifierCommandClause(
977
+ [[
978
+ "subCommand",
979
+ "bar",
980
+ "--goo",
981
+ "g",
982
+ "--modifier1",
983
+ "g",
984
+ "bar",
985
+ "--globalCommand",
986
+ "bar",
987
+ "--goo",
988
+ "g",
989
+ "--modifier2",
990
+ "g",
991
+ "bar",
992
+ ]],
993
+ nonModifierCommandsByName,
994
+ globalCommandsByShortAlias,
995
+ );
996
+ expectScanResult(scanResult, {
997
+ nonModifierCommandClause: {
998
+ command: subCommand,
999
+ potentialArgs: [
1000
+ "bar",
1001
+ "--goo",
1002
+ "g",
1003
+ "--modifier1",
1004
+ "g",
1005
+ "bar",
1006
+ "--globalCommand",
1007
+ "bar",
1008
+ "--goo",
1009
+ "g",
1010
+ "--modifier2",
1011
+ "g",
1012
+ "bar",
1013
+ ],
1014
+ },
1015
+ unusedArgSequences: [],
1016
+ });
1017
+
1018
+ scanResult = scanForNonModifierCommandClause(
1019
+ [[
1020
+ "subCommand",
1021
+ "bar",
1022
+ "--goo=g",
1023
+ "-1=g",
1024
+ "bar",
1025
+ "-g=bar",
1026
+ "--goo",
1027
+ "g",
1028
+ "-2=g",
1029
+ "bar",
1030
+ ]],
1031
+ nonModifierCommandsByName,
1032
+ globalCommandsByShortAlias,
1033
+ );
1034
+ expectScanResult(scanResult, {
1035
+ nonModifierCommandClause: {
1036
+ command: subCommand,
1037
+ potentialArgs: [
1038
+ "bar",
1039
+ "--goo=g",
1040
+ "-1=g",
1041
+ "bar",
1042
+ "-g=bar",
1043
+ "--goo",
1044
+ "g",
1045
+ "-2=g",
1046
+ "bar",
1047
+ ],
1048
+ },
1049
+ unusedArgSequences: [],
1050
+ });
1051
+ });
1052
+
1053
+ test("Group command and sub-command scanned (illegal scenario)", () => {
1054
+ const subCommand = getSubCommandWithOptionAndPositional();
1055
+ const groupCommand = getGroupCommand("group", [subCommand]);
1056
+ const nonModifierCommandsByName: ReadonlyMap<string, Command> = new Map([
1057
+ [subCommand.name, subCommand as Command],
1058
+ [groupCommand.name, groupCommand as Command],
1059
+ ]);
1060
+ const groupAndMemberCommandsByJoinedName: ReadonlyMap<
1061
+ string,
1062
+ { groupCommand: GroupCommand; command: SubCommand }
1063
+ > = new Map([
1064
+ [`${groupCommand.name}:${subCommand.name}`, {
1065
+ groupCommand,
1066
+ command: subCommand,
1067
+ }],
1068
+ ]);
1069
+
1070
+ const scanResult = scanForNonModifierCommandClause(
1071
+ [[
1072
+ "subCommand",
1073
+ "bar",
1074
+ "--goo",
1075
+ "g",
1076
+ "--modifier1",
1077
+ "g",
1078
+ "bar",
1079
+ "group:command",
1080
+ "bar",
1081
+ "--goo",
1082
+ "g",
1083
+ "--modifier2",
1084
+ "g",
1085
+ "bar",
1086
+ ]],
1087
+ nonModifierCommandsByName,
1088
+ new Map(),
1089
+ groupAndMemberCommandsByJoinedName,
1090
+ );
1091
+ expectScanResult(scanResult, {
1092
+ nonModifierCommandClause: {
1093
+ command: subCommand,
1094
+ potentialArgs: [
1095
+ "bar",
1096
+ "--goo",
1097
+ "g",
1098
+ "--modifier1",
1099
+ "g",
1100
+ "bar",
1101
+ "group:command",
1102
+ "bar",
1103
+ "--goo",
1104
+ "g",
1105
+ "--modifier2",
1106
+ "g",
1107
+ "bar",
1108
+ ],
1109
+ },
1110
+ unusedArgSequences: [],
1111
+ });
1112
+ });
1113
+ });