@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
@@ -286,7 +286,7 @@ describe("argumentValueValidation tests", () => {
286
286
  }]);
287
287
  });
288
288
 
289
- test("Invalid option argument value", () => {
289
+ test("Invalid option argument value - not an allowable value", () => {
290
290
  let option: Option = {
291
291
  name: "foo",
292
292
  type: ArgumentValueTypeName.STRING,
@@ -312,6 +312,27 @@ describe("argumentValueValidation tests", () => {
312
312
  }]);
313
313
  });
314
314
 
315
+ test("Invalid option argument value - not within range", () => {
316
+ const option: Option = {
317
+ name: "foo",
318
+ type: ArgumentValueTypeName.INTEGER,
319
+ minValueInclusive: 1,
320
+ };
321
+ const invalidArguments: Array<InvalidArgument> = [];
322
+ expect(validateOptionValue(option, "1", invalidArguments), "1");
323
+ expect(invalidArguments).toEqual([]);
324
+
325
+ expect(
326
+ validateOptionValue(option, "0", invalidArguments),
327
+ ).toBeUndefined();
328
+ expect(invalidArguments).toEqual([{
329
+ argument: option,
330
+ name: "foo",
331
+ value: "0",
332
+ reason: InvalidArgumentReason.ILLEGAL_VALUE,
333
+ }]);
334
+ });
335
+
315
336
  test("Complex options", () => {
316
337
  const option: ComplexOption = {
317
338
  name: "foo",
@@ -973,6 +994,199 @@ describe("argumentValueValidation tests", () => {
973
994
  }]);
974
995
  });
975
996
 
997
+ test("Option with custom validator that passes", () => {
998
+ const option: Option = {
999
+ name: "foo",
1000
+ type: ArgumentValueTypeName.STRING,
1001
+ validate: () => undefined,
1002
+ };
1003
+ const invalidArguments: Array<InvalidArgument> = [];
1004
+ expect(validateOptionValue(option, "hello", invalidArguments)).toEqual(
1005
+ "hello",
1006
+ );
1007
+ expect(invalidArguments).toEqual([]);
1008
+ });
1009
+
1010
+ test("Option with custom validator that fails", () => {
1011
+ const option: Option = {
1012
+ name: "foo",
1013
+ type: ArgumentValueTypeName.STRING,
1014
+ validate: (v) => (v as string).length < 3 ? "min 3 chars" : undefined,
1015
+ };
1016
+ const invalidArguments: Array<InvalidArgument> = [];
1017
+ expect(
1018
+ validateOptionValue(option, "ab", invalidArguments),
1019
+ ).toBeUndefined();
1020
+ expect(invalidArguments).toEqual([{
1021
+ argument: option,
1022
+ name: "foo",
1023
+ value: "ab",
1024
+ reason: InvalidArgumentReason.CUSTOM_VALIDATION,
1025
+ message: "min 3 chars",
1026
+ }]);
1027
+ });
1028
+
1029
+ test("Positional with custom validator that passes and fails", () => {
1030
+ const positional: Positional = {
1031
+ name: "count",
1032
+ type: ArgumentValueTypeName.NUMBER,
1033
+ validate: (v) => (v as number) % 2 !== 0 ? "must be even" : undefined,
1034
+ };
1035
+ let invalidArguments: Array<InvalidArgument> = [];
1036
+ expect(
1037
+ validatePositionalValue(positional, "4", invalidArguments),
1038
+ ).toEqual(4);
1039
+ expect(invalidArguments).toEqual([]);
1040
+
1041
+ invalidArguments = [];
1042
+ expect(
1043
+ validatePositionalValue(positional, "3", invalidArguments),
1044
+ ).toBeUndefined();
1045
+ expect(invalidArguments).toEqual([{
1046
+ argument: positional,
1047
+ name: "count",
1048
+ value: 3,
1049
+ reason: InvalidArgumentReason.CUSTOM_VALIDATION,
1050
+ message: "must be even",
1051
+ }]);
1052
+ });
1053
+
1054
+ test("GlobalCommand argument with custom validator that passes and fails", () => {
1055
+ const globalCommandArgument: GlobalCommandArgument = {
1056
+ type: ArgumentValueTypeName.STRING,
1057
+ validate: (v) =>
1058
+ (v as string).startsWith("x") ? undefined : "must start with x",
1059
+ };
1060
+ const globalCommand = getGlobalCommandWithShortAlias(
1061
+ "globalCommand",
1062
+ "f",
1063
+ globalCommandArgument,
1064
+ );
1065
+ let invalidArguments: Array<InvalidArgument> = [];
1066
+ expect(
1067
+ validateGlobalCommandArgumentValue(
1068
+ globalCommand,
1069
+ "xfoo",
1070
+ invalidArguments,
1071
+ ),
1072
+ ).toEqual("xfoo");
1073
+ expect(invalidArguments).toEqual([]);
1074
+
1075
+ invalidArguments = [];
1076
+ expect(
1077
+ validateGlobalCommandArgumentValue(
1078
+ globalCommand,
1079
+ "bar",
1080
+ invalidArguments,
1081
+ ),
1082
+ ).toBeUndefined();
1083
+ expect(invalidArguments).toEqual([{
1084
+ argument: globalCommandArgument,
1085
+ name: "globalCommand",
1086
+ value: "bar",
1087
+ reason: InvalidArgumentReason.CUSTOM_VALIDATION,
1088
+ message: "must start with x",
1089
+ }]);
1090
+ });
1091
+
1092
+ test("Option isArray with custom validator checking uniqueness", () => {
1093
+ const option: Option = {
1094
+ name: "tags",
1095
+ type: ArgumentValueTypeName.STRING,
1096
+ isArray: true,
1097
+ validate: (v) => {
1098
+ const arr = v as string[];
1099
+ return new Set(arr).size !== arr.length
1100
+ ? "values must be unique"
1101
+ : undefined;
1102
+ },
1103
+ };
1104
+ let invalidArguments: Array<InvalidArgument> = [];
1105
+ expect(
1106
+ validateOptionValue(option, ["a", "b", "c"], invalidArguments),
1107
+ ).toEqual(["a", "b", "c"]);
1108
+ expect(invalidArguments).toEqual([]);
1109
+
1110
+ invalidArguments = [];
1111
+ expect(
1112
+ validateOptionValue(option, ["a", "b", "a"], invalidArguments),
1113
+ ).toBeUndefined();
1114
+ expect(invalidArguments).toEqual([{
1115
+ argument: option,
1116
+ name: "tags",
1117
+ value: ["a", "b", "a"],
1118
+ reason: InvalidArgumentReason.CUSTOM_VALIDATION,
1119
+ message: "values must be unique",
1120
+ }]);
1121
+ });
1122
+
1123
+ test("Positional isVarargMultiple with custom validator checking uniqueness", () => {
1124
+ const positional: Positional = {
1125
+ name: "ids",
1126
+ type: ArgumentValueTypeName.NUMBER,
1127
+ isVarargMultiple: true,
1128
+ validate: (v) => {
1129
+ const arr = v as number[];
1130
+ return new Set(arr).size !== arr.length
1131
+ ? "values must be unique"
1132
+ : undefined;
1133
+ },
1134
+ };
1135
+ let invalidArguments: Array<InvalidArgument> = [];
1136
+ expect(
1137
+ validatePositionalValue(positional, ["1", "2", "3"], invalidArguments),
1138
+ ).toEqual([1, 2, 3]);
1139
+ expect(invalidArguments).toEqual([]);
1140
+
1141
+ invalidArguments = [];
1142
+ expect(
1143
+ validatePositionalValue(positional, ["1", "2", "1"], invalidArguments),
1144
+ ).toBeUndefined();
1145
+ expect(invalidArguments).toEqual([{
1146
+ argument: positional,
1147
+ name: "ids",
1148
+ value: [1, 2, 1],
1149
+ reason: InvalidArgumentReason.CUSTOM_VALIDATION,
1150
+ message: "values must be unique",
1151
+ }]);
1152
+ });
1153
+
1154
+ test("Custom validator not called when built-in validation fails", () => {
1155
+ const option: Option = {
1156
+ name: "foo",
1157
+ type: ArgumentValueTypeName.NUMBER,
1158
+ validate: () => {
1159
+ throw new Error("should not be called");
1160
+ },
1161
+ };
1162
+ const invalidArguments: Array<InvalidArgument> = [];
1163
+ expect(
1164
+ validateOptionValue(option, "notanumber", invalidArguments),
1165
+ ).toBeUndefined();
1166
+ expect(invalidArguments).toEqual([{
1167
+ argument: option,
1168
+ name: "foo",
1169
+ value: "notanumber",
1170
+ reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
1171
+ }]);
1172
+ });
1173
+
1174
+ test("Custom validator not called when value is undefined and optional", () => {
1175
+ const option: Option = {
1176
+ name: "foo",
1177
+ type: ArgumentValueTypeName.STRING,
1178
+ isOptional: true,
1179
+ validate: () => {
1180
+ throw new Error("should not be called");
1181
+ },
1182
+ };
1183
+ const invalidArguments: Array<InvalidArgument> = [];
1184
+ expect(
1185
+ validateOptionValue(option, undefined, invalidArguments),
1186
+ ).toBeUndefined();
1187
+ expect(invalidArguments).toEqual([]);
1188
+ });
1189
+
976
1190
  test("Invalid global command argument value", () => {
977
1191
  let globalCommandArgument: GlobalCommandArgument = {
978
1192
  type: ArgumentValueTypeName.STRING,
@@ -0,0 +1,47 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import ArgumentPrompterServiceProvider from "../../../src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts";
3
+ import { ARGUMENT_PROMPTER_SERVICE_ID } from "../../../src/api/service/core/ArgumentPrompterService.ts";
4
+ import type ArgumentPrompterService from "../../../src/api/service/core/ArgumentPrompterService.ts";
5
+ import DefaultContext from "../../../src/runtime/DefaultContext.ts";
6
+ import { getCLIConfig } from "../../fixtures/CLIConfig.ts";
7
+
8
+ function getMockArgumentPrompterService(): ArgumentPrompterService {
9
+ return {
10
+ promptForMissingArguments: (parseResult) => Promise.resolve(parseResult),
11
+ };
12
+ }
13
+
14
+ describe("ArgumentPrompterServiceProvider tests", () => {
15
+ test("ArgumentPrompterServiceProvider has correct serviceId", () => {
16
+ const provider = new ArgumentPrompterServiceProvider(
17
+ 100,
18
+ getMockArgumentPrompterService(),
19
+ );
20
+
21
+ expect(provider.serviceId).toEqual(ARGUMENT_PROMPTER_SERVICE_ID);
22
+ });
23
+
24
+ test("ArgumentPrompterServiceProvider getServiceInfo returns no commands", async () => {
25
+ const argPrompterService = getMockArgumentPrompterService();
26
+ const provider = new ArgumentPrompterServiceProvider(
27
+ 100,
28
+ argPrompterService,
29
+ );
30
+ const cliConfig = getCLIConfig();
31
+
32
+ const serviceInfo = await provider.getServiceInfo(cliConfig);
33
+
34
+ expect(serviceInfo.service).toBe(argPrompterService);
35
+ expect(serviceInfo.commands.length).toEqual(0);
36
+ });
37
+
38
+ test("ArgumentPrompterServiceProvider initService resolves", async () => {
39
+ const provider = new ArgumentPrompterServiceProvider(
40
+ 100,
41
+ getMockArgumentPrompterService(),
42
+ );
43
+ const context = new DefaultContext(getCLIConfig());
44
+
45
+ await expect(provider.initService(context)).resolves.toBeUndefined();
46
+ });
47
+ });