@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,751 @@
1
+ import type GlobalCommand from "../api/command/GlobalCommand.ts";
2
+ import type GlobalCommandArgument from "../api/argument/GlobalCommandArgument.ts";
3
+ import {
4
+ ArgumentValueTypeName,
5
+ ComplexValueTypeName,
6
+ } from "../api/argument/ArgumentValueTypes.ts";
7
+ import type SubCommand from "../api/command/SubCommand.ts";
8
+ import type Context from "../api/Context.ts";
9
+ import type UsageExample from "../api/command/UsageExample.ts";
10
+ import type Option from "../api/argument/Option.ts";
11
+ import type Positional from "../api/argument/Positional.ts";
12
+ import type GlobalModifierCommand from "../api/command/GlobalModifierCommand.ts";
13
+ import type GroupCommand from "../api/command/GroupCommand.ts";
14
+ import {
15
+ getGlobalCommandArgumentConfigurationKey,
16
+ getSubCommandArgumentConfigurationKey,
17
+ } from "./configHelper.ts";
18
+ import type Command from "../api/command/Command.ts";
19
+ import type CLIConfig from "../api/CLIConfig.ts";
20
+ import type PrinterService from "../api/service/core/PrinterService.ts";
21
+ import type SubCommandArgument from "../api/argument/SubCommandArgument.ts";
22
+ import type ComplexOption from "../api/argument/ComplexOption.ts";
23
+
24
+ const SYNTAX_INDENT_WIDTH = 2;
25
+ const SYNTAX_MIN_PADDING_WIDTH = 2;
26
+ const MINIMUM_SYNTAX_COLUMN_WIDTH = 15;
27
+ const HELP_SYNTAX_INDENT = " ".repeat(SYNTAX_INDENT_WIDTH);
28
+ const HELP_SYNTAX_PADDING = " ".repeat(SYNTAX_MIN_PADDING_WIDTH);
29
+
30
+ export interface HelpEntry {
31
+ readonly syntax: string;
32
+ readonly description?: string;
33
+ readonly subEntryIndent?: number;
34
+ readonly helpSubEntries?: Array<HelpEntry>;
35
+ }
36
+
37
+ export interface HelpSection {
38
+ readonly title: string;
39
+ readonly helpEntries: Array<HelpEntry>;
40
+ }
41
+
42
+ async function printSingleHelpEntry(
43
+ printerService: PrinterService,
44
+ helpEntry: HelpEntry,
45
+ syntaxWidth: number,
46
+ indentText: string,
47
+ isRoot: boolean,
48
+ isLastSibling: boolean,
49
+ ): Promise<void> {
50
+ let text = "";
51
+ const notesIndent = syntaxWidth;
52
+ let syntaxTreeChars = "";
53
+ let totalSyntaxLength = 0;
54
+
55
+ if (helpEntry.syntax) {
56
+ if (isRoot) {
57
+ text += HELP_SYNTAX_INDENT + printerService.primary(helpEntry.syntax);
58
+ totalSyntaxLength = SYNTAX_INDENT_WIDTH + helpEntry.syntax.length;
59
+ } else {
60
+ if (indentText.length > 0) {
61
+ syntaxTreeChars = isLastSibling ? "└╴" : "├╴";
62
+ }
63
+ text += HELP_SYNTAX_INDENT +
64
+ printerService.secondary(indentText + syntaxTreeChars) +
65
+ printerService.primary(helpEntry.syntax);
66
+ totalSyntaxLength = SYNTAX_INDENT_WIDTH + indentText.length +
67
+ syntaxTreeChars.length + helpEntry.syntax.length;
68
+ }
69
+ }
70
+ if (helpEntry.description) {
71
+ if (helpEntry.syntax) {
72
+ if (totalSyntaxLength > notesIndent) {
73
+ text += HELP_SYNTAX_PADDING +
74
+ printerService.secondary(helpEntry.description);
75
+ } else {
76
+ text += " ".repeat(syntaxWidth - totalSyntaxLength) +
77
+ printerService.secondary(helpEntry.description);
78
+ }
79
+ } else {
80
+ text += HELP_SYNTAX_PADDING +
81
+ printerService.secondary(helpEntry.description);
82
+ }
83
+ }
84
+ text += "\n";
85
+ await printerService.print(text);
86
+ }
87
+
88
+ async function printHelpEntry(
89
+ printerService: PrinterService,
90
+ helpEntry: HelpEntry,
91
+ syntaxWidth: number,
92
+ indentText: string,
93
+ isRoot: boolean,
94
+ hasChildren: boolean,
95
+ isLastSibling: boolean,
96
+ ): Promise<void> {
97
+ if (hasChildren) {
98
+ await printSingleHelpEntry(
99
+ printerService,
100
+ helpEntry,
101
+ syntaxWidth,
102
+ indentText,
103
+ isRoot,
104
+ isLastSibling,
105
+ );
106
+ indentText += (isLastSibling ? " " : "│ ") +
107
+ " ".repeat(helpEntry.subEntryIndent || 0);
108
+ for (let i = 0; i < helpEntry.helpSubEntries!.length; i++) {
109
+ const helpSubEntry = helpEntry.helpSubEntries![i];
110
+ const helpSubEntryHasChildren = helpSubEntry.helpSubEntries !== undefined;
111
+ const helpSubEntryIsLastSibling =
112
+ i === helpEntry.helpSubEntries!.length - 1;
113
+ await printHelpEntry(
114
+ printerService,
115
+ helpSubEntry,
116
+ syntaxWidth,
117
+ indentText,
118
+ false,
119
+ helpSubEntryHasChildren,
120
+ helpSubEntryIsLastSibling,
121
+ );
122
+ }
123
+ } else {
124
+ await printSingleHelpEntry(
125
+ printerService,
126
+ helpEntry,
127
+ syntaxWidth,
128
+ indentText,
129
+ isRoot,
130
+ isLastSibling,
131
+ );
132
+ }
133
+ }
134
+
135
+ function getSyntaxWidth(helpEntry: HelpEntry, indent: number): number {
136
+ if (
137
+ (helpEntry.helpSubEntries === undefined) ||
138
+ (helpEntry.helpSubEntries.length === 0)
139
+ ) {
140
+ // 2 is for the tree characters
141
+ let finalWidth = SYNTAX_INDENT_WIDTH + indent + 2 +
142
+ SYNTAX_MIN_PADDING_WIDTH;
143
+ if (helpEntry.syntax) {
144
+ finalWidth += helpEntry.syntax.length;
145
+ }
146
+ return finalWidth;
147
+ }
148
+ let maxSubIndent = 0;
149
+ helpEntry.helpSubEntries.forEach((helpSubEntry) => {
150
+ // 2 is for the tree characters
151
+ const subEntryWidth = getSyntaxWidth(
152
+ helpSubEntry,
153
+ (helpEntry.subEntryIndent ? helpEntry.subEntryIndent : 0) + 2,
154
+ );
155
+ if (subEntryWidth > maxSubIndent) {
156
+ maxSubIndent = subEntryWidth;
157
+ }
158
+ });
159
+ return indent + maxSubIndent;
160
+ }
161
+
162
+ export async function printHelpSections(
163
+ printerService: PrinterService,
164
+ sections: Array<HelpSection>,
165
+ ): Promise<void> {
166
+ // calculate alignment
167
+ let syntaxWidth = SYNTAX_INDENT_WIDTH + MINIMUM_SYNTAX_COLUMN_WIDTH +
168
+ SYNTAX_MIN_PADDING_WIDTH;
169
+ sections.forEach((section) => {
170
+ section.helpEntries.forEach((helpEntry) => {
171
+ const entrySyntaxWidth = getSyntaxWidth(helpEntry, 0);
172
+ // don't expand out if no description as we can overwrite that space on the line
173
+ if (
174
+ helpEntry.description && (helpEntry.description.length > 0) &&
175
+ (entrySyntaxWidth > syntaxWidth)
176
+ ) {
177
+ syntaxWidth = entrySyntaxWidth;
178
+ }
179
+ });
180
+ });
181
+
182
+ // print the help
183
+ for (let i = 0; i < sections.length; i++) {
184
+ const section = sections[i];
185
+ if (section.title.length > 0) {
186
+ await printerService.print(
187
+ `${printerService.emphasised(section.title)}\n\n`,
188
+ );
189
+ }
190
+ for (let j = 0; j < section.helpEntries.length; j++) {
191
+ const helpEntry = section.helpEntries[j];
192
+ await printHelpEntry(
193
+ printerService,
194
+ helpEntry,
195
+ syntaxWidth,
196
+ "",
197
+ true,
198
+ helpEntry.helpSubEntries !== undefined,
199
+ true,
200
+ );
201
+ }
202
+ if (section.helpEntries.length > 0) {
203
+ await printerService.print("\n");
204
+ }
205
+ }
206
+ }
207
+
208
+ function getGlobalCommandArgumentForm(
209
+ globalCommandArgument: GlobalCommandArgument,
210
+ ): string {
211
+ let argumentSyntax = "";
212
+
213
+ switch (globalCommandArgument.type) {
214
+ case ArgumentValueTypeName.BOOLEAN:
215
+ argumentSyntax = "true|false";
216
+ break;
217
+ case ArgumentValueTypeName.INTEGER:
218
+ argumentSyntax = "<integer_value>";
219
+ break;
220
+ case ArgumentValueTypeName.NUMBER:
221
+ argumentSyntax = "<number_value>";
222
+ break;
223
+ case ArgumentValueTypeName.STRING:
224
+ argumentSyntax = "<string_value>";
225
+ break;
226
+ }
227
+
228
+ if (
229
+ globalCommandArgument.isOptional ||
230
+ globalCommandArgument.type === ArgumentValueTypeName.BOOLEAN
231
+ ) {
232
+ argumentSyntax = `[${argumentSyntax}]`;
233
+ }
234
+
235
+ return argumentSyntax;
236
+ }
237
+
238
+ export function getGlobalArgumentHelpEntry(
239
+ cliConfig: CLIConfig,
240
+ includeEnvVars: boolean,
241
+ globalCommand: GlobalCommand,
242
+ ): HelpEntry {
243
+ const { argument } = globalCommand;
244
+
245
+ let description = globalCommand.description || "";
246
+ if (argument === undefined) {
247
+ return { syntax: "", description };
248
+ }
249
+
250
+ const syntax = getGlobalCommandArgumentForm(argument);
251
+
252
+ const notesItems: Array<string> = [];
253
+ if (
254
+ (argument.allowableValues !== undefined) &&
255
+ (argument.allowableValues.length > 0)
256
+ ) {
257
+ notesItems.push(`valid values: ${argument.allowableValues.join("|")}`);
258
+ } else {
259
+ switch (argument.type) {
260
+ case ArgumentValueTypeName.STRING:
261
+ notesItems.push("string value");
262
+ break;
263
+ case ArgumentValueTypeName.INTEGER:
264
+ notesItems.push("integer value");
265
+ break;
266
+ case ArgumentValueTypeName.NUMBER:
267
+ notesItems.push("number value");
268
+ break;
269
+ }
270
+ }
271
+
272
+ if (argument.defaultValue !== undefined) {
273
+ notesItems.push(`default: ${argument.defaultValue}`);
274
+ }
275
+
276
+ if (includeEnvVars) {
277
+ const configurationKey = getGlobalCommandArgumentConfigurationKey(
278
+ cliConfig,
279
+ globalCommand,
280
+ argument,
281
+ );
282
+ if (configurationKey !== undefined) {
283
+ notesItems.push(`env var: ${configurationKey}`);
284
+ }
285
+ }
286
+
287
+ if (notesItems.length > 0) {
288
+ if (description.length === 0) {
289
+ description = `(${notesItems.join(", ")})`;
290
+ } else {
291
+ description = `${description} (${notesItems.join(", ")})`;
292
+ }
293
+ }
294
+ return { syntax, description };
295
+ }
296
+
297
+ export function getSubCommandArgumentsSyntax(subCommand: SubCommand): string {
298
+ let syntax = "";
299
+
300
+ subCommand.options.forEach((option) => {
301
+ let optionSyntax = `--${option.name}`;
302
+ switch (option.type as ArgumentValueTypeName | ComplexValueTypeName) {
303
+ case ArgumentValueTypeName.BOOLEAN:
304
+ optionSyntax = `${optionSyntax} [true|false]`;
305
+ break;
306
+ case ArgumentValueTypeName.INTEGER:
307
+ optionSyntax = `${optionSyntax} <integer_value>`;
308
+ break;
309
+ case ArgumentValueTypeName.NUMBER:
310
+ optionSyntax = `${optionSyntax} <number_value>`;
311
+ break;
312
+ case ArgumentValueTypeName.STRING:
313
+ optionSyntax = `${optionSyntax} <string_value>`;
314
+ break;
315
+ case ComplexValueTypeName.COMPLEX:
316
+ optionSyntax = `${optionSyntax} <complex_value>`;
317
+ break;
318
+ }
319
+ if (option.isOptional || (option.defaultValue !== undefined)) {
320
+ optionSyntax = ` [${optionSyntax}]`;
321
+ } else {
322
+ optionSyntax = ` ${optionSyntax}`;
323
+ }
324
+ if (option.isArray) {
325
+ optionSyntax = `${optionSyntax}...`;
326
+ }
327
+ syntax = `${syntax}${optionSyntax}`;
328
+ });
329
+
330
+ subCommand.positionals.forEach((positional) => {
331
+ let positionalSyntax = `<${positional.name}>`;
332
+ if (positional.isVarargOptional) {
333
+ positionalSyntax = ` [${positionalSyntax}]`;
334
+ } else {
335
+ positionalSyntax = ` ${positionalSyntax}`;
336
+ }
337
+ if (positional.isVarargMultiple) {
338
+ positionalSyntax = `${positionalSyntax}...`;
339
+ }
340
+ syntax = `${syntax}${positionalSyntax}`;
341
+ });
342
+
343
+ return syntax;
344
+ }
345
+
346
+ function getOptionHelpEntry(
347
+ cliConfig: CLIConfig,
348
+ includeEnvVars: boolean,
349
+ command: Command,
350
+ option: Option | ComplexOption,
351
+ subEntryIndent = 0,
352
+ optionAncestry: Array<Option | ComplexOption> = [],
353
+ ): HelpEntry {
354
+ let subEntries: Array<HelpEntry> | undefined;
355
+ let optionCharacters = "--";
356
+ let aliasCharacter = "-";
357
+ let newSubEntryIndent = option.name.length;
358
+ if (subEntryIndent > 0) {
359
+ optionCharacters = ".";
360
+ aliasCharacter = ".";
361
+ newSubEntryIndent += 1;
362
+ }
363
+
364
+ const notesItems: Array<string> = [];
365
+ if (option.type === ComplexValueTypeName.COMPLEX) {
366
+ subEntries = [];
367
+ option.properties.forEach((property) => {
368
+ subEntries!.push(
369
+ getOptionHelpEntry(
370
+ cliConfig,
371
+ includeEnvVars,
372
+ command,
373
+ property,
374
+ newSubEntryIndent,
375
+ [...optionAncestry, option],
376
+ ),
377
+ );
378
+ });
379
+ } else {
380
+ if (
381
+ (option.allowableValues !== undefined) &&
382
+ (option.allowableValues.length > 0)
383
+ ) {
384
+ notesItems.push(`valid values: ${option.allowableValues.join("|")}`);
385
+ } else {
386
+ switch (option.type) {
387
+ case ArgumentValueTypeName.STRING:
388
+ notesItems.push(`string value`);
389
+ break;
390
+ case ArgumentValueTypeName.INTEGER:
391
+ notesItems.push(`integer value`);
392
+ break;
393
+ case ArgumentValueTypeName.NUMBER:
394
+ notesItems.push(`number value`);
395
+ break;
396
+ case ArgumentValueTypeName.BOOLEAN:
397
+ notesItems.push(`boolean value`);
398
+ break;
399
+ }
400
+ }
401
+ }
402
+ if (option.defaultValue !== undefined) {
403
+ notesItems.push(
404
+ `default: ${
405
+ Array.isArray(option.defaultValue)
406
+ ? `${option.defaultValue.join(", ")}`
407
+ : `${option.defaultValue}`
408
+ }`,
409
+ );
410
+ }
411
+
412
+ if (option.isOptional) {
413
+ notesItems.push("optional");
414
+ }
415
+ if (option.isArray) {
416
+ notesItems.push("array");
417
+ }
418
+
419
+ if (includeEnvVars && (option.type !== ComplexValueTypeName.COMPLEX)) {
420
+ const configurationKey = getSubCommandArgumentConfigurationKey(
421
+ cliConfig,
422
+ command,
423
+ [
424
+ ...(optionAncestry as Array<SubCommandArgument>),
425
+ option as unknown as SubCommandArgument,
426
+ ],
427
+ );
428
+ if (configurationKey !== undefined) {
429
+ notesItems.push(`env var: ${configurationKey}`);
430
+ }
431
+ }
432
+
433
+ let description = option.description || "";
434
+
435
+ if (description.length > 0) {
436
+ if (notesItems.length > 0) {
437
+ description = `${description} (${notesItems.join(", ")})`;
438
+ }
439
+ } else if (notesItems.length > 0) {
440
+ description = "(" + notesItems.join(", ") + ")";
441
+ }
442
+
443
+ return {
444
+ syntax: `${optionCharacters}${option.name}${
445
+ (option.shortAlias !== undefined)
446
+ ? `, ${aliasCharacter}${option.shortAlias}`
447
+ : ""
448
+ }`,
449
+ description,
450
+ subEntryIndent: newSubEntryIndent,
451
+ helpSubEntries: subEntries,
452
+ };
453
+ }
454
+
455
+ function getPositionalHelpEntry(
456
+ cliConfig: CLIConfig,
457
+ includeEnvVars: boolean,
458
+ command: Command,
459
+ positional: Positional,
460
+ ): HelpEntry {
461
+ const notesItems: Array<string> = [];
462
+ if (
463
+ (positional.allowableValues !== undefined) &&
464
+ (positional.allowableValues.length > 0)
465
+ ) {
466
+ notesItems.push(`valid values: ${positional.allowableValues.join("|")}`);
467
+ } else {
468
+ switch (positional.type) {
469
+ case ArgumentValueTypeName.STRING:
470
+ notesItems.push(`string value`);
471
+ break;
472
+ case ArgumentValueTypeName.INTEGER:
473
+ notesItems.push(`integer value`);
474
+ break;
475
+ case ArgumentValueTypeName.NUMBER:
476
+ notesItems.push(`number value`);
477
+ break;
478
+ case ArgumentValueTypeName.BOOLEAN:
479
+ notesItems.push(`boolean value`);
480
+ break;
481
+ }
482
+ }
483
+
484
+ if (positional.isVarargOptional) {
485
+ notesItems.push("optional");
486
+ }
487
+ if (positional.isVarargMultiple) {
488
+ notesItems.push("array");
489
+ }
490
+
491
+ if (includeEnvVars) {
492
+ const configurationKey = getSubCommandArgumentConfigurationKey(
493
+ cliConfig,
494
+ command,
495
+ [
496
+ positional,
497
+ ],
498
+ );
499
+ if (configurationKey !== undefined) {
500
+ notesItems.push(`env var: ${configurationKey}`);
501
+ }
502
+ }
503
+
504
+ let description = positional.description || "";
505
+
506
+ if (description.length > 0) {
507
+ if (notesItems.length > 0) {
508
+ description = `${description} (${notesItems.join(", ")})`;
509
+ }
510
+ } else if (notesItems.length > 0) {
511
+ description = "(" + notesItems.join(", ") + ")";
512
+ }
513
+ return {
514
+ syntax: `<${positional.name}>`,
515
+ description,
516
+ };
517
+ }
518
+
519
+ export function getCommandArgsHelpSections(
520
+ cliConfig: CLIConfig,
521
+ includeEnvVars: boolean,
522
+ subCommand: SubCommand,
523
+ isSingleCommandApp: boolean,
524
+ ): Array<HelpSection> {
525
+ const helpSections: Array<HelpSection> = [];
526
+ if ((subCommand.options.length > 0) || (subCommand.positionals.length > 0)) {
527
+ const argumentsSection: HelpSection = {
528
+ title: `${isSingleCommandApp ? "Command " : ""}Arguments`,
529
+ helpEntries: [],
530
+ };
531
+ subCommand.options.forEach((option) => {
532
+ argumentsSection.helpEntries.push(
533
+ getOptionHelpEntry(
534
+ cliConfig,
535
+ includeEnvVars,
536
+ subCommand,
537
+ option as Option,
538
+ ),
539
+ );
540
+ });
541
+ subCommand.positionals.forEach((positional) => {
542
+ argumentsSection.helpEntries.push(
543
+ getPositionalHelpEntry(
544
+ cliConfig,
545
+ includeEnvVars,
546
+ subCommand,
547
+ positional,
548
+ ),
549
+ );
550
+ });
551
+ helpSections.push(argumentsSection);
552
+ }
553
+ return helpSections;
554
+ }
555
+ export function getCommandExamplesHelpSections(
556
+ printerService: PrinterService,
557
+ context: Context,
558
+ subCommand: SubCommand,
559
+ ): Array<HelpSection> {
560
+ const helpSections: Array<HelpSection> = [];
561
+ if (
562
+ (subCommand.usageExamples !== undefined) &&
563
+ (subCommand.usageExamples.length > 0)
564
+ ) {
565
+ const usageSection: HelpSection = {
566
+ title: subCommand.usageExamples.length > 1 ? "Examples" : "Example",
567
+ helpEntries: [],
568
+ };
569
+ helpSections.push(usageSection);
570
+ subCommand.usageExamples.forEach((usageExample: UsageExample, index) => {
571
+ if (
572
+ (usageExample.description != null) &&
573
+ (usageExample.description.length > 0)
574
+ ) {
575
+ usageSection.helpEntries.push({
576
+ syntax: `${printerService.italic(usageExample.description)}:\n`,
577
+ });
578
+ }
579
+ usageSection.helpEntries.push({
580
+ syntax:
581
+ `$ ${context.cliConfig.name} ${subCommand.name} ${usageExample.exampleArguments}`,
582
+ });
583
+ if (usageExample.output !== undefined) {
584
+ usageExample.output.forEach((output) => {
585
+ usageSection.helpEntries.push({
586
+ syntax: printerService.secondary(output),
587
+ });
588
+ });
589
+ }
590
+ if (index < subCommand.usageExamples!.length - 1) {
591
+ usageSection.helpEntries.push({
592
+ syntax: "",
593
+ });
594
+ }
595
+ });
596
+ }
597
+ return helpSections;
598
+ }
599
+
600
+ export function getMultiCommandAppSyntax(
601
+ context: Context,
602
+ globalModifierCommands: ReadonlyArray<GlobalModifierCommand>,
603
+ globalCommands: ReadonlyArray<GlobalCommand>,
604
+ groupCommands: ReadonlyArray<GroupCommand>,
605
+ subCommands: ReadonlyArray<SubCommand>,
606
+ ): string {
607
+ const globalPrefix = ((groupCommands.length > 0) || (subCommands.length > 0))
608
+ ? "global_"
609
+ : "";
610
+ let syntax = context.cliConfig.name || "";
611
+ if (globalModifierCommands.length > 0) {
612
+ syntax += ` [<${globalPrefix}option>`;
613
+ // dont render global_option arg if none defined
614
+ const noArg = globalModifierCommands.every((
615
+ modifier,
616
+ ) => (modifier.argument === undefined));
617
+ if (!noArg) {
618
+ // render global_option arg in [] if all modifiers have non-optional, non-boolean arg with no default
619
+ const optionArgMandatory = globalModifierCommands.every((modifier) =>
620
+ (modifier.argument !== undefined) &&
621
+ !modifier.argument.isOptional &&
622
+ (modifier.argument.defaultValue === undefined) &&
623
+ modifier.argument.type !== ArgumentValueTypeName.BOOLEAN
624
+ );
625
+ syntax += optionArgMandatory ? " <value>" : " [<value>]";
626
+ }
627
+ syntax += "]";
628
+ }
629
+ if (globalModifierCommands.length > 1) {
630
+ syntax += "...";
631
+ }
632
+ let arg = false;
633
+ let argOptional = false;
634
+ let argValueOptional = false;
635
+ let multipleArg = false;
636
+ const commandClauses = [];
637
+ if (globalCommands.length > 0) {
638
+ commandClauses.push(`<${globalPrefix}command>`);
639
+ arg = globalCommands.some((
640
+ globalCommand,
641
+ ) => (globalCommand.argument !== undefined));
642
+ if (arg) {
643
+ argOptional = globalCommands.some((globalCommand) =>
644
+ (globalCommand.argument !== undefined) &&
645
+ (((globalCommand.argument.isOptional !== undefined) &&
646
+ globalCommand.argument.isOptional) ||
647
+ (globalCommand.argument.defaultValue !== undefined))
648
+ );
649
+ argValueOptional = globalCommands.some((globalCommand) =>
650
+ (globalCommand.argument !== undefined) &&
651
+ (globalCommand.argument.type === ArgumentValueTypeName.BOOLEAN)
652
+ );
653
+ }
654
+ }
655
+ if ((groupCommands.length > 0) || (subCommands.length > 0)) {
656
+ commandClauses.push("<command>");
657
+ arg = arg || groupCommands.some((groupCommand) =>
658
+ groupCommand.memberSubCommands.some((memberCommand) =>
659
+ memberCommand.options.length > 0 || memberCommand.positionals.length > 0
660
+ )
661
+ ) ||
662
+ subCommands.some((subCommand) =>
663
+ subCommand.options.length > 0 || subCommand.positionals.length > 0
664
+ );
665
+ if (arg) {
666
+ argOptional = argOptional || groupCommands.some((groupCommand) =>
667
+ groupCommand.memberSubCommands.some((memberCommand) =>
668
+ memberCommand.options.some((option) =>
669
+ ((option.isOptional !== undefined) && option.isOptional) ||
670
+ (option.defaultValue !== undefined)
671
+ ) ||
672
+ memberCommand.positionals.some((
673
+ positional,
674
+ ) => ((positional.isVarargOptional !== undefined) &&
675
+ positional.isVarargOptional)
676
+ )
677
+ )
678
+ ) ||
679
+ subCommands.some((subCommand) =>
680
+ subCommand.options.some((option) =>
681
+ ((option.isOptional !== undefined) && option.isOptional) ||
682
+ (option.defaultValue !== undefined)
683
+ ) ||
684
+ subCommand.positionals.some((
685
+ positional,
686
+ ) => ((positional.isVarargOptional !== undefined) &&
687
+ positional.isVarargOptional)
688
+ )
689
+ );
690
+ argValueOptional = argValueOptional ||
691
+ groupCommands.some((groupCommand) =>
692
+ groupCommand.memberSubCommands.some((memberCommand) =>
693
+ memberCommand.options.some((option) =>
694
+ option.type === ArgumentValueTypeName.BOOLEAN ||
695
+ memberCommand.positionals.some((positional) =>
696
+ positional.type === ArgumentValueTypeName.BOOLEAN
697
+ )
698
+ )
699
+ )
700
+ ) ||
701
+ subCommands.some((subCommand) =>
702
+ subCommand.options.some((option) =>
703
+ option.type ===
704
+ ArgumentValueTypeName.BOOLEAN
705
+ ) ||
706
+ subCommand.positionals.some((positional) =>
707
+ positional.type === ArgumentValueTypeName.BOOLEAN
708
+ )
709
+ );
710
+ multipleArg = multipleArg || groupCommands.some((groupCommand) =>
711
+ groupCommand.memberSubCommands.some((memberCommand) =>
712
+ memberCommand.options.some((option) =>
713
+ option.isArray
714
+ ) ||
715
+ memberCommand.positionals.some((positional) =>
716
+ positional.isVarargMultiple
717
+ )
718
+ )
719
+ ) ||
720
+ subCommands.some((subCommand) =>
721
+ subCommand.options.some((option) =>
722
+ option.isArray
723
+ ) ||
724
+ subCommand.positionals.some((positional) =>
725
+ positional.isVarargMultiple
726
+ )
727
+ );
728
+ }
729
+ }
730
+ if (commandClauses.length > 0) {
731
+ let subSyntax = `${commandClauses.join("|")}`;
732
+ // dont render arg if none defined (no global arg or no option/positional)
733
+ if (arg) {
734
+ let argSyntax = "<arg>";
735
+ // render arg value in [] if some are optional
736
+ argSyntax += argValueOptional ? " [<value>]" : " <value>";
737
+ // dont render arg in [] if none are optional and have no default
738
+ subSyntax += argOptional
739
+ ? ` [${argSyntax}]`
740
+ : multipleArg
741
+ ? ` <${argSyntax}>`
742
+ : ` ${argSyntax}`;
743
+ // check to render multiple arg
744
+ if (multipleArg) {
745
+ subSyntax += "...";
746
+ }
747
+ }
748
+ syntax += ` ${subSyntax}`;
749
+ }
750
+ return syntax;
751
+ }