@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.
- package/.github/workflows/release-bun-library.yml +1 -0
- package/README/core-commands.md +40 -0
- package/README/core-service-providers.md +198 -0
- package/README/development.md +60 -0
- package/README/implementation-details.md +474 -0
- package/README/key-concepts.md +751 -0
- package/README.md +12 -1413
- package/bun.lock +20 -18
- package/index.ts +98 -1
- package/package.json +8 -7
- package/src/api/BaseCLIFeatureOptions.ts +10 -0
- package/src/api/CLIConfig.ts +5 -0
- package/src/api/RunResult.ts +16 -0
- package/src/api/argument/Argument.ts +40 -1
- package/src/api/argument/ArgumentValueTypes.ts +1 -1
- package/src/api/command/SubCommand.ts +1 -1
- package/src/api/service/ServiceProvider.ts +3 -3
- package/src/api/service/core/ArgumentPrompterService.ts +10 -0
- package/src/api/service/core/AsciiBannerGeneratorService.ts +44 -7
- package/src/api/service/core/CompletionService.ts +36 -0
- package/src/api/service/core/DataDumpGeneratorService.ts +27 -0
- package/src/api/service/core/ImagePrinterService.ts +26 -0
- package/src/api/service/core/KeyValueService.ts +22 -5
- package/src/api/service/core/PrinterService.ts +127 -1
- package/src/api/service/core/PrompterService.ts +49 -0
- package/src/api/service/core/SecretService.ts +27 -0
- package/src/api/service/core/ShutdownService.ts +17 -1
- package/src/api/service/core/Table.ts +85 -0
- package/src/api/service/core/TableGeneratorService.ts +38 -0
- package/src/api/service/core/TreePrinterService.ts +11 -0
- package/src/cli/BaseCLI.ts +116 -44
- package/src/cli/DefaultRuntimeCLI.ts +10 -18
- package/src/command/CompletionCommand.ts +179 -0
- package/src/command/MultiCommandCliHelpCommand.ts +20 -32
- package/src/command/SingleCommandCliHelpCommand.ts +10 -1
- package/src/launcher.ts +16 -58
- package/src/runtime/command/CommandValidator.ts +70 -6
- package/src/runtime/runner.ts +112 -31
- package/src/runtime/scanner.ts +18 -15
- package/src/runtime/values/argumentValueValidation.ts +93 -10
- package/src/runtime/values/globalCommandValuePopulation.ts +1 -1
- package/src/runtime/values/subCommandValuePopulation.ts +9 -8
- package/src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts +34 -0
- package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +330 -0
- package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +1 -1
- package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +254 -17
- package/src/service/banner/BannerServiceProvider.ts +2 -2
- package/src/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.ts +85 -0
- package/src/service/chiselAsciiBannerGenerator/chisel.flf.json +3 -0
- package/src/service/completion/CompletionServiceProvider.ts +212 -0
- package/src/service/completion/DefaultCompletionService.ts +364 -0
- package/src/service/completion/shell/ShellHandler.ts +16 -0
- package/src/service/completion/shell/bash.ts +47 -0
- package/src/service/completion/shell/fish.ts +43 -0
- package/src/service/completion/shell/powershell.ts +66 -0
- package/src/service/completion/shell/zsh.ts +50 -0
- package/src/service/configuration/ConfigurationServiceProvider.ts +138 -42
- package/src/service/configuration/DefaultKeyValueService.ts +53 -7
- package/src/service/configuration/DefaultSecretService.ts +85 -0
- package/src/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.ts +41 -0
- package/src/service/dataDumpGenerator/DefaultDataDumpGeneratorService.ts +80 -0
- package/src/service/imagePrinter/DefaultImagePrinterService.ts +23 -0
- package/src/service/imagePrinter/ImagePrinterServiceProvider.ts +32 -0
- package/src/service/prettyPrinter/PrettyPrinterServiceProvider.ts +1 -1
- package/src/service/printer/DefaultPrinterService.ts +147 -27
- package/src/service/printer/PrinterServiceProvider.ts +7 -7
- package/src/service/printer/command/LogLevelCommand.ts +2 -1
- package/src/service/printer/command/NoHyperlinksCommand.ts +40 -0
- package/src/service/printer/terminal/Progress.ts +21 -4
- package/src/service/printer/terminal/Spinner.ts +32 -8
- package/src/service/printer/terminal/Theme.ts +1 -1
- package/src/service/prompter/DefaultPrompterService.ts +87 -0
- package/src/service/prompter/PrompterServiceProvider.ts +36 -0
- package/src/service/prompter/command/NoPromptCommand.ts +40 -0
- package/src/service/prompter/prompt/PromptContext.ts +47 -0
- package/src/service/prompter/prompt/promptAcknowledge.ts +28 -0
- package/src/service/prompter/prompt/promptMultiSelect.ts +99 -0
- package/src/service/prompter/prompt/promptOpenUrl.ts +97 -0
- package/src/service/prompter/prompt/promptSingleSelect.ts +102 -0
- package/src/service/prompter/prompt/promptText.ts +107 -0
- package/src/service/prompter/prompt/promptToggle.ts +28 -0
- package/src/service/shutdown/DefaultShutdownService.ts +13 -0
- package/src/service/shutdown/ShutdownServiceProvider.ts +28 -9
- package/src/service/shutdown/ShutdownState.ts +5 -0
- package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +1 -1
- package/src/service/tableGenerator/DefaultTableGeneratorService.ts +415 -0
- package/src/service/tableGenerator/TableGeneratorServiceProvider.ts +43 -0
- package/src/service/treePrinter/DefaultTreePrinterService.ts +49 -0
- package/src/service/treePrinter/TreePrinterServiceProvider.ts +40 -0
- package/src/terminal/Ansi.ts +27 -0
- package/src/terminal/ImageRenderer.ts +199 -0
- package/src/terminal/KeyReader.ts +23 -0
- package/src/terminal/PngDecoder.ts +143 -0
- package/src/{service/printer/terminal → terminal}/Styler.ts +3 -0
- package/src/{service/printer/terminal → terminal}/Terminal.ts +1 -0
- package/src/terminal/TtyKeyReader.ts +83 -0
- package/src/terminal/TtyStyler.ts +78 -0
- package/src/{service/printer/terminal → terminal}/TtyTerminal.ts +9 -1
- package/src/terminal/supportsHyperlinks.ts +110 -0
- package/src/util/configHelper.ts +2 -2
- package/src/util/envVarHelper.ts +2 -2
- package/src/util/helpHelper.ts +139 -123
- package/src/util/logger.ts +16 -14
- package/src/util/runnerHelper.ts +41 -0
- package/tests/cli/BaseCLI.test.ts +50 -54
- package/tests/command/CompletionCommand.test.ts +68 -0
- package/tests/command/UsageCommand.test.ts +3 -2
- package/tests/command/VersionCommand.test.ts +3 -2
- package/tests/fixtures/Context.ts +9 -2
- package/tests/fixtures/util.ts +1 -1
- package/tests/runtime/command/CommandValidator.test.ts +103 -0
- package/tests/runtime/parser.test.ts +9 -7
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
- package/tests/runtime/runner.test.ts +36 -0
- package/tests/runtime/values/argumentValueValidation.test.ts +215 -1
- package/tests/service/argumentPrompter/ArgumentPrompterServiceProvider.test.ts +47 -0
- package/tests/service/argumentPrompter/DefaultArgumentPrompterService.test.ts +754 -0
- package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +5 -4
- package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +163 -3
- package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +1 -1
- package/tests/service/banner/BannerServiceProvider.test.ts +8 -4
- package/tests/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.test.ts +52 -0
- package/tests/service/completion/CompletionServiceProvider.test.ts +404 -0
- package/tests/service/completion/DefaultCompletionService.test.ts +278 -0
- package/tests/service/completion/shell/bash.test.ts +73 -0
- package/tests/service/completion/shell/fish.test.ts +62 -0
- package/tests/service/completion/shell/powershell.test.ts +59 -0
- package/tests/service/completion/shell/zsh.test.ts +60 -0
- package/tests/service/configuration/ConfigurationServiceProvider.test.ts +24 -12
- package/tests/service/configuration/DefaultKeyValueService.test.ts +118 -16
- package/tests/service/configuration/DefaultSecretService.test.ts +148 -0
- package/tests/service/configuration/command/ConfigCommand.test.ts +29 -0
- package/tests/service/configuration/command/DumpConfigCommand.test.ts +60 -0
- package/tests/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.test.ts +57 -0
- package/tests/service/dataDumpGenerator/DefaultDataDumpGeneratorService.test.ts +217 -0
- package/tests/service/imagePrinter/DefaultImagePrinterService.test.ts +96 -0
- package/tests/service/imagePrinter/ImagePrinterServiceProvider.test.ts +17 -0
- package/tests/service/prettyPrinter/DefaultPrettyPrinterService.test.ts +2 -2
- package/tests/service/prettyPrinter/PrettyPrinterServiceProvider.test.ts +5 -4
- package/tests/service/printer/DefaultPrinterService.test.ts +233 -2
- package/tests/service/printer/PrinterServiceProvider.test.ts +6 -5
- package/tests/service/printer/command/DarkModeCommand.test.ts +83 -0
- package/tests/service/printer/command/LogLevelCommand.test.ts +8 -6
- package/tests/service/printer/command/NoColorCommand.test.ts +83 -0
- package/tests/service/printer/command/NoHyperlinksCommand.test.ts +89 -0
- package/tests/service/printer/terminal/Progress.test.ts +64 -4
- package/tests/service/printer/terminal/Spinner.test.ts +44 -3
- package/tests/service/prompter/DefaultPrompterService.test.ts +615 -0
- package/tests/service/prompter/PrompterServiceProvider.test.ts +47 -0
- package/tests/service/prompter/command/NoPromptCommand.test.ts +58 -0
- package/tests/service/shutdown/ShutdownServiceProvider.test.ts +4 -2
- package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +3 -2
- package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +5 -4
- package/tests/service/tableGenerator/DefaultTableGeneratorService.test.ts +363 -0
- package/tests/service/tableGenerator/TableGeneratorServiceProvider.test.ts +47 -0
- package/tests/service/treePrinter/DefaultTreePrinterService.test.ts +144 -0
- package/tests/service/treePrinter/TreePrinterServiceProvider.test.ts +55 -0
- package/tests/terminal/ImageRenderer.test.ts +161 -0
- package/tests/terminal/PngDecoder.test.ts +223 -0
- package/tests/terminal/supportsHyperlinks.test.ts +149 -0
- package/tests/util/configHelper.test.ts +4 -4
- package/tests/util/logger.test.ts +23 -1
- package/tsconfig.json +31 -0
- package/src/service/printer/terminal/Ansi.ts +0 -10
- 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/
|
|
27
|
-
import TtyStyler from "../../src/
|
|
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
|
-
|
|
47
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
48
|
+
new TtyTerminal(dummyStderr.writeStream),
|
|
42
49
|
new TtyStyler(3),
|
|
43
|
-
|
|
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
|
-
|
|
70
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
71
|
+
new TtyTerminal(dummyStderr.writeStream),
|
|
65
72
|
new TtyStyler(3),
|
|
66
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
expect(this.copyOfKeyValueService!.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
307
|
-
|
|
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/
|
|
13
|
-
import TtyStyler from "../../src/
|
|
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/
|
|
10
|
-
import TtyStyler from "../../src/
|
|
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/
|
|
10
|
-
import TtyStyler from "../../src/
|
|
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
|
}
|
package/tests/fixtures/util.ts
CHANGED
|
@@ -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]
|
|
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]
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
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]
|
|
40
|
-
expect(orderedServices[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;
|