@flowscripter/dynamic-cli-framework 1.1.6 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release-bun-library.yml +1 -0
- package/README/core-commands.md +40 -0
- package/README/core-service-providers.md +198 -0
- package/README/development.md +60 -0
- package/README/implementation-details.md +474 -0
- package/README/key-concepts.md +751 -0
- package/README.md +12 -1413
- package/bun.lock +20 -18
- package/index.ts +97 -1
- package/package.json +8 -7
- package/src/api/BaseCLIFeatureOptions.ts +10 -0
- package/src/api/CLIConfig.ts +5 -0
- package/src/api/RunResult.ts +16 -0
- package/src/api/argument/Argument.ts +24 -0
- package/src/api/service/ServiceProvider.ts +3 -3
- package/src/api/service/core/ArgumentPrompterService.ts +10 -0
- package/src/api/service/core/AsciiBannerGeneratorService.ts +44 -7
- package/src/api/service/core/CompletionService.ts +36 -0
- package/src/api/service/core/DataDumpGeneratorService.ts +27 -0
- package/src/api/service/core/ImagePrinterService.ts +26 -0
- package/src/api/service/core/KeyValueService.ts +22 -5
- package/src/api/service/core/PrinterService.ts +127 -1
- package/src/api/service/core/PrompterService.ts +49 -0
- package/src/api/service/core/SecretService.ts +27 -0
- package/src/api/service/core/ShutdownService.ts +17 -1
- package/src/api/service/core/Table.ts +85 -0
- package/src/api/service/core/TableGeneratorService.ts +38 -0
- package/src/api/service/core/TreePrinterService.ts +11 -0
- package/src/cli/BaseCLI.ts +116 -44
- package/src/cli/DefaultRuntimeCLI.ts +10 -18
- package/src/command/CompletionCommand.ts +179 -0
- package/src/command/MultiCommandCliHelpCommand.ts +18 -2
- package/src/command/SingleCommandCliHelpCommand.ts +10 -1
- package/src/launcher.ts +16 -58
- package/src/runtime/command/CommandValidator.ts +3 -3
- package/src/runtime/runner.ts +102 -30
- package/src/runtime/scanner.ts +18 -15
- package/src/runtime/values/argumentValueValidation.ts +49 -5
- package/src/runtime/values/globalCommandValuePopulation.ts +1 -1
- package/src/runtime/values/subCommandValuePopulation.ts +9 -8
- package/src/service/argumentPrompter/ArgumentPrompterServiceProvider.ts +34 -0
- package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +330 -0
- package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +1 -1
- package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +254 -17
- package/src/service/banner/BannerServiceProvider.ts +2 -2
- package/src/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.ts +85 -0
- package/src/service/chiselAsciiBannerGenerator/chisel.flf.json +3 -0
- package/src/service/completion/CompletionServiceProvider.ts +212 -0
- package/src/service/completion/DefaultCompletionService.ts +364 -0
- package/src/service/completion/shell/ShellHandler.ts +16 -0
- package/src/service/completion/shell/bash.ts +47 -0
- package/src/service/completion/shell/fish.ts +43 -0
- package/src/service/completion/shell/powershell.ts +66 -0
- package/src/service/completion/shell/zsh.ts +50 -0
- package/src/service/configuration/ConfigurationServiceProvider.ts +138 -42
- package/src/service/configuration/DefaultKeyValueService.ts +53 -7
- package/src/service/configuration/DefaultSecretService.ts +85 -0
- package/src/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.ts +41 -0
- package/src/service/dataDumpGenerator/DefaultDataDumpGeneratorService.ts +80 -0
- package/src/service/imagePrinter/DefaultImagePrinterService.ts +23 -0
- package/src/service/imagePrinter/ImagePrinterServiceProvider.ts +32 -0
- package/src/service/prettyPrinter/PrettyPrinterServiceProvider.ts +1 -1
- package/src/service/printer/DefaultPrinterService.ts +147 -27
- package/src/service/printer/PrinterServiceProvider.ts +7 -7
- package/src/service/printer/command/NoHyperlinksCommand.ts +40 -0
- package/src/service/printer/terminal/Progress.ts +21 -4
- package/src/service/printer/terminal/Spinner.ts +32 -8
- package/src/service/printer/terminal/Theme.ts +1 -1
- package/src/service/prompter/DefaultPrompterService.ts +87 -0
- package/src/service/prompter/PrompterServiceProvider.ts +36 -0
- package/src/service/prompter/command/NoPromptCommand.ts +40 -0
- package/src/service/prompter/prompt/PromptContext.ts +47 -0
- package/src/service/prompter/prompt/promptAcknowledge.ts +28 -0
- package/src/service/prompter/prompt/promptMultiSelect.ts +99 -0
- package/src/service/prompter/prompt/promptOpenUrl.ts +97 -0
- package/src/service/prompter/prompt/promptSingleSelect.ts +102 -0
- package/src/service/prompter/prompt/promptText.ts +107 -0
- package/src/service/prompter/prompt/promptToggle.ts +28 -0
- package/src/service/shutdown/DefaultShutdownService.ts +13 -0
- package/src/service/shutdown/ShutdownServiceProvider.ts +28 -9
- package/src/service/shutdown/ShutdownState.ts +5 -0
- package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +1 -1
- package/src/service/tableGenerator/DefaultTableGeneratorService.ts +415 -0
- package/src/service/tableGenerator/TableGeneratorServiceProvider.ts +43 -0
- package/src/service/treePrinter/DefaultTreePrinterService.ts +49 -0
- package/src/service/treePrinter/TreePrinterServiceProvider.ts +40 -0
- package/src/terminal/Ansi.ts +27 -0
- package/src/terminal/ImageRenderer.ts +199 -0
- package/src/terminal/KeyReader.ts +23 -0
- package/src/terminal/PngDecoder.ts +143 -0
- package/src/{service/printer/terminal → terminal}/Styler.ts +3 -0
- package/src/{service/printer/terminal → terminal}/Terminal.ts +1 -0
- package/src/terminal/TtyKeyReader.ts +83 -0
- package/src/terminal/TtyStyler.ts +78 -0
- package/src/{service/printer/terminal → terminal}/TtyTerminal.ts +9 -1
- package/src/terminal/supportsHyperlinks.ts +110 -0
- package/src/util/configHelper.ts +2 -2
- package/src/util/envVarHelper.ts +2 -2
- package/src/util/helpHelper.ts +66 -122
- package/src/util/logger.ts +16 -14
- package/src/util/runnerHelper.ts +1 -1
- package/tests/cli/BaseCLI.test.ts +50 -54
- package/tests/command/CompletionCommand.test.ts +68 -0
- package/tests/command/UsageCommand.test.ts +3 -2
- package/tests/command/VersionCommand.test.ts +3 -2
- package/tests/fixtures/Context.ts +9 -2
- package/tests/fixtures/util.ts +1 -1
- package/tests/runtime/parser.test.ts +9 -7
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
- package/tests/runtime/values/argumentValueValidation.test.ts +193 -0
- package/tests/service/argumentPrompter/ArgumentPrompterServiceProvider.test.ts +47 -0
- package/tests/service/argumentPrompter/DefaultArgumentPrompterService.test.ts +754 -0
- package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +5 -4
- package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +163 -3
- package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +1 -1
- package/tests/service/banner/BannerServiceProvider.test.ts +8 -4
- package/tests/service/chiselAsciiBannerGenerator/ChiselFontAsciiBannerGeneratorService.test.ts +52 -0
- package/tests/service/completion/CompletionServiceProvider.test.ts +404 -0
- package/tests/service/completion/DefaultCompletionService.test.ts +278 -0
- package/tests/service/completion/shell/bash.test.ts +73 -0
- package/tests/service/completion/shell/fish.test.ts +62 -0
- package/tests/service/completion/shell/powershell.test.ts +59 -0
- package/tests/service/completion/shell/zsh.test.ts +60 -0
- package/tests/service/configuration/ConfigurationServiceProvider.test.ts +24 -12
- package/tests/service/configuration/DefaultKeyValueService.test.ts +118 -16
- package/tests/service/configuration/DefaultSecretService.test.ts +148 -0
- package/tests/service/configuration/command/ConfigCommand.test.ts +29 -0
- package/tests/service/configuration/command/DumpConfigCommand.test.ts +60 -0
- package/tests/service/dataDumpGenerator/DataDumpGeneratorServiceProvider.test.ts +57 -0
- package/tests/service/dataDumpGenerator/DefaultDataDumpGeneratorService.test.ts +217 -0
- package/tests/service/imagePrinter/DefaultImagePrinterService.test.ts +96 -0
- package/tests/service/imagePrinter/ImagePrinterServiceProvider.test.ts +17 -0
- package/tests/service/prettyPrinter/DefaultPrettyPrinterService.test.ts +2 -2
- package/tests/service/prettyPrinter/PrettyPrinterServiceProvider.test.ts +5 -4
- package/tests/service/printer/DefaultPrinterService.test.ts +233 -2
- package/tests/service/printer/PrinterServiceProvider.test.ts +6 -5
- package/tests/service/printer/command/DarkModeCommand.test.ts +83 -0
- package/tests/service/printer/command/LogLevelCommand.test.ts +7 -5
- package/tests/service/printer/command/NoColorCommand.test.ts +83 -0
- package/tests/service/printer/command/NoHyperlinksCommand.test.ts +89 -0
- package/tests/service/printer/terminal/Progress.test.ts +64 -4
- package/tests/service/printer/terminal/Spinner.test.ts +44 -3
- package/tests/service/prompter/DefaultPrompterService.test.ts +615 -0
- package/tests/service/prompter/PrompterServiceProvider.test.ts +47 -0
- package/tests/service/prompter/command/NoPromptCommand.test.ts +58 -0
- package/tests/service/shutdown/ShutdownServiceProvider.test.ts +4 -2
- package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +3 -2
- package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +5 -4
- package/tests/service/tableGenerator/DefaultTableGeneratorService.test.ts +363 -0
- package/tests/service/tableGenerator/TableGeneratorServiceProvider.test.ts +47 -0
- package/tests/service/treePrinter/DefaultTreePrinterService.test.ts +144 -0
- package/tests/service/treePrinter/TreePrinterServiceProvider.test.ts +55 -0
- package/tests/terminal/ImageRenderer.test.ts +161 -0
- package/tests/terminal/PngDecoder.test.ts +223 -0
- package/tests/terminal/supportsHyperlinks.test.ts +149 -0
- package/tests/util/configHelper.test.ts +4 -4
- package/tests/util/logger.test.ts +23 -1
- package/tsconfig.json +31 -0
- package/src/service/printer/terminal/Ansi.ts +0 -10
- package/src/service/printer/terminal/TtyStyler.ts +0 -39
package/src/util/envVarHelper.ts
CHANGED
|
@@ -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]
|
|
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,
|
package/src/util/helpHelper.ts
CHANGED
|
@@ -21,12 +21,12 @@ import type CLIConfig from "../api/CLIConfig.ts";
|
|
|
21
21
|
import type PrinterService from "../api/service/core/PrinterService.ts";
|
|
22
22
|
import type SubCommandArgument from "../api/argument/SubCommandArgument.ts";
|
|
23
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";
|
|
24
26
|
|
|
25
27
|
const SYNTAX_INDENT_WIDTH = 2;
|
|
26
|
-
const SYNTAX_MIN_PADDING_WIDTH = 2;
|
|
27
28
|
const MINIMUM_SYNTAX_COLUMN_WIDTH = 15;
|
|
28
29
|
const HELP_SYNTAX_INDENT = " ".repeat(SYNTAX_INDENT_WIDTH);
|
|
29
|
-
const HELP_SYNTAX_PADDING = " ".repeat(SYNTAX_MIN_PADDING_WIDTH);
|
|
30
30
|
|
|
31
31
|
export interface HelpEntry {
|
|
32
32
|
readonly syntax: string;
|
|
@@ -40,169 +40,113 @@ export interface HelpSection {
|
|
|
40
40
|
readonly helpEntries: Array<HelpEntry>;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
interface FlattenedRow {
|
|
44
|
+
syntax: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function flattenHelpEntry(
|
|
44
49
|
printerService: PrinterService,
|
|
45
50
|
helpEntry: HelpEntry,
|
|
46
|
-
syntaxWidth: number,
|
|
47
51
|
indentText: string,
|
|
48
52
|
isRoot: boolean,
|
|
49
53
|
isLastSibling: boolean,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let syntaxTreeChars = "";
|
|
54
|
-
let totalSyntaxLength = 0;
|
|
55
|
-
|
|
54
|
+
rows: FlattenedRow[],
|
|
55
|
+
): void {
|
|
56
|
+
let syntax = "";
|
|
56
57
|
if (helpEntry.syntax) {
|
|
57
58
|
if (isRoot) {
|
|
58
|
-
|
|
59
|
-
totalSyntaxLength = SYNTAX_INDENT_WIDTH + helpEntry.syntax.length;
|
|
59
|
+
syntax = HELP_SYNTAX_INDENT + printerService.primary(helpEntry.syntax);
|
|
60
60
|
} else {
|
|
61
|
+
let syntaxTreeChars = "";
|
|
61
62
|
if (indentText.length > 0) {
|
|
62
63
|
syntaxTreeChars = isLastSibling ? "└╴" : "├╴";
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
syntax = HELP_SYNTAX_INDENT +
|
|
65
66
|
printerService.secondary(indentText + syntaxTreeChars) +
|
|
66
67
|
printerService.primary(helpEntry.syntax);
|
|
67
|
-
totalSyntaxLength = SYNTAX_INDENT_WIDTH + indentText.length +
|
|
68
|
-
syntaxTreeChars.length + helpEntry.syntax.length;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
if (helpEntry.description) {
|
|
72
|
-
if (helpEntry.syntax) {
|
|
73
|
-
if (totalSyntaxLength > notesIndent) {
|
|
74
|
-
text += HELP_SYNTAX_PADDING +
|
|
75
|
-
printerService.secondary(helpEntry.description);
|
|
76
|
-
} else {
|
|
77
|
-
text += " ".repeat(syntaxWidth - totalSyntaxLength) +
|
|
78
|
-
printerService.secondary(helpEntry.description);
|
|
79
|
-
}
|
|
80
|
-
} else {
|
|
81
|
-
text += HELP_SYNTAX_PADDING +
|
|
82
|
-
printerService.secondary(helpEntry.description);
|
|
83
68
|
}
|
|
84
69
|
}
|
|
85
|
-
text += "\n";
|
|
86
|
-
await printerService.print(text);
|
|
87
|
-
}
|
|
88
70
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (hasChildren) {
|
|
99
|
-
await printSingleHelpEntry(
|
|
100
|
-
printerService,
|
|
101
|
-
helpEntry,
|
|
102
|
-
syntaxWidth,
|
|
103
|
-
indentText,
|
|
104
|
-
isRoot,
|
|
105
|
-
isLastSibling,
|
|
106
|
-
);
|
|
107
|
-
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 ? " " : "│ ") +
|
|
108
80
|
" ".repeat(helpEntry.subEntryIndent || 0);
|
|
109
|
-
for (let i = 0; i < helpEntry.helpSubEntries
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const helpSubEntryIsLastSibling =
|
|
113
|
-
i === helpEntry.helpSubEntries!.length - 1;
|
|
114
|
-
await printHelpEntry(
|
|
81
|
+
for (let i = 0; i < helpEntry.helpSubEntries.length; i++) {
|
|
82
|
+
const sub = helpEntry.helpSubEntries[i]!;
|
|
83
|
+
flattenHelpEntry(
|
|
115
84
|
printerService,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
indentText,
|
|
85
|
+
sub,
|
|
86
|
+
nextIndent,
|
|
119
87
|
false,
|
|
120
|
-
|
|
121
|
-
|
|
88
|
+
i === helpEntry.helpSubEntries.length - 1,
|
|
89
|
+
rows,
|
|
122
90
|
);
|
|
123
91
|
}
|
|
124
|
-
} else {
|
|
125
|
-
await printSingleHelpEntry(
|
|
126
|
-
printerService,
|
|
127
|
-
helpEntry,
|
|
128
|
-
syntaxWidth,
|
|
129
|
-
indentText,
|
|
130
|
-
isRoot,
|
|
131
|
-
isLastSibling,
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function getSyntaxWidth(helpEntry: HelpEntry, indent: number): number {
|
|
137
|
-
if (
|
|
138
|
-
(helpEntry.helpSubEntries === undefined) ||
|
|
139
|
-
(helpEntry.helpSubEntries.length === 0)
|
|
140
|
-
) {
|
|
141
|
-
// 2 is for the tree characters
|
|
142
|
-
let finalWidth = SYNTAX_INDENT_WIDTH + indent + 2 +
|
|
143
|
-
SYNTAX_MIN_PADDING_WIDTH;
|
|
144
|
-
if (helpEntry.syntax) {
|
|
145
|
-
finalWidth += helpEntry.syntax.length;
|
|
146
|
-
}
|
|
147
|
-
return finalWidth;
|
|
148
92
|
}
|
|
149
|
-
let maxSubIndent = 0;
|
|
150
|
-
helpEntry.helpSubEntries.forEach((helpSubEntry) => {
|
|
151
|
-
// 2 is for the tree characters
|
|
152
|
-
const subEntryWidth = getSyntaxWidth(
|
|
153
|
-
helpSubEntry,
|
|
154
|
-
(helpEntry.subEntryIndent ? helpEntry.subEntryIndent : 0) + 2,
|
|
155
|
-
);
|
|
156
|
-
if (subEntryWidth > maxSubIndent) {
|
|
157
|
-
maxSubIndent = subEntryWidth;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
return indent + maxSubIndent;
|
|
161
93
|
}
|
|
162
94
|
|
|
163
95
|
export async function printHelpSections(
|
|
164
96
|
printerService: PrinterService,
|
|
97
|
+
tableGeneratorService: TableGeneratorService,
|
|
165
98
|
sections: Array<HelpSection>,
|
|
166
99
|
): Promise<void> {
|
|
167
|
-
|
|
168
|
-
let syntaxWidth = SYNTAX_INDENT_WIDTH + MINIMUM_SYNTAX_COLUMN_WIDTH +
|
|
169
|
-
SYNTAX_MIN_PADDING_WIDTH;
|
|
170
|
-
sections.forEach((section) => {
|
|
171
|
-
section.helpEntries.forEach((helpEntry) => {
|
|
172
|
-
const entrySyntaxWidth = getSyntaxWidth(helpEntry, 0);
|
|
173
|
-
// don't expand out if no description as we can overwrite that space on the line
|
|
174
|
-
if (
|
|
175
|
-
helpEntry.description && (helpEntry.description.length > 0) &&
|
|
176
|
-
(entrySyntaxWidth > syntaxWidth)
|
|
177
|
-
) {
|
|
178
|
-
syntaxWidth = entrySyntaxWidth;
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
});
|
|
100
|
+
const terminalWidth = printerService.stdoutColumns();
|
|
182
101
|
|
|
183
|
-
// print the help
|
|
184
102
|
for (let i = 0; i < sections.length; i++) {
|
|
185
|
-
const section = sections[i]
|
|
103
|
+
const section = sections[i]!;
|
|
186
104
|
if (section.title.length > 0) {
|
|
187
105
|
await printerService.print(
|
|
188
106
|
`${printerService.emphasised(section.title)}\n\n`,
|
|
189
107
|
);
|
|
190
108
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
109
|
+
|
|
110
|
+
if (section.helpEntries.length === 0) continue;
|
|
111
|
+
|
|
112
|
+
const rows: FlattenedRow[] = [];
|
|
113
|
+
for (const helpEntry of section.helpEntries) {
|
|
114
|
+
flattenHelpEntry(
|
|
194
115
|
printerService,
|
|
195
116
|
helpEntry,
|
|
196
|
-
syntaxWidth,
|
|
197
117
|
"",
|
|
198
118
|
true,
|
|
199
|
-
helpEntry.helpSubEntries !== undefined,
|
|
200
119
|
true,
|
|
120
|
+
rows,
|
|
201
121
|
);
|
|
202
122
|
}
|
|
203
|
-
|
|
204
|
-
|
|
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
|
+
}
|
|
205
147
|
}
|
|
148
|
+
|
|
149
|
+
await printerService.print("\n");
|
|
206
150
|
}
|
|
207
151
|
}
|
|
208
152
|
|
package/src/util/logger.ts
CHANGED
|
@@ -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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
113
|
+
LEVEL_PADDINGS.TRACE!,
|
|
114
|
+
defaultLogger.trace,
|
|
113
115
|
),
|
|
114
116
|
debug: wrapDefaultLogger(
|
|
115
117
|
loggerName,
|
|
116
|
-
LEVEL_PADDINGS.DEBUG
|
|
117
|
-
|
|
118
|
+
LEVEL_PADDINGS.DEBUG!,
|
|
119
|
+
defaultLogger.debug,
|
|
118
120
|
),
|
|
119
121
|
info: wrapDefaultLogger(
|
|
120
122
|
loggerName,
|
|
121
|
-
LEVEL_PADDINGS.INFO
|
|
122
|
-
|
|
123
|
+
LEVEL_PADDINGS.INFO!,
|
|
124
|
+
defaultLogger.info,
|
|
123
125
|
),
|
|
124
126
|
warn: wrapDefaultLogger(
|
|
125
127
|
loggerName,
|
|
126
|
-
LEVEL_PADDINGS.WARN
|
|
127
|
-
|
|
128
|
+
LEVEL_PADDINGS.WARN!,
|
|
129
|
+
defaultLogger.warn,
|
|
128
130
|
),
|
|
129
131
|
error: wrapDefaultLogger(
|
|
130
132
|
loggerName,
|
|
131
|
-
LEVEL_PADDINGS.ERROR
|
|
132
|
-
|
|
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
|
-
|
|
145
|
+
LEVEL_PADDINGS.ERROR!,
|
|
146
|
+
defaultLogger.error,
|
|
145
147
|
),
|
|
146
148
|
};
|
|
147
149
|
}
|
package/src/util/runnerHelper.ts
CHANGED
|
@@ -10,7 +10,7 @@ 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 CommandRegistry from "../runtime/registry/CommandRegistry.ts";
|
|
13
|
+
import type CommandRegistry from "../runtime/registry/CommandRegistry.ts";
|
|
14
14
|
import { findPossibleCommandNames } from "./helpHelper.ts";
|
|
15
15
|
|
|
16
16
|
function getCommandString(
|
|
@@ -23,24 +23,31 @@ import type Context from "../../src/api/Context.ts";
|
|
|
23
23
|
import type CLIConfig from "../../src/api/CLIConfig.ts";
|
|
24
24
|
import StreamString from "../fixtures/StreamString.ts";
|
|
25
25
|
import { expectStringEquals, expectStringIncludes } from "../fixtures/util.ts";
|
|
26
|
-
import TtyTerminal from "../../src/
|
|
27
|
-
import TtyStyler from "../../src/
|
|
26
|
+
import TtyTerminal from "../../src/terminal/TtyTerminal.ts";
|
|
27
|
+
import TtyStyler from "../../src/terminal/TtyStyler.ts";
|
|
28
|
+
import type KeyReader from "../../src/terminal/KeyReader.ts";
|
|
29
|
+
|
|
30
|
+
const mockKeyReader: KeyReader = {
|
|
31
|
+
enableRawMode() {},
|
|
32
|
+
disableRawMode() {},
|
|
33
|
+
readKey: () => Promise.resolve({}),
|
|
34
|
+
};
|
|
28
35
|
|
|
29
36
|
describe("BaseCLI tests", () => {
|
|
30
37
|
test("BaseCLI no command specified works", async () => {
|
|
31
38
|
const config = getCLIConfig();
|
|
32
39
|
const dummyStdout = new StreamString();
|
|
33
40
|
const dummyStderr = new StreamString();
|
|
34
|
-
const terminal = new TtyTerminal(dummyStdout.writeStream);
|
|
35
41
|
const baseCLI = new BaseCLI(
|
|
36
42
|
config,
|
|
37
43
|
dummyStdout.writableStream,
|
|
38
44
|
dummyStderr.writableStream,
|
|
39
45
|
false,
|
|
40
46
|
false,
|
|
41
|
-
|
|
47
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
48
|
+
new TtyTerminal(dummyStderr.writeStream),
|
|
42
49
|
new TtyStyler(3),
|
|
43
|
-
|
|
50
|
+
mockKeyReader,
|
|
44
51
|
);
|
|
45
52
|
|
|
46
53
|
baseCLI.addCommand(getSubCommandWithOptionAndPositional());
|
|
@@ -54,16 +61,16 @@ describe("BaseCLI tests", () => {
|
|
|
54
61
|
const config = getCLIConfig();
|
|
55
62
|
const dummyStdout = new StreamString();
|
|
56
63
|
const dummyStderr = new StreamString();
|
|
57
|
-
const terminal = new TtyTerminal(dummyStderr.writeStream);
|
|
58
64
|
const baseCLI = new BaseCLI(
|
|
59
65
|
config,
|
|
60
66
|
dummyStdout.writableStream,
|
|
61
67
|
dummyStderr.writableStream,
|
|
62
68
|
false,
|
|
63
69
|
false,
|
|
64
|
-
|
|
70
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
71
|
+
new TtyTerminal(dummyStderr.writeStream),
|
|
65
72
|
new TtyStyler(3),
|
|
66
|
-
|
|
73
|
+
mockKeyReader,
|
|
67
74
|
);
|
|
68
75
|
|
|
69
76
|
let modifierHasRun = false;
|
|
@@ -122,11 +129,14 @@ describe("BaseCLI tests", () => {
|
|
|
122
129
|
dummyStderr.writableStream,
|
|
123
130
|
false,
|
|
124
131
|
false,
|
|
132
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
125
133
|
new TtyTerminal(dummyStderr.writeStream),
|
|
126
134
|
new TtyStyler(3),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
mockKeyReader,
|
|
136
|
+
{
|
|
137
|
+
configFileSupportEnabled: true,
|
|
138
|
+
keyValueServiceEnabled: true,
|
|
139
|
+
},
|
|
130
140
|
);
|
|
131
141
|
|
|
132
142
|
let serviceProvider1Initialised = false;
|
|
@@ -140,21 +150,20 @@ describe("BaseCLI tests", () => {
|
|
|
140
150
|
const SERVICE_2 = "service2";
|
|
141
151
|
|
|
142
152
|
interface ServiceInterface {
|
|
143
|
-
serviceMethod(context: Context): void
|
|
153
|
+
serviceMethod(context: Context): Promise<void>;
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
class DefaultService1 implements ServiceInterface {
|
|
147
157
|
copyOfKeyValueService: KeyValueService | undefined;
|
|
148
158
|
|
|
149
|
-
serviceMethod(context: Context): void {
|
|
150
|
-
|
|
151
|
-
expect(this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
|
|
159
|
+
async serviceMethod(context: Context): Promise<void> {
|
|
160
|
+
expect(await this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
|
|
152
161
|
expect(
|
|
153
|
-
this.copyOfKeyValueService!.getKey("name"),
|
|
162
|
+
await this.copyOfKeyValueService!.getKey("name"),
|
|
154
163
|
).toEqual(
|
|
155
164
|
"modifierCommand",
|
|
156
165
|
);
|
|
157
|
-
this.copyOfKeyValueService!.setKey(
|
|
166
|
+
await this.copyOfKeyValueService!.setKey(
|
|
158
167
|
"name",
|
|
159
168
|
"defaultService1+modifierCommand",
|
|
160
169
|
);
|
|
@@ -163,10 +172,9 @@ describe("BaseCLI tests", () => {
|
|
|
163
172
|
KEY_VALUE_SERVICE_ID,
|
|
164
173
|
) as KeyValueService;
|
|
165
174
|
|
|
166
|
-
|
|
167
|
-
expect(keyValueService.hasKey("name")).toBeTrue();
|
|
175
|
+
expect(await keyValueService.hasKey("name")).toBeTrue();
|
|
168
176
|
expect(
|
|
169
|
-
this.copyOfKeyValueService!.getKey("name"),
|
|
177
|
+
await this.copyOfKeyValueService!.getKey("name"),
|
|
170
178
|
).toEqual(
|
|
171
179
|
"defaultService1+modifierCommand",
|
|
172
180
|
);
|
|
@@ -178,13 +186,12 @@ describe("BaseCLI tests", () => {
|
|
|
178
186
|
class DefaultService2 implements ServiceInterface {
|
|
179
187
|
copyOfKeyValueService: KeyValueService | undefined;
|
|
180
188
|
|
|
181
|
-
serviceMethod(context: Context): void {
|
|
182
|
-
|
|
183
|
-
expect(this.copyOfKeyValueService!.
|
|
184
|
-
expect(this.copyOfKeyValueService!.getKey("name")).toEqual(
|
|
189
|
+
async serviceMethod(context: Context): Promise<void> {
|
|
190
|
+
expect(await this.copyOfKeyValueService!.hasKey("name")).toBeTrue();
|
|
191
|
+
expect(await this.copyOfKeyValueService!.getKey("name")).toEqual(
|
|
185
192
|
"subCommand",
|
|
186
193
|
);
|
|
187
|
-
this.copyOfKeyValueService!.setKey(
|
|
194
|
+
await this.copyOfKeyValueService!.setKey(
|
|
188
195
|
"name",
|
|
189
196
|
"defaultService2+subCommand",
|
|
190
197
|
);
|
|
@@ -193,10 +200,9 @@ describe("BaseCLI tests", () => {
|
|
|
193
200
|
KEY_VALUE_SERVICE_ID,
|
|
194
201
|
) as KeyValueService;
|
|
195
202
|
|
|
196
|
-
|
|
197
|
-
expect(keyValueService.hasKey("name")).toBeTrue();
|
|
203
|
+
expect(await keyValueService.hasKey("name")).toBeTrue();
|
|
198
204
|
expect(
|
|
199
|
-
this.copyOfKeyValueService!.getKey("name"),
|
|
205
|
+
await this.copyOfKeyValueService!.getKey("name"),
|
|
200
206
|
).toEqual(
|
|
201
207
|
"defaultService2+subCommand",
|
|
202
208
|
);
|
|
@@ -211,7 +217,7 @@ describe("BaseCLI tests", () => {
|
|
|
211
217
|
|
|
212
218
|
defaultService1: DefaultService1 | undefined;
|
|
213
219
|
|
|
214
|
-
|
|
220
|
+
getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
215
221
|
this.defaultService1 = new DefaultService1();
|
|
216
222
|
return Promise.resolve({
|
|
217
223
|
service: this.defaultService1,
|
|
@@ -219,20 +225,17 @@ describe("BaseCLI tests", () => {
|
|
|
219
225
|
});
|
|
220
226
|
}
|
|
221
227
|
|
|
222
|
-
initService(context: Context): Promise<void> {
|
|
228
|
+
async initService(context: Context): Promise<void> {
|
|
223
229
|
const keyValueService = context.getServiceById(
|
|
224
230
|
KEY_VALUE_SERVICE_ID,
|
|
225
231
|
) as KeyValueService;
|
|
226
232
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
keyValueService.setKey("name", "defaultService2");
|
|
233
|
+
expect(await keyValueService.hasKey("name")).toBeFalse();
|
|
234
|
+
await keyValueService.setKey("name", "defaultService2");
|
|
230
235
|
|
|
231
|
-
// take a copy to check later when service is accessed via a command
|
|
232
236
|
this.defaultService1!.copyOfKeyValueService = keyValueService;
|
|
233
237
|
|
|
234
238
|
serviceProvider1Initialised = true;
|
|
235
|
-
return Promise.resolve(undefined);
|
|
236
239
|
}
|
|
237
240
|
}
|
|
238
241
|
|
|
@@ -242,7 +245,7 @@ describe("BaseCLI tests", () => {
|
|
|
242
245
|
|
|
243
246
|
defaultService2: DefaultService2 | undefined;
|
|
244
247
|
|
|
245
|
-
|
|
248
|
+
getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
246
249
|
this.defaultService2 = new DefaultService2();
|
|
247
250
|
return Promise.resolve({
|
|
248
251
|
service: this.defaultService2,
|
|
@@ -250,20 +253,17 @@ describe("BaseCLI tests", () => {
|
|
|
250
253
|
});
|
|
251
254
|
}
|
|
252
255
|
|
|
253
|
-
initService(context: Context): Promise<void> {
|
|
256
|
+
async initService(context: Context): Promise<void> {
|
|
254
257
|
const keyValueService = context.getServiceById(
|
|
255
258
|
KEY_VALUE_SERVICE_ID,
|
|
256
259
|
) as KeyValueService;
|
|
257
260
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
keyValueService.setKey("name", "defaultService2");
|
|
261
|
+
expect(await keyValueService.hasKey("name")).toBeFalse();
|
|
262
|
+
await keyValueService.setKey("name", "defaultService2");
|
|
261
263
|
|
|
262
|
-
// take a copy to check later when service is accessed via a command
|
|
263
264
|
this.defaultService2!.copyOfKeyValueService = keyValueService;
|
|
264
265
|
|
|
265
266
|
serviceProvider2Initialised = true;
|
|
266
|
-
return Promise.resolve(undefined);
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
@@ -282,37 +282,33 @@ describe("BaseCLI tests", () => {
|
|
|
282
282
|
};
|
|
283
283
|
const subCommand = getSubCommand("command", [option], []);
|
|
284
284
|
|
|
285
|
-
modifierCommand.execute = (context): Promise<void> => {
|
|
285
|
+
modifierCommand.execute = async (context): Promise<void> => {
|
|
286
286
|
const keyValueService = context.getServiceById(
|
|
287
287
|
KEY_VALUE_SERVICE_ID,
|
|
288
288
|
) as KeyValueService;
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
keyValueService.setKey("name", "modifierCommand");
|
|
290
|
+
expect(await keyValueService.hasKey("name")).toBeFalse();
|
|
291
|
+
await keyValueService.setKey("name", "modifierCommand");
|
|
293
292
|
|
|
294
293
|
const service1 = context.getServiceById(SERVICE_1) as ServiceInterface;
|
|
295
294
|
|
|
296
|
-
service1.serviceMethod(context);
|
|
295
|
+
await service1.serviceMethod(context);
|
|
297
296
|
|
|
298
297
|
modifierHasRun = true;
|
|
299
|
-
return Promise.resolve();
|
|
300
298
|
};
|
|
301
|
-
subCommand.execute = (context): Promise<void> => {
|
|
299
|
+
subCommand.execute = async (context): Promise<void> => {
|
|
302
300
|
const keyValueService = context.getServiceById(
|
|
303
301
|
KEY_VALUE_SERVICE_ID,
|
|
304
302
|
) as KeyValueService;
|
|
305
303
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
keyValueService.setKey("name", "subCommand");
|
|
304
|
+
expect(await keyValueService.hasKey("name")).toBeFalse();
|
|
305
|
+
await keyValueService.setKey("name", "subCommand");
|
|
309
306
|
|
|
310
307
|
const service2 = context.getServiceById(SERVICE_2) as ServiceInterface;
|
|
311
308
|
|
|
312
|
-
service2.serviceMethod(context);
|
|
309
|
+
await service2.serviceMethod(context);
|
|
313
310
|
|
|
314
311
|
subHasRun = true;
|
|
315
|
-
return Promise.resolve();
|
|
316
312
|
};
|
|
317
313
|
|
|
318
314
|
baseCLI.addServiceProvider(new ServiceProvider1());
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
CompletionCompleteSubCommand,
|
|
4
|
+
CompletionGroupCommand,
|
|
5
|
+
CompletionIntegrationSubCommand,
|
|
6
|
+
} from "../../src/command/CompletionCommand.ts";
|
|
7
|
+
|
|
8
|
+
describe("CompletionIntegrationSubCommand", () => {
|
|
9
|
+
test("has correct name", () => {
|
|
10
|
+
const cmd = new CompletionIntegrationSubCommand();
|
|
11
|
+
expect(cmd.name).toEqual("integration");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("has shell positional with allowable values", () => {
|
|
15
|
+
const cmd = new CompletionIntegrationSubCommand();
|
|
16
|
+
expect(cmd.positionals.length).toEqual(1);
|
|
17
|
+
expect(cmd.positionals[0]!.name).toEqual("shell");
|
|
18
|
+
expect(cmd.positionals[0]!.allowableValues).toEqual([
|
|
19
|
+
"bash",
|
|
20
|
+
"zsh",
|
|
21
|
+
"fish",
|
|
22
|
+
"powershell",
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("has optional config-path option", () => {
|
|
27
|
+
const cmd = new CompletionIntegrationSubCommand();
|
|
28
|
+
expect(cmd.options.length).toEqual(1);
|
|
29
|
+
expect(cmd.options[0]!.name).toEqual("config-path");
|
|
30
|
+
expect(cmd.options[0]!.isOptional).toEqual(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("CompletionCompleteSubCommand", () => {
|
|
35
|
+
test("has correct name", () => {
|
|
36
|
+
const cmd = new CompletionCompleteSubCommand();
|
|
37
|
+
expect(cmd.name).toEqual("complete");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("has shell positional and vararg args positional", () => {
|
|
41
|
+
const cmd = new CompletionCompleteSubCommand();
|
|
42
|
+
expect(cmd.positionals.length).toEqual(2);
|
|
43
|
+
expect(cmd.positionals[0]!.name).toEqual("shell");
|
|
44
|
+
expect(cmd.positionals[1]!.name).toEqual("args");
|
|
45
|
+
expect(cmd.positionals[1]!.isVarargMultiple).toEqual(true);
|
|
46
|
+
expect(cmd.positionals[1]!.isVarargOptional).toEqual(true);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("CompletionGroupCommand", () => {
|
|
51
|
+
test("has correct name", () => {
|
|
52
|
+
const cmd = new CompletionGroupCommand(
|
|
53
|
+
new CompletionIntegrationSubCommand(),
|
|
54
|
+
new CompletionCompleteSubCommand(),
|
|
55
|
+
);
|
|
56
|
+
expect(cmd.name).toEqual("completions");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("has two member sub-commands", () => {
|
|
60
|
+
const cmd = new CompletionGroupCommand(
|
|
61
|
+
new CompletionIntegrationSubCommand(),
|
|
62
|
+
new CompletionCompleteSubCommand(),
|
|
63
|
+
);
|
|
64
|
+
expect(cmd.memberSubCommands.length).toEqual(2);
|
|
65
|
+
expect(cmd.memberSubCommands[0]!.name).toEqual("integration");
|
|
66
|
+
expect(cmd.memberSubCommands[1]!.name).toEqual("complete");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -9,8 +9,8 @@ import type Context from "../../src/api/Context.ts";
|
|
|
9
9
|
import type Command from "../../src/api/command/Command.ts";
|
|
10
10
|
import StreamString from "../fixtures/StreamString.ts";
|
|
11
11
|
import { expectStringEquals } from "../fixtures/util.ts";
|
|
12
|
-
import TtyTerminal from "../../src/
|
|
13
|
-
import TtyStyler from "../../src/
|
|
12
|
+
import TtyTerminal from "../../src/terminal/TtyTerminal.ts";
|
|
13
|
+
import TtyStyler from "../../src/terminal/TtyStyler.ts";
|
|
14
14
|
|
|
15
15
|
describe("UsageCommand tests", () => {
|
|
16
16
|
test("Usage works", async () => {
|
|
@@ -21,6 +21,7 @@ describe("UsageCommand tests", () => {
|
|
|
21
21
|
dummyStderr.writableStream,
|
|
22
22
|
true,
|
|
23
23
|
true,
|
|
24
|
+
new TtyTerminal(dummyStdout.writeStream),
|
|
24
25
|
new TtyTerminal(dummyStderr.writeStream),
|
|
25
26
|
new TtyStyler(3),
|
|
26
27
|
);
|