@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,394 @@
1
+ import type Positional from "../api/argument/Positional.ts";
2
+ import type Option from "../api/argument/Option.ts";
3
+ import type SubCommand from "../api/command/SubCommand.ts";
4
+ import type GlobalCommand from "../api/command/GlobalCommand.ts";
5
+ import type GroupCommand from "../api/command/GroupCommand.ts";
6
+ import { distance } from "fastest-levenshtein";
7
+ import {
8
+ type ArgumentSingleValueType,
9
+ type ArgumentValues,
10
+ ArgumentValueTypeName,
11
+ } from "../api/argument/ArgumentValueTypes.ts";
12
+ import type Context from "../api/Context.ts";
13
+ import type { HelpSection } from "../util/helpHelper.ts";
14
+ import {
15
+ getCommandArgsHelpSections,
16
+ getCommandExamplesHelpSections,
17
+ getGlobalArgumentHelpEntry,
18
+ getMultiCommandAppSyntax,
19
+ getSubCommandArgumentsSyntax,
20
+ printHelpSections,
21
+ } from "../util/helpHelper.ts";
22
+ import type GlobalModifierCommand from "../api/command/GlobalModifierCommand.ts";
23
+ import type CommandRegistry from "../runtime/registry/CommandRegistry.ts";
24
+ import type PrinterService from "../api/service/core/PrinterService.ts";
25
+ import {
26
+ Icon,
27
+ PRINTER_SERVICE_ID,
28
+ } from "../api/service/core/PrinterService.ts";
29
+
30
+ /**
31
+ * Provides common implementation for both {@link MultiCommandCliHelpGlobalCommand} and {@link MultiCommandCliHelpSubCommand}.
32
+ */
33
+ abstract class MultiCommandCliAbstractHelpCommand {
34
+ readonly name = "help";
35
+ readonly description = "Display application help";
36
+ readonly #includeEnvVars: boolean;
37
+ readonly #commandRegistry: CommandRegistry;
38
+
39
+ constructor(
40
+ includeEnvVars: boolean,
41
+ commandRegistry: CommandRegistry,
42
+ ) {
43
+ this.#includeEnvVars = includeEnvVars;
44
+ this.#commandRegistry = commandRegistry;
45
+ }
46
+
47
+ #findPossibleCommandNames(
48
+ commandName: string,
49
+ groupCommands: ReadonlyArray<GroupCommand>,
50
+ subCommands: ReadonlyArray<SubCommand>,
51
+ ): string[] {
52
+ const levenCommandArray = new Array<[number, string]>();
53
+ subCommands.forEach((subCommand) => {
54
+ levenCommandArray.push([
55
+ distance(subCommand.name, commandName),
56
+ subCommand.name,
57
+ ]);
58
+ });
59
+ groupCommands.forEach((groupCommand) => {
60
+ groupCommand.memberSubCommands.forEach((memberCommand) => {
61
+ const memberName = `${groupCommand.name}:${memberCommand.name}`;
62
+ levenCommandArray.push([
63
+ distance(memberName, commandName),
64
+ memberName,
65
+ ]);
66
+ });
67
+ });
68
+ return levenCommandArray
69
+ .sort((a, b) => a[0] - b[0])
70
+ .slice(0, 2)
71
+ .filter((value) => value[0] < 3)
72
+ .map((value) => value[1]);
73
+ }
74
+
75
+ #getGlobalCommandsHelpSection(
76
+ context: Context,
77
+ title: string,
78
+ globalCommands: ReadonlyArray<GlobalCommand>,
79
+ ): HelpSection {
80
+ const globalCommandsSection: HelpSection = {
81
+ title,
82
+ helpEntries: [],
83
+ };
84
+ globalCommands.forEach((globalCommand) => {
85
+ const { syntax, description } = getGlobalArgumentHelpEntry(
86
+ context.cliConfig,
87
+ this.#includeEnvVars,
88
+ globalCommand,
89
+ );
90
+ globalCommandsSection.helpEntries.push({
91
+ syntax: `--${globalCommand.name} ${syntax}`,
92
+ description,
93
+ });
94
+ if (globalCommand.shortAlias !== undefined) {
95
+ globalCommandsSection.helpEntries.push({
96
+ syntax: `-${globalCommand.shortAlias} ${syntax}`,
97
+ description,
98
+ });
99
+ }
100
+ });
101
+ globalCommandsSection.helpEntries.sort((a, b) =>
102
+ a.syntax.localeCompare(b.syntax)
103
+ );
104
+ return globalCommandsSection;
105
+ }
106
+
107
+ #getGenericHelpSections(
108
+ context: Context,
109
+ globalModifierCommands: ReadonlyArray<GlobalModifierCommand>,
110
+ globalCommands: ReadonlyArray<GlobalCommand>,
111
+ groupCommands: ReadonlyArray<GroupCommand>,
112
+ subCommands: ReadonlyArray<SubCommand>,
113
+ ): Array<HelpSection> {
114
+ const globalPrefix =
115
+ ((groupCommands.length > 0) || (subCommands.length > 0)) ? "Global " : "";
116
+ const helpSections: HelpSection[] = [];
117
+ if (globalModifierCommands.length > 0) {
118
+ helpSections.push(
119
+ this.#getGlobalCommandsHelpSection(
120
+ context,
121
+ `${globalPrefix}Options`,
122
+ globalModifierCommands,
123
+ ),
124
+ );
125
+ }
126
+ if (globalCommands.length > 0) {
127
+ helpSections.push(
128
+ this.#getGlobalCommandsHelpSection(
129
+ context,
130
+ `${globalPrefix}Commands`,
131
+ globalCommands,
132
+ ),
133
+ );
134
+ }
135
+ groupCommands.forEach((groupCommand) => {
136
+ const topicSection: HelpSection = {
137
+ title: `${
138
+ groupCommand.name.charAt(0).toUpperCase() + groupCommand.name.slice(1)
139
+ } Commands`,
140
+ helpEntries: [],
141
+ };
142
+ groupCommand.memberSubCommands.forEach((memberCommand) => {
143
+ topicSection.helpEntries.push({
144
+ syntax: `${groupCommand.name}:${memberCommand.name}`,
145
+ description: memberCommand.description,
146
+ });
147
+ });
148
+ topicSection.helpEntries.sort((a, b) => a.syntax.localeCompare(b.syntax));
149
+ helpSections.push(topicSection);
150
+ });
151
+ if (subCommands.length > 0) {
152
+ const subCommandsByTopic = new Map<string, SubCommand[]>();
153
+ const otherSubCommands: SubCommand[] = [];
154
+
155
+ subCommands.forEach((subCommand) => {
156
+ if (subCommand.helpTopic === undefined) {
157
+ otherSubCommands.push(subCommand);
158
+ } else {
159
+ const commands = subCommandsByTopic.get(subCommand.helpTopic) || [];
160
+ commands.push(subCommand);
161
+ subCommandsByTopic.set(subCommand.helpTopic, commands);
162
+ }
163
+ });
164
+
165
+ for (const key of subCommandsByTopic.keys()) {
166
+ const topicCommands = subCommandsByTopic.get(key);
167
+ if (topicCommands !== undefined) {
168
+ const topicSection: HelpSection = {
169
+ title: `${key.charAt(0).toUpperCase() + key.slice(1)} Commands`,
170
+ helpEntries: [],
171
+ };
172
+ topicCommands.sort((a, b) => a.name.localeCompare(b.name));
173
+ topicCommands.forEach((subCommand) => {
174
+ topicSection.helpEntries.push({
175
+ syntax: subCommand.name,
176
+ description: subCommand.description,
177
+ });
178
+ });
179
+ helpSections.push(topicSection);
180
+ }
181
+ }
182
+
183
+ if (otherSubCommands.length > 0) {
184
+ const topicSection: HelpSection = {
185
+ title: (subCommandsByTopic.size > 0 || groupCommands.length > 0)
186
+ ? "Other Commands"
187
+ : "Sub-Commands",
188
+ helpEntries: [],
189
+ };
190
+ otherSubCommands.sort((a, b) => a.name.localeCompare(b.name));
191
+ otherSubCommands.forEach((subCommand) => {
192
+ topicSection.helpEntries.push({
193
+ syntax: subCommand.name,
194
+ description: subCommand.description,
195
+ });
196
+ });
197
+ helpSections.push(topicSection);
198
+ }
199
+ }
200
+ return helpSections;
201
+ }
202
+
203
+ protected async printGenericHelp(
204
+ context: Context,
205
+ ): Promise<void> {
206
+ const printerService = context.getServiceById(
207
+ PRINTER_SERVICE_ID,
208
+ ) as PrinterService;
209
+
210
+ const globalModifierCommands = this.#commandRegistry
211
+ .getGlobalModifierCommands();
212
+ const globalCommands = this.#commandRegistry.getGlobalCommands();
213
+ const groupCommands = this.#commandRegistry.getGroupCommands();
214
+ const subCommands = this.#commandRegistry.getSubCommands();
215
+ const helpSections: Array<HelpSection> = [];
216
+ helpSections.push({
217
+ title: "Usage",
218
+ helpEntries: [
219
+ {
220
+ syntax: getMultiCommandAppSyntax(
221
+ context,
222
+ globalModifierCommands,
223
+ globalCommands,
224
+ groupCommands,
225
+ subCommands,
226
+ ),
227
+ },
228
+ ],
229
+ });
230
+
231
+ helpSections.push(
232
+ ...this.#getGenericHelpSections(
233
+ context,
234
+ globalModifierCommands,
235
+ globalCommands,
236
+ groupCommands,
237
+ subCommands,
238
+ ),
239
+ );
240
+
241
+ await printHelpSections(printerService, helpSections);
242
+ }
243
+
244
+ protected async printUsageHelp(
245
+ context: Context,
246
+ commandName: string,
247
+ ): Promise<void> {
248
+ const printerService = context.getServiceById(
249
+ PRINTER_SERVICE_ID,
250
+ ) as PrinterService;
251
+
252
+ const groupCommands = this.#commandRegistry.getGroupCommands();
253
+ const subCommands = this.#commandRegistry.getSubCommands();
254
+
255
+ // find sub-command or group sub-command
256
+ let subCommand = this.#commandRegistry.getSubCommandByName(commandName);
257
+
258
+ let groupCommand: GroupCommand | undefined;
259
+
260
+ if (subCommand === undefined) {
261
+ const result = this.#commandRegistry
262
+ .getGroupCommandAndMemberSubCommandByJoinedName(commandName);
263
+ if (result !== undefined) {
264
+ groupCommand = result.groupCommand;
265
+ subCommand = result.command;
266
+ }
267
+ }
268
+
269
+ if (subCommand === undefined) {
270
+ await printerService.print(
271
+ `Unknown command: ${printerService.red(commandName)}\n\n`,
272
+ Icon.FAILURE,
273
+ );
274
+
275
+ // look for other possible matches
276
+ const possibleCommandNames = this.#findPossibleCommandNames(
277
+ commandName,
278
+ groupCommands,
279
+ subCommands,
280
+ );
281
+ if (possibleCommandNames.length > 0) {
282
+ await printerService.print(
283
+ `Possible matches: ${possibleCommandNames.join(", ")}\n\n`,
284
+ Icon.INFORMATION,
285
+ );
286
+ }
287
+ await this.printGenericHelp(context);
288
+ return;
289
+ }
290
+
291
+ // display command help
292
+ const name = (groupCommand === undefined)
293
+ ? subCommand.name
294
+ : `${groupCommand.name}:${subCommand.name}`;
295
+ const helpSections: HelpSection[] = [];
296
+
297
+ if (
298
+ (subCommand.description !== undefined) &&
299
+ (subCommand.description.length > 0)
300
+ ) {
301
+ helpSections.push({
302
+ title: "Command",
303
+ helpEntries: [{
304
+ syntax: subCommand.name,
305
+ description: subCommand.description,
306
+ }],
307
+ });
308
+ }
309
+ helpSections.push({
310
+ title: "Usage",
311
+ helpEntries: [{
312
+ syntax: `${context.cliConfig.name}${name.length > 0 ? ` ${name}` : ""}${
313
+ getSubCommandArgumentsSyntax(subCommand)
314
+ }`,
315
+ }],
316
+ });
317
+
318
+ helpSections.push(
319
+ ...getCommandArgsHelpSections(
320
+ context.cliConfig,
321
+ this.#includeEnvVars,
322
+ subCommand,
323
+ false,
324
+ ),
325
+ );
326
+
327
+ helpSections.push(
328
+ ...getCommandExamplesHelpSections(printerService, context, subCommand),
329
+ );
330
+
331
+ await printHelpSections(printerService, helpSections);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Implementation of multi-command CLI help available via `myCli --help`, `myCli -h`, `myCli --help <command>` or `myCli -h <command>`.
337
+ */
338
+ export class MultiCommandCliHelpGlobalCommand
339
+ extends MultiCommandCliAbstractHelpCommand
340
+ implements GlobalCommand {
341
+ readonly shortAlias = "h";
342
+
343
+ readonly argument = {
344
+ name: "command",
345
+ type: ArgumentValueTypeName.STRING,
346
+ isOptional: true,
347
+ };
348
+
349
+ public async execute(
350
+ context: Context,
351
+ argumentValue?: ArgumentSingleValueType,
352
+ ): Promise<void> {
353
+ const commandName = argumentValue as
354
+ | string
355
+ | undefined;
356
+ if (commandName !== undefined) {
357
+ await this.printUsageHelp(context, commandName);
358
+ } else {
359
+ await this.printGenericHelp(context);
360
+ }
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Implementation of multi-command CLI help available via `myCli help` or `myCli help <command>`.
366
+ */
367
+ export class MultiCommandCliHelpSubCommand
368
+ extends MultiCommandCliAbstractHelpCommand
369
+ implements SubCommand {
370
+ public readonly options: ReadonlyArray<Option> = [];
371
+
372
+ public readonly positionals: ReadonlyArray<Positional> = [
373
+ {
374
+ name: "command",
375
+ type: ArgumentValueTypeName.STRING,
376
+ isVarargOptional: true,
377
+ description: "Display help for the specific <command>",
378
+ },
379
+ ];
380
+
381
+ public async execute(
382
+ context: Context,
383
+ argumentValues: ArgumentValues,
384
+ ): Promise<void> {
385
+ const commandName = argumentValues["command"] as
386
+ | string
387
+ | undefined;
388
+ if (commandName !== undefined) {
389
+ await this.printUsageHelp(context, commandName);
390
+ } else {
391
+ await this.printGenericHelp(context);
392
+ }
393
+ }
394
+ }
@@ -0,0 +1,183 @@
1
+ import type GlobalCommand from "../api/command/GlobalCommand.ts";
2
+ import type SubCommand from "../api/command/SubCommand.ts";
3
+ import type Option from "../api/argument/Option.ts";
4
+ import type Positional from "../api/argument/Positional.ts";
5
+ import type Context from "../api/Context.ts";
6
+ import {
7
+ getCommandArgsHelpSections,
8
+ getCommandExamplesHelpSections,
9
+ getGlobalArgumentHelpEntry,
10
+ getSubCommandArgumentsSyntax,
11
+ type HelpEntry,
12
+ type HelpSection,
13
+ printHelpSections,
14
+ } from "../util/helpHelper.ts";
15
+ import type GlobalModifierCommand from "../api/command/GlobalModifierCommand.ts";
16
+ import type GroupCommand from "../api/command/GroupCommand.ts";
17
+ import type CommandRegistry from "../runtime/registry/CommandRegistry.ts";
18
+ import type PrinterService from "../api/service/core/PrinterService.ts";
19
+ import { PRINTER_SERVICE_ID } from "../api/service/core/PrinterService.ts";
20
+
21
+ /**
22
+ * Provides common implementation for both {@link SingleCommandCliHelpGlobalCommand} and {@link SingleCommandCliHelpSubCommand}.
23
+ */
24
+ abstract class SingleCommandCliAbstractHelpCommand {
25
+ readonly #includeEnvVars: boolean;
26
+ readonly #defaultCommand: SubCommand;
27
+ readonly #commandRegistry: CommandRegistry;
28
+
29
+ constructor(
30
+ includeEnvVars: boolean,
31
+ defaultCommand: SubCommand,
32
+ commandRegistry: CommandRegistry,
33
+ ) {
34
+ this.#includeEnvVars = includeEnvVars;
35
+ this.#defaultCommand = defaultCommand;
36
+ this.#commandRegistry = commandRegistry;
37
+ }
38
+
39
+ readonly name = "help";
40
+
41
+ readonly description = "Display application help";
42
+
43
+ #getAmalgamatedGenericHelpSection(
44
+ context: Context,
45
+ globalModifierCommands: ReadonlyArray<GlobalModifierCommand>,
46
+ globalCommands: ReadonlyArray<GlobalCommand>,
47
+ groupCommands: ReadonlyArray<GroupCommand>,
48
+ subCommands: ReadonlyArray<SubCommand>,
49
+ ): HelpSection {
50
+ const helpSection: HelpSection = {
51
+ title: "Other Arguments",
52
+ helpEntries: [],
53
+ };
54
+ if (globalModifierCommands.length > 0) {
55
+ globalModifierCommands.forEach((globalModifierCommand) => {
56
+ const { syntax, description } = getGlobalArgumentHelpEntry(
57
+ context.cliConfig,
58
+ this.#includeEnvVars,
59
+ globalModifierCommand,
60
+ );
61
+ helpSection.helpEntries.push({
62
+ syntax: `--${globalModifierCommand.name} ${syntax}`,
63
+ description,
64
+ });
65
+ if (globalModifierCommand.shortAlias !== undefined) {
66
+ helpSection.helpEntries.push({
67
+ syntax: `-${globalModifierCommand.shortAlias} ${syntax}`,
68
+ description,
69
+ });
70
+ }
71
+ });
72
+ }
73
+ if (globalCommands.length > 0) {
74
+ globalCommands.forEach((globalCommand) => {
75
+ const { syntax, description } = getGlobalArgumentHelpEntry(
76
+ context.cliConfig,
77
+ this.#includeEnvVars,
78
+ globalCommand,
79
+ );
80
+ helpSection.helpEntries.push({
81
+ syntax: `--${globalCommand.name} ${syntax}`,
82
+ description,
83
+ });
84
+ if (globalCommand.shortAlias !== undefined) {
85
+ helpSection.helpEntries.push({
86
+ syntax: `-${globalCommand.shortAlias} ${syntax}`,
87
+ description,
88
+ });
89
+ }
90
+ });
91
+ }
92
+ groupCommands.forEach((groupCommand) => {
93
+ groupCommand.memberSubCommands.forEach((memberCommand: SubCommand) => {
94
+ helpSection.helpEntries.push({
95
+ syntax: `${groupCommand.name}:${memberCommand.name}`,
96
+ description: memberCommand.description,
97
+ });
98
+ });
99
+ });
100
+ if (subCommands.length > 0) {
101
+ subCommands.forEach((subCommand) => {
102
+ helpSection.helpEntries.push({
103
+ syntax: subCommand.name,
104
+ description: subCommand.description,
105
+ });
106
+ });
107
+ }
108
+ helpSection.helpEntries.sort((a: HelpEntry, b: HelpEntry) =>
109
+ a.syntax.localeCompare(b.syntax)
110
+ );
111
+ return helpSection;
112
+ }
113
+
114
+ public async execute(
115
+ context: Context,
116
+ ): Promise<void> {
117
+ const helpSections: Array<HelpSection> = [];
118
+ helpSections.push({
119
+ title: "Usage",
120
+ helpEntries: [{
121
+ syntax: `${context.cliConfig.name || ""}${
122
+ getSubCommandArgumentsSyntax(this.#defaultCommand)
123
+ }`,
124
+ }],
125
+ });
126
+
127
+ helpSections.push(
128
+ ...getCommandArgsHelpSections(
129
+ context.cliConfig,
130
+ this.#includeEnvVars,
131
+ this.#defaultCommand,
132
+ true,
133
+ ),
134
+ );
135
+
136
+ const globalModifierCommands = this.#commandRegistry
137
+ .getGlobalModifierCommands();
138
+ const globalCommands = this.#commandRegistry.getGlobalCommands();
139
+ helpSections.push(
140
+ this.#getAmalgamatedGenericHelpSection(
141
+ context,
142
+ globalModifierCommands,
143
+ globalCommands,
144
+ [],
145
+ [],
146
+ ),
147
+ );
148
+
149
+ const printerService = context.getServiceById(
150
+ PRINTER_SERVICE_ID,
151
+ ) as PrinterService;
152
+
153
+ helpSections.push(
154
+ ...getCommandExamplesHelpSections(
155
+ printerService,
156
+ context,
157
+ this.#defaultCommand,
158
+ ),
159
+ );
160
+
161
+ await printHelpSections(printerService, helpSections);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Implementation of default command CLI help available via `myCli --help` or `myCli -h`.
167
+ */
168
+ export class SingleCommandCliHelpGlobalCommand
169
+ extends SingleCommandCliAbstractHelpCommand
170
+ implements GlobalCommand {
171
+ readonly shortAlias = "h";
172
+ }
173
+
174
+ /**
175
+ * Implementation of default-command CLI help available via `myCli help`.
176
+ */
177
+ export class SingleCommandCliHelpSubCommand
178
+ extends SingleCommandCliAbstractHelpCommand
179
+ implements SubCommand {
180
+ public readonly options: ReadonlyArray<Option> = [];
181
+
182
+ public readonly positionals: ReadonlyArray<Positional> = [];
183
+ }
@@ -0,0 +1,35 @@
1
+ import type Context from "../api/Context.ts";
2
+ import type GlobalCommand from "../api/command/GlobalCommand.ts";
3
+ import type Command from "../api/command/Command.ts";
4
+ import type PrinterService from "../api/service/core/PrinterService.ts";
5
+ import { PRINTER_SERVICE_ID } from "../api/service/core/PrinterService.ts";
6
+
7
+ /**
8
+ * Command printing basic CLI usage.
9
+ */
10
+ export default class UsageCommand implements GlobalCommand {
11
+ readonly name = "usage";
12
+ readonly description = "Show usage information";
13
+
14
+ readonly #helpCommand: Command;
15
+
16
+ public constructor(
17
+ helpCommand: Command,
18
+ ) {
19
+ this.#helpCommand = helpCommand;
20
+ }
21
+
22
+ public async execute(
23
+ context: Context,
24
+ ): Promise<void> {
25
+ const printerService = context.getServiceById(
26
+ PRINTER_SERVICE_ID,
27
+ ) as PrinterService;
28
+
29
+ await printerService.print(
30
+ `${printerService.secondary("Try running:")}\n\n ${
31
+ printerService.primary(context.cliConfig.name)
32
+ } --${printerService.primary(this.#helpCommand.name)}\n\n`,
33
+ );
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ import type Context from "../api/Context.ts";
2
+ import type GlobalCommand from "../api/command/GlobalCommand.ts";
3
+ import type PrinterService from "../api/service/core/PrinterService.ts";
4
+ import { PRINTER_SERVICE_ID } from "../api/service/core/PrinterService.ts";
5
+
6
+ /**
7
+ * Implementation of a {@link GlobalCommand} which outputs the version of the CLI.
8
+ */
9
+ export default class VersionCommand implements GlobalCommand {
10
+ readonly name = "version";
11
+ readonly description = "Show version information";
12
+ readonly shortAlias = "v";
13
+
14
+ public async execute(
15
+ context: Context,
16
+ ): Promise<void> {
17
+ const printerService = context.getServiceById(
18
+ PRINTER_SERVICE_ID,
19
+ ) as PrinterService;
20
+ await printerService.print(`${context.cliConfig.version}\n`);
21
+ }
22
+ }