@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
@@ -21,7 +21,7 @@ import type ComplexOption from "../api/argument/ComplexOption.ts";
21
21
 
22
22
  function getKeySegment(segment: string) {
23
23
  let keySegment = segment.replace("-", "_").toUpperCase();
24
- if (keySegment[0].match(/[0-9]/)) {
24
+ if (keySegment[0]!.match(/[0-9]/)) {
25
25
  keySegment = `_${keySegment}`;
26
26
  }
27
27
  return keySegment;
@@ -152,7 +152,7 @@ function getOptionValuesFromEnvVars(
152
152
  getOptionValuesFromEnvVars(
153
153
  (envVarValues[option.name] as Array<PopulatedArgumentValues>)[
154
154
  index
155
- ],
155
+ ]!,
156
156
  matches,
157
157
  env,
158
158
  property,
@@ -1,3 +1,4 @@
1
+ import { distance } from "fastest-levenshtein";
1
2
  import type GlobalCommand from "../api/command/GlobalCommand.ts";
2
3
  import type GlobalCommandArgument from "../api/argument/GlobalCommandArgument.ts";
3
4
  import {
@@ -20,12 +21,12 @@ import type CLIConfig from "../api/CLIConfig.ts";
20
21
  import type PrinterService from "../api/service/core/PrinterService.ts";
21
22
  import type SubCommandArgument from "../api/argument/SubCommandArgument.ts";
22
23
  import type ComplexOption from "../api/argument/ComplexOption.ts";
24
+ import type TableGeneratorService from "../api/service/core/TableGeneratorService.ts";
25
+ import Table from "../api/service/core/Table.ts";
23
26
 
24
27
  const SYNTAX_INDENT_WIDTH = 2;
25
- const SYNTAX_MIN_PADDING_WIDTH = 2;
26
28
  const MINIMUM_SYNTAX_COLUMN_WIDTH = 15;
27
29
  const HELP_SYNTAX_INDENT = " ".repeat(SYNTAX_INDENT_WIDTH);
28
- const HELP_SYNTAX_PADDING = " ".repeat(SYNTAX_MIN_PADDING_WIDTH);
29
30
 
30
31
  export interface HelpEntry {
31
32
  readonly syntax: string;
@@ -39,169 +40,113 @@ export interface HelpSection {
39
40
  readonly helpEntries: Array<HelpEntry>;
40
41
  }
41
42
 
42
- async function printSingleHelpEntry(
43
+ interface FlattenedRow {
44
+ syntax: string;
45
+ description?: string;
46
+ }
47
+
48
+ function flattenHelpEntry(
43
49
  printerService: PrinterService,
44
50
  helpEntry: HelpEntry,
45
- syntaxWidth: number,
46
51
  indentText: string,
47
52
  isRoot: boolean,
48
53
  isLastSibling: boolean,
49
- ): Promise<void> {
50
- let text = "";
51
- const notesIndent = syntaxWidth;
52
- let syntaxTreeChars = "";
53
- let totalSyntaxLength = 0;
54
-
54
+ rows: FlattenedRow[],
55
+ ): void {
56
+ let syntax = "";
55
57
  if (helpEntry.syntax) {
56
58
  if (isRoot) {
57
- text += HELP_SYNTAX_INDENT + printerService.primary(helpEntry.syntax);
58
- totalSyntaxLength = SYNTAX_INDENT_WIDTH + helpEntry.syntax.length;
59
+ syntax = HELP_SYNTAX_INDENT + printerService.primary(helpEntry.syntax);
59
60
  } else {
61
+ let syntaxTreeChars = "";
60
62
  if (indentText.length > 0) {
61
63
  syntaxTreeChars = isLastSibling ? "└╴" : "├╴";
62
64
  }
63
- text += HELP_SYNTAX_INDENT +
65
+ syntax = HELP_SYNTAX_INDENT +
64
66
  printerService.secondary(indentText + syntaxTreeChars) +
65
67
  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
68
  }
83
69
  }
84
- text += "\n";
85
- await printerService.print(text);
86
- }
87
70
 
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 ? " " : "│ ") +
71
+ const description = helpEntry.description
72
+ ? printerService.secondary(helpEntry.description)
73
+ : undefined;
74
+
75
+ rows.push({ syntax, description });
76
+
77
+ if (helpEntry.helpSubEntries !== undefined) {
78
+ const nextIndent = indentText +
79
+ (isLastSibling ? " " : "│ ") +
107
80
  " ".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(
81
+ for (let i = 0; i < helpEntry.helpSubEntries.length; i++) {
82
+ const sub = helpEntry.helpSubEntries[i]!;
83
+ flattenHelpEntry(
114
84
  printerService,
115
- helpSubEntry,
116
- syntaxWidth,
117
- indentText,
85
+ sub,
86
+ nextIndent,
118
87
  false,
119
- helpSubEntryHasChildren,
120
- helpSubEntryIsLastSibling,
88
+ i === helpEntry.helpSubEntries.length - 1,
89
+ rows,
121
90
  );
122
91
  }
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
92
  }
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
93
  }
161
94
 
162
95
  export async function printHelpSections(
163
96
  printerService: PrinterService,
97
+ tableGeneratorService: TableGeneratorService,
164
98
  sections: Array<HelpSection>,
165
99
  ): 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
- });
100
+ const terminalWidth = printerService.stdoutColumns();
181
101
 
182
- // print the help
183
102
  for (let i = 0; i < sections.length; i++) {
184
- const section = sections[i];
103
+ const section = sections[i]!;
185
104
  if (section.title.length > 0) {
186
105
  await printerService.print(
187
106
  `${printerService.emphasised(section.title)}\n\n`,
188
107
  );
189
108
  }
190
- for (let j = 0; j < section.helpEntries.length; j++) {
191
- const helpEntry = section.helpEntries[j];
192
- await printHelpEntry(
109
+
110
+ if (section.helpEntries.length === 0) continue;
111
+
112
+ const rows: FlattenedRow[] = [];
113
+ for (const helpEntry of section.helpEntries) {
114
+ flattenHelpEntry(
193
115
  printerService,
194
116
  helpEntry,
195
- syntaxWidth,
196
117
  "",
197
118
  true,
198
- helpEntry.helpSubEntries !== undefined,
199
119
  true,
120
+ rows,
200
121
  );
201
122
  }
202
- if (section.helpEntries.length > 0) {
203
- await printerService.print("\n");
123
+
124
+ const hasDescriptions = rows.some((r) => r.description !== undefined);
125
+
126
+ if (hasDescriptions) {
127
+ const table = new Table(rows.length, 2, {
128
+ border: false,
129
+ padding: 1,
130
+ maxWidth: terminalWidth,
131
+ });
132
+ table.column(0, { minWidth: MINIMUM_SYNTAX_COLUMN_WIDTH });
133
+ table.column(1, { flexWeight: 1 });
134
+
135
+ for (let r = 0; r < rows.length; r++) {
136
+ const row = rows[r]!;
137
+ table.cell(r, 0, row.syntax);
138
+ table.cell(r, 1, row.description ?? "");
139
+ }
140
+
141
+ const rendered = tableGeneratorService.render(table);
142
+ await printerService.print(rendered + "\n");
143
+ } else {
144
+ for (const row of rows) {
145
+ await printerService.print(row.syntax + "\n");
146
+ }
204
147
  }
148
+
149
+ await printerService.print("\n");
205
150
  }
206
151
  }
207
152
 
@@ -267,6 +212,20 @@ export function getGlobalArgumentHelpEntry(
267
212
  notesItems.push("number value");
268
213
  break;
269
214
  }
215
+ if (
216
+ (argument.minValueInclusive !== undefined) ||
217
+ (argument.maxValueInclusive !== undefined)
218
+ ) {
219
+ let range = "..";
220
+
221
+ if (argument.minValueInclusive !== undefined) {
222
+ range = argument.minValueInclusive + range;
223
+ }
224
+ if (argument.maxValueInclusive !== undefined) {
225
+ range += argument.maxValueInclusive;
226
+ }
227
+ notesItems.push(range);
228
+ }
270
229
  }
271
230
 
272
231
  if (argument.defaultValue !== undefined) {
@@ -399,6 +358,21 @@ function getOptionHelpEntry(
399
358
  }
400
359
  }
401
360
  }
361
+ if (
362
+ (option.minValueInclusive !== undefined) ||
363
+ (option.maxValueInclusive !== undefined)
364
+ ) {
365
+ let range = "..";
366
+
367
+ if (option.minValueInclusive !== undefined) {
368
+ range = option.minValueInclusive + range;
369
+ }
370
+ if (option.maxValueInclusive !== undefined) {
371
+ range += option.maxValueInclusive;
372
+ }
373
+ notesItems.push(range);
374
+ }
375
+
402
376
  if (option.defaultValue !== undefined) {
403
377
  notesItems.push(
404
378
  `default: ${
@@ -480,6 +454,20 @@ function getPositionalHelpEntry(
480
454
  break;
481
455
  }
482
456
  }
457
+ if (
458
+ (positional.minValueInclusive !== undefined) ||
459
+ (positional.maxValueInclusive !== undefined)
460
+ ) {
461
+ let range = "..";
462
+
463
+ if (positional.minValueInclusive !== undefined) {
464
+ range = positional.minValueInclusive + range;
465
+ }
466
+ if (positional.maxValueInclusive !== undefined) {
467
+ range += positional.maxValueInclusive;
468
+ }
469
+ notesItems.push(range);
470
+ }
483
471
 
484
472
  if (positional.isVarargOptional) {
485
473
  notesItems.push("optional");
@@ -633,7 +621,7 @@ export function getMultiCommandAppSyntax(
633
621
  let argOptional = false;
634
622
  let argValueOptional = false;
635
623
  let multipleArg = false;
636
- const commandClauses = [];
624
+ const commandClauses: string[] = [];
637
625
  if (globalCommands.length > 0) {
638
626
  commandClauses.push(`<${globalPrefix}command>`);
639
627
  arg = globalCommands.some((
@@ -749,3 +737,31 @@ export function getMultiCommandAppSyntax(
749
737
  }
750
738
  return syntax;
751
739
  }
740
+
741
+ export function findPossibleCommandNames(
742
+ commandName: string,
743
+ groupCommands: ReadonlyArray<GroupCommand>,
744
+ subCommands: ReadonlyArray<SubCommand>,
745
+ ): string[] {
746
+ const levenCommandArray = new Array<[number, string]>();
747
+ subCommands.forEach((subCommand) => {
748
+ levenCommandArray.push([
749
+ distance(subCommand.name, commandName),
750
+ subCommand.name,
751
+ ]);
752
+ });
753
+ groupCommands.forEach((groupCommand) => {
754
+ groupCommand.memberSubCommands.forEach((memberCommand) => {
755
+ const memberName = `${groupCommand.name}:${memberCommand.name}`;
756
+ levenCommandArray.push([
757
+ distance(memberName, commandName),
758
+ memberName,
759
+ ]);
760
+ });
761
+ });
762
+ return levenCommandArray
763
+ .sort((a, b) => a[0] - b[0])
764
+ .slice(0, 2)
765
+ .filter((value) => value[0] < 3)
766
+ .map((value) => value[1]);
767
+ }
@@ -86,9 +86,11 @@ function wrapDefaultLogger(
86
86
  * @returns A logger instance with the specified name.
87
87
  */
88
88
  export default function getLogger(loggerName: string): Logger {
89
- if (globalThis.defaultLogger === undefined) {
90
- globalThis.defaultLogger = getDefaultLogger();
89
+ const g = globalThis as Record<string, unknown>;
90
+ if (g.defaultLogger === undefined) {
91
+ g.defaultLogger = getDefaultLogger();
91
92
  }
93
+ const defaultLogger = g.defaultLogger as Logger;
92
94
 
93
95
  if (loggerName.length > maxLoggerNameLength) {
94
96
  maxLoggerNameLength = loggerName.length;
@@ -108,28 +110,28 @@ export default function getLogger(loggerName: string): Logger {
108
110
  return {
109
111
  trace: wrapDefaultLogger(
110
112
  loggerName,
111
- LEVEL_PADDINGS.TRACE,
112
- globalThis.defaultLogger.trace,
113
+ LEVEL_PADDINGS.TRACE!,
114
+ defaultLogger.trace,
113
115
  ),
114
116
  debug: wrapDefaultLogger(
115
117
  loggerName,
116
- LEVEL_PADDINGS.DEBUG,
117
- globalThis.defaultLogger.debug,
118
+ LEVEL_PADDINGS.DEBUG!,
119
+ defaultLogger.debug,
118
120
  ),
119
121
  info: wrapDefaultLogger(
120
122
  loggerName,
121
- LEVEL_PADDINGS.INFO,
122
- globalThis.defaultLogger.info,
123
+ LEVEL_PADDINGS.INFO!,
124
+ defaultLogger.info,
123
125
  ),
124
126
  warn: wrapDefaultLogger(
125
127
  loggerName,
126
- LEVEL_PADDINGS.WARN,
127
- globalThis.defaultLogger.warn,
128
+ LEVEL_PADDINGS.WARN!,
129
+ defaultLogger.warn,
128
130
  ),
129
131
  error: wrapDefaultLogger(
130
132
  loggerName,
131
- LEVEL_PADDINGS.ERROR,
132
- globalThis.defaultLogger.error,
133
+ LEVEL_PADDINGS.ERROR!,
134
+ defaultLogger.error,
133
135
  ),
134
136
  };
135
137
  }
@@ -140,8 +142,8 @@ export default function getLogger(loggerName: string): Logger {
140
142
  warn: () => {},
141
143
  error: wrapDefaultLogger(
142
144
  loggerName,
143
- LEVEL_PADDINGS.ERROR,
144
- globalThis.defaultLogger.error,
145
+ LEVEL_PADDINGS.ERROR!,
146
+ defaultLogger.error,
145
147
  ),
146
148
  };
147
149
  }
@@ -10,6 +10,8 @@ import {
10
10
  } from "../runtime/command/CommandTypeGuards.ts";
11
11
  import type Context from "../api/Context.ts";
12
12
  import { getInvalidArgumentString } from "../runtime/values/argumentValueValidation.ts";
13
+ import type CommandRegistry from "../runtime/registry/CommandRegistry.ts";
14
+ import { findPossibleCommandNames } from "./helpHelper.ts";
13
15
 
14
16
  function getCommandString(
15
17
  printerService: PrinterService,
@@ -91,6 +93,45 @@ export async function printNoCommandSpecifiedError(context: Context) {
91
93
  await printerService.error("No command specified\n\n");
92
94
  }
93
95
 
96
+ export async function printNoCommandRecognisedError(
97
+ context: Context,
98
+ commandRegistry: CommandRegistry,
99
+ availableArgs: ReadonlyArray<string>,
100
+ ) {
101
+ const printerService = context.getServiceById(
102
+ PRINTER_SERVICE_ID,
103
+ ) as PrinterService;
104
+ await printerService.error("No command recognised\n\n");
105
+
106
+ if (availableArgs.length > 0) {
107
+ const groupCommands = commandRegistry.getGroupCommands();
108
+ const subCommands = commandRegistry.getSubCommands();
109
+
110
+ const allPossibleCommandNames: string[] = [];
111
+ for (const arg of availableArgs) {
112
+ // look for other possible matches
113
+ const possibleCommandNames = findPossibleCommandNames(
114
+ arg,
115
+ groupCommands,
116
+ subCommands,
117
+ );
118
+ // add the possible command names to the list if not already present
119
+ for (const possibleCommandName of possibleCommandNames) {
120
+ if (!allPossibleCommandNames.includes(possibleCommandName)) {
121
+ allPossibleCommandNames.push(possibleCommandName);
122
+ }
123
+ }
124
+ }
125
+
126
+ if (allPossibleCommandNames.length > 0) {
127
+ await printerService.print(
128
+ `Possible matches: ${allPossibleCommandNames.join(", ")}\n\n`,
129
+ Icon.INFORMATION,
130
+ );
131
+ }
132
+ }
133
+ }
134
+
94
135
  export async function printUnusedArgsWarning(
95
136
  context: Context,
96
137
  overallUnusedArgs: ReadonlyArray<string>,