@flowscripter/dynamic-cli-framework 1.1.5 → 1.2.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 (165) hide show
  1. package/.github/workflows/release-bun-library.yml +1 -0
  2. package/README/core-commands.md +40 -0
  3. package/README/core-service-providers.md +198 -0
  4. package/README/development.md +60 -0
  5. package/README/implementation-details.md +474 -0
  6. package/README/key-concepts.md +751 -0
  7. package/README.md +12 -1413
  8. package/bun.lock +20 -18
  9. package/index.ts +98 -1
  10. package/package.json +8 -7
  11. package/src/api/BaseCLIFeatureOptions.ts +10 -0
  12. package/src/api/CLIConfig.ts +5 -0
  13. package/src/api/RunResult.ts +16 -0
  14. package/src/api/argument/Argument.ts +40 -1
  15. package/src/api/argument/ArgumentValueTypes.ts +1 -1
  16. package/src/api/command/SubCommand.ts +1 -1
  17. package/src/api/service/ServiceProvider.ts +3 -3
  18. package/src/api/service/core/ArgumentPrompterService.ts +10 -0
  19. package/src/api/service/core/AsciiBannerGeneratorService.ts +44 -7
  20. package/src/api/service/core/CompletionService.ts +36 -0
  21. package/src/api/service/core/DataDumpGeneratorService.ts +27 -0
  22. package/src/api/service/core/ImagePrinterService.ts +26 -0
  23. package/src/api/service/core/KeyValueService.ts +22 -5
  24. package/src/api/service/core/PrinterService.ts +127 -1
  25. package/src/api/service/core/PrompterService.ts +49 -0
  26. package/src/api/service/core/SecretService.ts +27 -0
  27. package/src/api/service/core/ShutdownService.ts +17 -1
  28. package/src/api/service/core/Table.ts +85 -0
  29. package/src/api/service/core/TableGeneratorService.ts +38 -0
  30. package/src/api/service/core/TreePrinterService.ts +11 -0
  31. package/src/cli/BaseCLI.ts +116 -44
  32. package/src/cli/DefaultRuntimeCLI.ts +10 -18
  33. package/src/command/CompletionCommand.ts +179 -0
  34. package/src/command/MultiCommandCliHelpCommand.ts +20 -32
  35. package/src/command/SingleCommandCliHelpCommand.ts +10 -1
  36. package/src/launcher.ts +16 -58
  37. package/src/runtime/command/CommandValidator.ts +70 -6
  38. package/src/runtime/runner.ts +112 -31
  39. package/src/runtime/scanner.ts +18 -15
  40. package/src/runtime/values/argumentValueValidation.ts +93 -10
  41. package/src/runtime/values/globalCommandValuePopulation.ts +1 -1
  42. package/src/runtime/values/subCommandValuePopulation.ts +9 -8
  43. package/src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts +34 -0
  44. package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +330 -0
  45. package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +1 -1
  46. package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +254 -17
  47. package/src/service/banner/BannerServiceProvider.ts +2 -2
  48. package/src/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.ts +85 -0
  49. package/src/service/chiselAsciiBannerGenerator/chisel.flf.json +3 -0
  50. package/src/service/completion/CompletionServiceProvider.ts +212 -0
  51. package/src/service/completion/DefaultCompletionService.ts +364 -0
  52. package/src/service/completion/shell/ShellHandler.ts +16 -0
  53. package/src/service/completion/shell/bash.ts +47 -0
  54. package/src/service/completion/shell/fish.ts +43 -0
  55. package/src/service/completion/shell/powershell.ts +66 -0
  56. package/src/service/completion/shell/zsh.ts +50 -0
  57. package/src/service/configuration/ConfigurationServiceProvider.ts +138 -42
  58. package/src/service/configuration/DefaultKeyValueService.ts +53 -7
  59. package/src/service/configuration/DefaultSecretService.ts +85 -0
  60. package/src/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.ts +41 -0
  61. package/src/service/dataDumpGenerator/DefaultDataDumpGeneratorService.ts +80 -0
  62. package/src/service/imagePrinter/DefaultImagePrinterService.ts +23 -0
  63. package/src/service/imagePrinter/ImagePrinterServiceProvider.ts +32 -0
  64. package/src/service/prettyPrinter/PrettyPrinterServiceProvider.ts +1 -1
  65. package/src/service/printer/DefaultPrinterService.ts +147 -27
  66. package/src/service/printer/PrinterServiceProvider.ts +7 -7
  67. package/src/service/printer/command/LogLevelCommand.ts +2 -1
  68. package/src/service/printer/command/NoHyperlinksCommand.ts +40 -0
  69. package/src/service/printer/terminal/Progress.ts +21 -4
  70. package/src/service/printer/terminal/Spinner.ts +32 -8
  71. package/src/service/printer/terminal/Theme.ts +1 -1
  72. package/src/service/prompter/DefaultPrompterService.ts +87 -0
  73. package/src/service/prompter/PrompterServiceProvider.ts +36 -0
  74. package/src/service/prompter/command/NoPromptCommand.ts +40 -0
  75. package/src/service/prompter/prompt/PromptContext.ts +47 -0
  76. package/src/service/prompter/prompt/promptAcknowledge.ts +28 -0
  77. package/src/service/prompter/prompt/promptMultiSelect.ts +99 -0
  78. package/src/service/prompter/prompt/promptOpenUrl.ts +97 -0
  79. package/src/service/prompter/prompt/promptSingleSelect.ts +102 -0
  80. package/src/service/prompter/prompt/promptText.ts +107 -0
  81. package/src/service/prompter/prompt/promptToggle.ts +28 -0
  82. package/src/service/shutdown/DefaultShutdownService.ts +13 -0
  83. package/src/service/shutdown/ShutdownServiceProvider.ts +28 -9
  84. package/src/service/shutdown/ShutdownState.ts +5 -0
  85. package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +1 -1
  86. package/src/service/tableGenerator/DefaultTableGeneratorService.ts +415 -0
  87. package/src/service/tableGenerator/TableGeneratorServiceProvider.ts +43 -0
  88. package/src/service/treePrinter/DefaultTreePrinterService.ts +49 -0
  89. package/src/service/treePrinter/TreePrinterServiceProvider.ts +40 -0
  90. package/src/terminal/Ansi.ts +27 -0
  91. package/src/terminal/ImageRenderer.ts +199 -0
  92. package/src/terminal/KeyReader.ts +23 -0
  93. package/src/terminal/PngDecoder.ts +143 -0
  94. package/src/{service/printer/terminal → terminal}/Styler.ts +3 -0
  95. package/src/{service/printer/terminal → terminal}/Terminal.ts +1 -0
  96. package/src/terminal/TtyKeyReader.ts +83 -0
  97. package/src/terminal/TtyStyler.ts +78 -0
  98. package/src/{service/printer/terminal → terminal}/TtyTerminal.ts +9 -1
  99. package/src/terminal/supportsHyperlinks.ts +110 -0
  100. package/src/util/configHelper.ts +2 -2
  101. package/src/util/envVarHelper.ts +2 -2
  102. package/src/util/helpHelper.ts +139 -123
  103. package/src/util/logger.ts +16 -14
  104. package/src/util/runnerHelper.ts +41 -0
  105. package/tests/cli/BaseCLI.test.ts +50 -54
  106. package/tests/command/CompletionCommand.test.ts +68 -0
  107. package/tests/command/UsageCommand.test.ts +3 -2
  108. package/tests/command/VersionCommand.test.ts +3 -2
  109. package/tests/fixtures/Context.ts +9 -2
  110. package/tests/fixtures/util.ts +1 -1
  111. package/tests/runtime/command/CommandValidator.test.ts +103 -0
  112. package/tests/runtime/parser.test.ts +9 -7
  113. package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
  114. package/tests/runtime/runner.test.ts +36 -0
  115. package/tests/runtime/values/argumentValueValidation.test.ts +215 -1
  116. package/tests/service/argumentPrompter/ArgumentPrompterServiceProvider.test.ts +47 -0
  117. package/tests/service/argumentPrompter/DefaultArgumentPrompterService.test.ts +754 -0
  118. package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +5 -4
  119. package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +163 -3
  120. package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +1 -1
  121. package/tests/service/banner/BannerServiceProvider.test.ts +8 -4
  122. package/tests/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.test.ts +52 -0
  123. package/tests/service/completion/CompletionServiceProvider.test.ts +404 -0
  124. package/tests/service/completion/DefaultCompletionService.test.ts +278 -0
  125. package/tests/service/completion/shell/bash.test.ts +73 -0
  126. package/tests/service/completion/shell/fish.test.ts +62 -0
  127. package/tests/service/completion/shell/powershell.test.ts +59 -0
  128. package/tests/service/completion/shell/zsh.test.ts +60 -0
  129. package/tests/service/configuration/ConfigurationServiceProvider.test.ts +24 -12
  130. package/tests/service/configuration/DefaultKeyValueService.test.ts +118 -16
  131. package/tests/service/configuration/DefaultSecretService.test.ts +148 -0
  132. package/tests/service/configuration/command/ConfigCommand.test.ts +29 -0
  133. package/tests/service/configuration/command/DumpConfigCommand.test.ts +60 -0
  134. package/tests/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.test.ts +57 -0
  135. package/tests/service/dataDumpGenerator/DefaultDataDumpGeneratorService.test.ts +217 -0
  136. package/tests/service/imagePrinter/DefaultImagePrinterService.test.ts +96 -0
  137. package/tests/service/imagePrinter/ImagePrinterServiceProvider.test.ts +17 -0
  138. package/tests/service/prettyPrinter/DefaultPrettyPrinterService.test.ts +2 -2
  139. package/tests/service/prettyPrinter/PrettyPrinterServiceProvider.test.ts +5 -4
  140. package/tests/service/printer/DefaultPrinterService.test.ts +233 -2
  141. package/tests/service/printer/PrinterServiceProvider.test.ts +6 -5
  142. package/tests/service/printer/command/DarkModeCommand.test.ts +83 -0
  143. package/tests/service/printer/command/LogLevelCommand.test.ts +8 -6
  144. package/tests/service/printer/command/NoColorCommand.test.ts +83 -0
  145. package/tests/service/printer/command/NoHyperlinksCommand.test.ts +89 -0
  146. package/tests/service/printer/terminal/Progress.test.ts +64 -4
  147. package/tests/service/printer/terminal/Spinner.test.ts +44 -3
  148. package/tests/service/prompter/DefaultPrompterService.test.ts +615 -0
  149. package/tests/service/prompter/PrompterServiceProvider.test.ts +47 -0
  150. package/tests/service/prompter/command/NoPromptCommand.test.ts +58 -0
  151. package/tests/service/shutdown/ShutdownServiceProvider.test.ts +4 -2
  152. package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +3 -2
  153. package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +5 -4
  154. package/tests/service/tableGenerator/DefaultTableGeneratorService.test.ts +363 -0
  155. package/tests/service/tableGenerator/TableGeneratorServiceProvider.test.ts +47 -0
  156. package/tests/service/treePrinter/DefaultTreePrinterService.test.ts +144 -0
  157. package/tests/service/treePrinter/TreePrinterServiceProvider.test.ts +55 -0
  158. package/tests/terminal/ImageRenderer.test.ts +161 -0
  159. package/tests/terminal/PngDecoder.test.ts +223 -0
  160. package/tests/terminal/supportsHyperlinks.test.ts +149 -0
  161. package/tests/util/configHelper.test.ts +4 -4
  162. package/tests/util/logger.test.ts +23 -1
  163. package/tsconfig.json +31 -0
  164. package/src/service/printer/terminal/Ansi.ts +0 -10
  165. package/src/service/printer/terminal/TtyStyler.ts +0 -39
@@ -23,24 +23,31 @@ import type Context from "../../src/api/Context.ts";
23
23
  import type CLIConfig from "../../src/api/CLIConfig.ts";
24
24
  import StreamString from "../fixtures/StreamString.ts";
25
25
  import { expectStringEquals, expectStringIncludes } from "../fixtures/util.ts";
26
- import TtyTerminal from "../../src/service/printer/terminal/TtyTerminal.ts";
27
- import TtyStyler from "../../src/service/printer/terminal/TtyStyler.ts";
26
+ import TtyTerminal from "../../src/terminal/TtyTerminal.ts";
27
+ import TtyStyler from "../../src/terminal/TtyStyler.ts";
28
+ import type KeyReader from "../../src/terminal/KeyReader.ts";
29
+
30
+ const mockKeyReader: KeyReader = {
31
+ enableRawMode() {},
32
+ disableRawMode() {},
33
+ readKey: () => Promise.resolve({}),
34
+ };
28
35
 
29
36
  describe("BaseCLI tests", () => {
30
37
  test("BaseCLI no command specified works", async () => {
31
38
  const config = getCLIConfig();
32
39
  const dummyStdout = new StreamString();
33
40
  const dummyStderr = new StreamString();
34
- const terminal = new TtyTerminal(dummyStdout.writeStream);
35
41
  const baseCLI = new BaseCLI(
36
42
  config,
37
43
  dummyStdout.writableStream,
38
44
  dummyStderr.writableStream,
39
45
  false,
40
46
  false,
41
- terminal,
47
+ new TtyTerminal(dummyStdout.writeStream),
48
+ new TtyTerminal(dummyStderr.writeStream),
42
49
  new TtyStyler(3),
43
- false,
50
+ mockKeyReader,
44
51
  );
45
52
 
46
53
  baseCLI.addCommand(getSubCommandWithOptionAndPositional());
@@ -54,16 +61,16 @@ describe("BaseCLI tests", () => {
54
61
  const config = getCLIConfig();
55
62
  const dummyStdout = new StreamString();
56
63
  const dummyStderr = new StreamString();
57
- const terminal = new TtyTerminal(dummyStderr.writeStream);
58
64
  const baseCLI = new BaseCLI(
59
65
  config,
60
66
  dummyStdout.writableStream,
61
67
  dummyStderr.writableStream,
62
68
  false,
63
69
  false,
64
- terminal,
70
+ new TtyTerminal(dummyStdout.writeStream),
71
+ new TtyTerminal(dummyStderr.writeStream),
65
72
  new TtyStyler(3),
66
- false,
73
+ mockKeyReader,
67
74
  );
68
75
 
69
76
  let modifierHasRun = false;
@@ -122,11 +129,14 @@ describe("BaseCLI tests", () => {
122
129
  dummyStderr.writableStream,
123
130
  false,
124
131
  false,
132
+ new TtyTerminal(dummyStdout.writeStream),
125
133
  new TtyTerminal(dummyStderr.writeStream),
126
134
  new TtyStyler(3),
127
- false,
128
- true,
129
- true,
135
+ mockKeyReader,
136
+ {
137
+ configFileSupportEnabled: true,
138
+ keyValueServiceEnabled: true,
139
+ },
130
140
  );
131
141
 
132
142
  let serviceProvider1Initialised = false;
@@ -140,21 +150,20 @@ describe("BaseCLI tests", () => {
140
150
  const SERVICE_2 = "service2";
141
151
 
142
152
  interface ServiceInterface {
143
- serviceMethod(context: Context): void;
153
+ serviceMethod(context: Context): Promise<void>;
144
154
  }
145
155
 
146
156
  class DefaultService1 implements ServiceInterface {
147
157
  copyOfKeyValueService: KeyValueService | undefined;
148
158
 
149
- serviceMethod(context: Context): void {
150
- // should only have access to the modifierCommand KV scope
151
- expect(this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
159
+ async serviceMethod(context: Context): Promise<void> {
160
+ expect(await this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
152
161
  expect(
153
- this.copyOfKeyValueService!.getKey("name"),
162
+ await this.copyOfKeyValueService!.getKey("name"),
154
163
  ).toEqual(
155
164
  "modifierCommand",
156
165
  );
157
- this.copyOfKeyValueService!.setKey(
166
+ await this.copyOfKeyValueService!.setKey(
158
167
  "name",
159
168
  "defaultService1+modifierCommand",
160
169
  );
@@ -163,10 +172,9 @@ describe("BaseCLI tests", () => {
163
172
  KEY_VALUE_SERVICE_ID,
164
173
  ) as KeyValueService;
165
174
 
166
- // should only have access to the modifierCommand KV scope
167
- expect(keyValueService.hasKey("name")).toBeTrue();
175
+ expect(await keyValueService.hasKey("name")).toBeTrue();
168
176
  expect(
169
- this.copyOfKeyValueService!.getKey("name"),
177
+ await this.copyOfKeyValueService!.getKey("name"),
170
178
  ).toEqual(
171
179
  "defaultService1+modifierCommand",
172
180
  );
@@ -178,13 +186,12 @@ describe("BaseCLI tests", () => {
178
186
  class DefaultService2 implements ServiceInterface {
179
187
  copyOfKeyValueService: KeyValueService | undefined;
180
188
 
181
- serviceMethod(context: Context): void {
182
- // should only have access to the modifierCommand KV scope
183
- expect(this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
184
- expect(this.copyOfKeyValueService!.getKey("name")).toEqual(
189
+ async serviceMethod(context: Context): Promise<void> {
190
+ expect(await this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
191
+ expect(await this.copyOfKeyValueService!.getKey("name")).toEqual(
185
192
  "subCommand",
186
193
  );
187
- this.copyOfKeyValueService!.setKey(
194
+ await this.copyOfKeyValueService!.setKey(
188
195
  "name",
189
196
  "defaultService2+subCommand",
190
197
  );
@@ -193,10 +200,9 @@ describe("BaseCLI tests", () => {
193
200
  KEY_VALUE_SERVICE_ID,
194
201
  ) as KeyValueService;
195
202
 
196
- // should only have access to the subCommand KV scope
197
- expect(keyValueService.hasKey("name")).toBeTrue();
203
+ expect(await keyValueService.hasKey("name")).toBeTrue();
198
204
  expect(
199
- this.copyOfKeyValueService!.getKey("name"),
205
+ await this.copyOfKeyValueService!.getKey("name"),
200
206
  ).toEqual(
201
207
  "defaultService2+subCommand",
202
208
  );
@@ -211,7 +217,7 @@ describe("BaseCLI tests", () => {
211
217
 
212
218
  defaultService1: DefaultService1 | undefined;
213
219
 
214
- provide(_cliConfig: CLIConfig): Promise<ServiceInfo> {
220
+ getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
215
221
  this.defaultService1 = new DefaultService1();
216
222
  return Promise.resolve({
217
223
  service: this.defaultService1,
@@ -219,20 +225,17 @@ describe("BaseCLI tests", () => {
219
225
  });
220
226
  }
221
227
 
222
- initService(context: Context): Promise<void> {
228
+ async initService(context: Context): Promise<void> {
223
229
  const keyValueService = context.getServiceById(
224
230
  KEY_VALUE_SERVICE_ID,
225
231
  ) as KeyValueService;
226
232
 
227
- // should only have access to the ServiceProvider1 KV scope
228
- expect(keyValueService.hasKey("name")).toBeFalse();
229
- keyValueService.setKey("name", "defaultService2");
233
+ expect(await keyValueService.hasKey("name")).toBeFalse();
234
+ await keyValueService.setKey("name", "defaultService2");
230
235
 
231
- // take a copy to check later when service is accessed via a command
232
236
  this.defaultService1!.copyOfKeyValueService = keyValueService;
233
237
 
234
238
  serviceProvider1Initialised = true;
235
- return Promise.resolve(undefined);
236
239
  }
237
240
  }
238
241
 
@@ -242,7 +245,7 @@ describe("BaseCLI tests", () => {
242
245
 
243
246
  defaultService2: DefaultService2 | undefined;
244
247
 
245
- provide(_cliConfig: CLIConfig): Promise<ServiceInfo> {
248
+ getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
246
249
  this.defaultService2 = new DefaultService2();
247
250
  return Promise.resolve({
248
251
  service: this.defaultService2,
@@ -250,20 +253,17 @@ describe("BaseCLI tests", () => {
250
253
  });
251
254
  }
252
255
 
253
- initService(context: Context): Promise<void> {
256
+ async initService(context: Context): Promise<void> {
254
257
  const keyValueService = context.getServiceById(
255
258
  KEY_VALUE_SERVICE_ID,
256
259
  ) as KeyValueService;
257
260
 
258
- // should only have access to the ServiceProvider2 KV scope
259
- expect(keyValueService.hasKey("name")).toBeFalse();
260
- keyValueService.setKey("name", "defaultService2");
261
+ expect(await keyValueService.hasKey("name")).toBeFalse();
262
+ await keyValueService.setKey("name", "defaultService2");
261
263
 
262
- // take a copy to check later when service is accessed via a command
263
264
  this.defaultService2!.copyOfKeyValueService = keyValueService;
264
265
 
265
266
  serviceProvider2Initialised = true;
266
- return Promise.resolve(undefined);
267
267
  }
268
268
  }
269
269
 
@@ -282,37 +282,33 @@ describe("BaseCLI tests", () => {
282
282
  };
283
283
  const subCommand = getSubCommand("command", [option], []);
284
284
 
285
- modifierCommand.execute = (context): Promise<void> => {
285
+ modifierCommand.execute = async (context): Promise<void> => {
286
286
  const keyValueService = context.getServiceById(
287
287
  KEY_VALUE_SERVICE_ID,
288
288
  ) as KeyValueService;
289
289
 
290
- // should only have access to the modifierCommand KV scope
291
- expect(keyValueService.hasKey("name")).toBeFalse();
292
- keyValueService.setKey("name", "modifierCommand");
290
+ expect(await keyValueService.hasKey("name")).toBeFalse();
291
+ await keyValueService.setKey("name", "modifierCommand");
293
292
 
294
293
  const service1 = context.getServiceById(SERVICE_1) as ServiceInterface;
295
294
 
296
- service1.serviceMethod(context);
295
+ await service1.serviceMethod(context);
297
296
 
298
297
  modifierHasRun = true;
299
- return Promise.resolve();
300
298
  };
301
- subCommand.execute = (context): Promise<void> => {
299
+ subCommand.execute = async (context): Promise<void> => {
302
300
  const keyValueService = context.getServiceById(
303
301
  KEY_VALUE_SERVICE_ID,
304
302
  ) as KeyValueService;
305
303
 
306
- // should only have access to the subCommand KV scope
307
- expect(keyValueService.hasKey("name")).toBeFalse();
308
- keyValueService.setKey("name", "subCommand");
304
+ expect(await keyValueService.hasKey("name")).toBeFalse();
305
+ await keyValueService.setKey("name", "subCommand");
309
306
 
310
307
  const service2 = context.getServiceById(SERVICE_2) as ServiceInterface;
311
308
 
312
- service2.serviceMethod(context);
309
+ await service2.serviceMethod(context);
313
310
 
314
311
  subHasRun = true;
315
- return Promise.resolve();
316
312
  };
317
313
 
318
314
  baseCLI.addServiceProvider(new ServiceProvider1());
@@ -0,0 +1,68 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ CompletionCompleteSubCommand,
4
+ CompletionGroupCommand,
5
+ CompletionIntegrationSubCommand,
6
+ } from "../../src/command/CompletionCommand.ts";
7
+
8
+ describe("CompletionIntegrationSubCommand", () => {
9
+ test("has correct name", () => {
10
+ const cmd = new CompletionIntegrationSubCommand();
11
+ expect(cmd.name).toEqual("integration");
12
+ });
13
+
14
+ test("has shell positional with allowable values", () => {
15
+ const cmd = new CompletionIntegrationSubCommand();
16
+ expect(cmd.positionals.length).toEqual(1);
17
+ expect(cmd.positionals[0]!.name).toEqual("shell");
18
+ expect(cmd.positionals[0]!.allowableValues).toEqual([
19
+ "bash",
20
+ "zsh",
21
+ "fish",
22
+ "powershell",
23
+ ]);
24
+ });
25
+
26
+ test("has optional config-path option", () => {
27
+ const cmd = new CompletionIntegrationSubCommand();
28
+ expect(cmd.options.length).toEqual(1);
29
+ expect(cmd.options[0]!.name).toEqual("config-path");
30
+ expect(cmd.options[0]!.isOptional).toEqual(true);
31
+ });
32
+ });
33
+
34
+ describe("CompletionCompleteSubCommand", () => {
35
+ test("has correct name", () => {
36
+ const cmd = new CompletionCompleteSubCommand();
37
+ expect(cmd.name).toEqual("complete");
38
+ });
39
+
40
+ test("has shell positional and vararg args positional", () => {
41
+ const cmd = new CompletionCompleteSubCommand();
42
+ expect(cmd.positionals.length).toEqual(2);
43
+ expect(cmd.positionals[0]!.name).toEqual("shell");
44
+ expect(cmd.positionals[1]!.name).toEqual("args");
45
+ expect(cmd.positionals[1]!.isVarargMultiple).toEqual(true);
46
+ expect(cmd.positionals[1]!.isVarargOptional).toEqual(true);
47
+ });
48
+ });
49
+
50
+ describe("CompletionGroupCommand", () => {
51
+ test("has correct name", () => {
52
+ const cmd = new CompletionGroupCommand(
53
+ new CompletionIntegrationSubCommand(),
54
+ new CompletionCompleteSubCommand(),
55
+ );
56
+ expect(cmd.name).toEqual("completions");
57
+ });
58
+
59
+ test("has two member sub-commands", () => {
60
+ const cmd = new CompletionGroupCommand(
61
+ new CompletionIntegrationSubCommand(),
62
+ new CompletionCompleteSubCommand(),
63
+ );
64
+ expect(cmd.memberSubCommands.length).toEqual(2);
65
+ expect(cmd.memberSubCommands[0]!.name).toEqual("integration");
66
+ expect(cmd.memberSubCommands[1]!.name).toEqual("complete");
67
+ });
68
+ });
@@ -9,8 +9,8 @@ import type Context from "../../src/api/Context.ts";
9
9
  import type Command from "../../src/api/command/Command.ts";
10
10
  import StreamString from "../fixtures/StreamString.ts";
11
11
  import { expectStringEquals } from "../fixtures/util.ts";
12
- import TtyTerminal from "../../src/service/printer/terminal/TtyTerminal.ts";
13
- import TtyStyler from "../../src/service/printer/terminal/TtyStyler.ts";
12
+ import TtyTerminal from "../../src/terminal/TtyTerminal.ts";
13
+ import TtyStyler from "../../src/terminal/TtyStyler.ts";
14
14
 
15
15
  describe("UsageCommand tests", () => {
16
16
  test("Usage works", async () => {
@@ -21,6 +21,7 @@ describe("UsageCommand tests", () => {
21
21
  dummyStderr.writableStream,
22
22
  true,
23
23
  true,
24
+ new TtyTerminal(dummyStdout.writeStream),
24
25
  new TtyTerminal(dummyStderr.writeStream),
25
26
  new TtyStyler(3),
26
27
  );
@@ -6,8 +6,8 @@ import { PRINTER_SERVICE_ID } from "../../src/api/service/core/PrinterService.ts
6
6
  import DefaultPrinterService from "../../src/service/printer/DefaultPrinterService.ts";
7
7
  import StreamString from "../fixtures/StreamString.ts";
8
8
  import { expectStringEquals } from "../fixtures/util.ts";
9
- import TtyTerminal from "../../src/service/printer/terminal/TtyTerminal.ts";
10
- import TtyStyler from "../../src/service/printer/terminal/TtyStyler.ts";
9
+ import TtyTerminal from "../../src/terminal/TtyTerminal.ts";
10
+ import TtyStyler from "../../src/terminal/TtyStyler.ts";
11
11
 
12
12
  describe("VersionCommand tests", () => {
13
13
  test("Version works", async () => {
@@ -18,6 +18,7 @@ describe("VersionCommand tests", () => {
18
18
  dummyStderr.writableStream,
19
19
  true,
20
20
  true,
21
+ new TtyTerminal(dummyStdout.writeStream),
21
22
  new TtyTerminal(dummyStderr.writeStream),
22
23
  new TtyStyler(3),
23
24
  );
@@ -6,8 +6,10 @@ import DefaultKeyValueService from "../../src/service/configuration/DefaultKeyVa
6
6
  import { KEY_VALUE_SERVICE_ID } from "../../src/api/service/core/KeyValueService.ts";
7
7
  import type Context from "../../src/api/Context.ts";
8
8
  import WritableStreamString from "./StreamString.ts";
9
- import TtyTerminal from "../../src/service/printer/terminal/TtyTerminal.ts";
10
- import TtyStyler from "../../src/service/printer/terminal/TtyStyler.ts";
9
+ import TtyTerminal from "../../src/terminal/TtyTerminal.ts";
10
+ import TtyStyler from "../../src/terminal/TtyStyler.ts";
11
+ import { TABLE_GENERATOR_SERVICE_ID } from "../../src/api/service/core/TableGeneratorService.ts";
12
+ import DefaultTableGeneratorService from "../../src/service/tableGenerator/DefaultTableGeneratorService.ts";
11
13
 
12
14
  export function getContext(
13
15
  streamString: WritableStreamString,
@@ -19,6 +21,7 @@ export function getContext(
19
21
  false,
20
22
  false,
21
23
  new TtyTerminal(streamString.writeStream),
24
+ new TtyTerminal(streamString.writeStream),
22
25
  new TtyStyler(3),
23
26
  );
24
27
 
@@ -28,6 +31,10 @@ export function getContext(
28
31
  KEY_VALUE_SERVICE_ID,
29
32
  new DefaultKeyValueService(),
30
33
  );
34
+ defaultContext.addServiceInstance(
35
+ TABLE_GENERATOR_SERVICE_ID,
36
+ new DefaultTableGeneratorService(),
37
+ );
31
38
 
32
39
  return defaultContext;
33
40
  }
@@ -20,7 +20,7 @@ export function expectStringEquals(actual: string, expected: string) {
20
20
  }
21
21
 
22
22
  export function expectBytesEquals(actual: string, expected: Uint8Array) {
23
- expect(encoder.encode(actual)).toEqual(expected);
23
+ expect(encoder.encode(actual)).toEqual(expected as Uint8Array<ArrayBuffer>);
24
24
  }
25
25
 
26
26
  export function sleep(ms: number) {
@@ -90,6 +90,77 @@ describe("CommandValidator tests", () => {
90
90
  .toThrow();
91
91
  });
92
92
 
93
+ test(
94
+ "SubCommand validation fails due to boolean argument type not supporting allowable values",
95
+ () => {
96
+ const command = getSubCommand("command", [{
97
+ name: "foo",
98
+ type: ArgumentValueTypeName.BOOLEAN,
99
+ allowableValues: ["goo"],
100
+ }], []);
101
+
102
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
103
+ .toThrow();
104
+ },
105
+ );
106
+
107
+ test(
108
+ "SubCommand validation fails due to boolean argument type not supporting case sensitivity",
109
+ () => {
110
+ const command = getSubCommand("command", [{
111
+ name: "foo",
112
+ type: ArgumentValueTypeName.BOOLEAN,
113
+ isCaseInsensitive: true,
114
+ }], []);
115
+
116
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
117
+ .toThrow();
118
+ },
119
+ );
120
+
121
+ test(
122
+ "SubCommand validation fails due to number argument type not supporting case insensitivity",
123
+ () => {
124
+ const command = getSubCommand("command", [{
125
+ name: "foo",
126
+ type: ArgumentValueTypeName.NUMBER,
127
+ isCaseInsensitive: false,
128
+ }], []);
129
+
130
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
131
+ .toThrow();
132
+ },
133
+ );
134
+
135
+ test(
136
+ "SubCommand validation fails due to string argument type not supporting value range",
137
+ () => {
138
+ const command = getSubCommand("command", [{
139
+ name: "foo",
140
+ type: ArgumentValueTypeName.STRING,
141
+ minValueInclusive: 1,
142
+ }], []);
143
+
144
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
145
+ .toThrow();
146
+ },
147
+ );
148
+
149
+ test(
150
+ "SubCommand validation fails due to number argument type not supporting value range and allowable values",
151
+ () => {
152
+ const command = getSubCommand("command", [{
153
+ name: "foo",
154
+ type: ArgumentValueTypeName.NUMBER,
155
+ allowableValues: [1],
156
+ minValueInclusive: 1,
157
+ }], []);
158
+
159
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
160
+ .toThrow();
161
+ },
162
+ );
163
+
93
164
  test(
94
165
  "SubCommand validation fails due to option default value" +
95
166
  " not matching any values specified in argument valid values",
@@ -106,6 +177,38 @@ describe("CommandValidator tests", () => {
106
177
  },
107
178
  );
108
179
 
180
+ test(
181
+ "SubCommand validation fails due to option default value" +
182
+ " not matching any values specified in argument valid values with case sensitivity",
183
+ () => {
184
+ const command = getSubCommand("command", [{
185
+ name: "foo",
186
+ type: ArgumentValueTypeName.STRING,
187
+ defaultValue: "bar",
188
+ allowableValues: ["BAR"],
189
+ }], []);
190
+
191
+ expect(() => new CommandValidator(getCLIConfig()).validate(command))
192
+ .toThrow();
193
+ },
194
+ );
195
+
196
+ test(
197
+ "SubCommand validation successed with option default value" +
198
+ " matching value specified in argument valid values with case insensitivity",
199
+ () => {
200
+ const command = getSubCommand("command", [{
201
+ name: "foo",
202
+ type: ArgumentValueTypeName.STRING,
203
+ defaultValue: "bar",
204
+ allowableValues: ["BAR"],
205
+ isCaseInsensitive: true,
206
+ }], []);
207
+
208
+ new CommandValidator(getCLIConfig()).validate(command);
209
+ },
210
+ );
211
+
109
212
  test(
110
213
  "SubCommand validation fails due to the type of option defaultValue" +
111
214
  " not matching the type specified in argument type",
@@ -386,7 +386,7 @@ describe("parser tests", () => {
386
386
  },
387
387
  };
388
388
 
389
- ((argumentValues.alpha as Array<PopulatedArgumentValues>)[0].beta as Array<
389
+ ((argumentValues.alpha as Array<PopulatedArgumentValues>)[0]!.beta as Array<
390
390
  PopulatedArgumentValues
391
391
  >)[1] = {
392
392
  gamma: "foo2",
@@ -439,12 +439,14 @@ describe("parser tests", () => {
439
439
  };
440
440
 
441
441
  // these are still stored as strings as the validation (and type conversion) failed fast
442
- (((argumentValues.alpha as Array<PopulatedArgumentValues>)[0].beta as Array<
443
- PopulatedArgumentValues
444
- >)[1].delta as Array<ArgumentValueType>)[0] = "0";
445
- (((argumentValues.alpha as Array<PopulatedArgumentValues>)[0].beta as Array<
446
- PopulatedArgumentValues
447
- >)[1].delta as Array<ArgumentValueType>)[2] = "2";
442
+ (((argumentValues.alpha as Array<PopulatedArgumentValues>)[0]!
443
+ .beta as Array<
444
+ PopulatedArgumentValues
445
+ >)[1]!.delta as Array<ArgumentValueType>)[0] = "0";
446
+ (((argumentValues.alpha as Array<PopulatedArgumentValues>)[0]!
447
+ .beta as Array<
448
+ PopulatedArgumentValues
449
+ >)[1]!.delta as Array<ArgumentValueType>)[2] = "2";
448
450
 
449
451
  expectParseResult(parseResult, {
450
452
  command: subCommand,
@@ -14,7 +14,7 @@ function getServiceProvider(
14
14
  return {
15
15
  serviceId,
16
16
  servicePriority,
17
- provide(_cliConfig: CLIConfig): Promise<ServiceInfo> {
17
+ getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
18
18
  return Promise.resolve({
19
19
  service: {},
20
20
  commands: [],
@@ -36,7 +36,7 @@ describe("DefaultServiceProviderRegistry tests", () => {
36
36
  serviceProviderRegistry.addServiceProvider(serviceProvider2);
37
37
 
38
38
  const orderedServices = serviceProviderRegistry.getServiceProviders();
39
- expect(orderedServices[0].servicePriority).toEqual(2);
40
- expect(orderedServices[1].servicePriority).toEqual(1);
39
+ expect(orderedServices[0]!.servicePriority).toEqual(2);
40
+ expect(orderedServices[1]!.servicePriority).toEqual(1);
41
41
  });
42
42
  });
@@ -689,6 +689,42 @@ describe("runner tests", () => {
689
689
  expect(hasRun).toBeFalse();
690
690
  });
691
691
 
692
+ test("No command recognised", async () => {
693
+ const streamString = new StreamString();
694
+ let hasRun = false;
695
+ const option = {
696
+ name: "foo",
697
+ type: ArgumentValueTypeName.STRING,
698
+ shortAlias: "f",
699
+ };
700
+ const subCommand = getSubCommand("command", [option], []);
701
+
702
+ subCommand.execute = (): Promise<void> => {
703
+ hasRun = true;
704
+ return Promise.resolve();
705
+ };
706
+
707
+ const commandRegistry = new DefaultCommandRegistry([subCommand]);
708
+ const runResult = await run(
709
+ ["blah", "comman", "ommand"],
710
+ commandRegistry,
711
+ getServiceProviderRegistry(),
712
+ undefined,
713
+ getContext(streamString),
714
+ );
715
+
716
+ expect(runResult.runState).toEqual(RunState.NO_COMMAND);
717
+ expectStringIncludes(
718
+ streamString.getString(),
719
+ "No command recognised",
720
+ );
721
+ expectStringIncludes(
722
+ streamString.getString(),
723
+ "Possible matches: command\n",
724
+ );
725
+ expect(hasRun).toBeFalse();
726
+ });
727
+
692
728
  test("Unknown arg warning in non-global run", async () => {
693
729
  const streamString = new StreamString();
694
730
  let hasRun = false;