@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.
- package/.github/workflows/check-bun-dependencies.yml +9 -0
- package/.github/workflows/lint-pr-message.yml +11 -0
- package/.github/workflows/release-bun-library.yml +8 -0
- package/.github/workflows/validate-bun-library-pr.yml +8 -0
- package/LICENSE +21 -0
- package/README.md +1475 -0
- package/bun.lock +63 -0
- package/index.ts +89 -0
- package/package.json +23 -0
- package/src/api/CLI.ts +17 -0
- package/src/api/CLIConfig.ts +19 -0
- package/src/api/Context.ts +25 -0
- package/src/api/RunResult.ts +142 -0
- package/src/api/argument/Argument.ts +36 -0
- package/src/api/argument/ArgumentValueTypes.ts +87 -0
- package/src/api/argument/ComplexOption.ts +31 -0
- package/src/api/argument/GlobalCommandArgument.ts +17 -0
- package/src/api/argument/Option.ts +32 -0
- package/src/api/argument/Positional.ts +27 -0
- package/src/api/argument/SubCommandArgument.ts +20 -0
- package/src/api/command/Command.ts +22 -0
- package/src/api/command/GlobalCommand.ts +33 -0
- package/src/api/command/GlobalModifierCommand.ts +12 -0
- package/src/api/command/GroupCommand.ts +20 -0
- package/src/api/command/SubCommand.ts +41 -0
- package/src/api/command/UsageExample.ts +21 -0
- package/src/api/service/ServiceProvider.ts +52 -0
- package/src/api/service/core/AsciiBannerGeneratorService.ts +35 -0
- package/src/api/service/core/KeyValueService.ts +28 -0
- package/src/api/service/core/PrinterService.ts +266 -0
- package/src/api/service/core/ShutdownService.ts +12 -0
- package/src/api/service/core/SyntaxHighlighterService.ts +42 -0
- package/src/cli/BaseCLI.ts +312 -0
- package/src/cli/DefaultRuntimeCLI.ts +54 -0
- package/src/command/MultiCommandCliHelpCommand.ts +394 -0
- package/src/command/SingleCommandCliHelpCommand.ts +183 -0
- package/src/command/UsageCommand.ts +35 -0
- package/src/command/VersionCommand.ts +22 -0
- package/src/launcher.ts +127 -0
- package/src/runtime/DefaultContext.ts +32 -0
- package/src/runtime/argument/ArgumentTypeGuards.ts +8 -0
- package/src/runtime/command/CommandTypeGuards.ts +30 -0
- package/src/runtime/command/CommandValidator.ts +549 -0
- package/src/runtime/parser.ts +219 -0
- package/src/runtime/registry/CommandRegistry.ts +121 -0
- package/src/runtime/registry/DefaultCommandRegistry.ts +318 -0
- package/src/runtime/registry/DefaultServiceProviderRegistry.ts +26 -0
- package/src/runtime/registry/ServiceProviderRegistry.ts +11 -0
- package/src/runtime/runner.ts +697 -0
- package/src/runtime/scanner.ts +417 -0
- package/src/runtime/values/ValuePopulationResult.ts +41 -0
- package/src/runtime/values/argumentValueMerge.ts +201 -0
- package/src/runtime/values/argumentValueValidation.ts +610 -0
- package/src/runtime/values/globalCommandValuePopulation.ts +96 -0
- package/src/runtime/values/subCommandValuePopulation.ts +680 -0
- package/src/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.ts +45 -0
- package/src/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.ts +38 -0
- package/src/service/asciiBannerGenerator/standard.flf.json +3 -0
- package/src/service/banner/BannerServiceProvider.ts +99 -0
- package/src/service/banner/command/NoBannerCommand.ts +42 -0
- package/src/service/configuration/ConfigurationServiceProvider.ts +571 -0
- package/src/service/configuration/DefaultKeyValueService.ts +60 -0
- package/src/service/configuration/command/ConfigCommand.ts +43 -0
- package/src/service/configuration/command/DumpConfigCommand.ts +47 -0
- package/src/service/printer/DefaultPrinterService.ts +256 -0
- package/src/service/printer/PrinterServiceProvider.ts +61 -0
- package/src/service/printer/command/DarkModeCommand.ts +43 -0
- package/src/service/printer/command/LogLevelCommand.ts +68 -0
- package/src/service/printer/command/NoColorCommand.ts +43 -0
- package/src/service/printer/terminal/Ansi.ts +10 -0
- package/src/service/printer/terminal/Color.ts +14 -0
- package/src/service/printer/terminal/Progress.ts +373 -0
- package/src/service/printer/terminal/Spinner.ts +100 -0
- package/src/service/printer/terminal/Styler.ts +5 -0
- package/src/service/printer/terminal/Terminal.ts +8 -0
- package/src/service/printer/terminal/Theme.ts +34 -0
- package/src/service/printer/terminal/TtyStyler.ts +39 -0
- package/src/service/printer/terminal/TtyTerminal.ts +65 -0
- package/src/service/shutdown/DefaultShutdownService.ts +9 -0
- package/src/service/shutdown/ShutdownServiceProvider.ts +55 -0
- package/src/service/syntaxHighlighter/DefaultSyntaxHighlighterService.ts +52 -0
- package/src/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.ts +51 -0
- package/src/util/configHelper.ts +95 -0
- package/src/util/envVarHelper.ts +338 -0
- package/src/util/helpHelper.ts +751 -0
- package/src/util/logger.ts +84 -0
- package/src/util/runnerHelper.ts +112 -0
- package/tests/cli/BaseCLI.test.ts +344 -0
- package/tests/command/MultiCommandCliHelpCommand.test.ts +650 -0
- package/tests/command/SingleCommandCliHelpCommand.test.ts +384 -0
- package/tests/command/UsageCommand.test.ts +53 -0
- package/tests/command/VersionCommand.test.ts +36 -0
- package/tests/fixtures/CLIConfig.ts +9 -0
- package/tests/fixtures/Command.ts +229 -0
- package/tests/fixtures/CommandRegistry.ts +8 -0
- package/tests/fixtures/ConfigurationServiceProvider.ts +28 -0
- package/tests/fixtures/Context.ts +33 -0
- package/tests/fixtures/ServiceProviderRegistry.ts +16 -0
- package/tests/fixtures/StreamString.ts +37 -0
- package/tests/fixtures/util.ts +41 -0
- package/tests/runtime/DefaultContext.test.ts +20 -0
- package/tests/runtime/argument/ArgumentTypeGuards.test.ts +49 -0
- package/tests/runtime/command/CommandTypeGuards.test.ts +43 -0
- package/tests/runtime/command/CommandValidator.test.ts +816 -0
- package/tests/runtime/parser.test.ts +766 -0
- package/tests/runtime/registry/DefaultCommandRegistry.test.ts +166 -0
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +42 -0
- package/tests/runtime/runner.test.ts +1298 -0
- package/tests/runtime/scanner.test.ts +1113 -0
- package/tests/runtime/values/argumentValueMerge.test.ts +314 -0
- package/tests/runtime/values/argumentValueValidation.test.ts +1020 -0
- package/tests/runtime/values/globalCommandValuePopulation.test.ts +662 -0
- package/tests/runtime/values/subCommandValuePopulation.test.ts +2658 -0
- package/tests/service/asciiBannerGenerator/AsciiBannerGeneratorServiceProvider.test.ts +22 -0
- package/tests/service/asciiBannerGenerator/DefaultAsciiBannerGeneratorService.test.ts +70 -0
- package/tests/service/asciiBannerGenerator/__snapshots__/DefaultAsciiBannerGeneratorService.test.ts.snap +22 -0
- package/tests/service/asciiBannerGenerator/small.flf.json +3 -0
- package/tests/service/configuration/ConfigurationServiceProvider.test.ts +249 -0
- package/tests/service/configuration/DefaultKeyValueService.test.ts +65 -0
- package/tests/service/launchBanner/BannerServiceProvider.test.ts +49 -0
- package/tests/service/launchBanner/__snapshots__/BannerServiceProvider.test.ts.snap +3 -0
- package/tests/service/launchBanner/command/NoBannerCommand.test.ts +21 -0
- package/tests/service/printer/DefaultPrinterService.test.ts +409 -0
- package/tests/service/printer/PrinterServiceProvider.test.ts +29 -0
- package/tests/service/printer/command/LogLevelCommand.test.ts +58 -0
- package/tests/service/printer/terminal/Progress.test.ts +40 -0
- package/tests/service/printer/terminal/Spinner.test.ts +57 -0
- package/tests/service/shutdown/ShutdownServiceProvider.test.ts +13 -0
- package/tests/service/syntaxHighlighter/DefaultSyntaxHighlighterService.test.ts +123 -0
- package/tests/service/syntaxHighlighter/SyntaxHighlighterServiceProvider.test.ts +41 -0
- package/tests/util/configHelper.test.ts +275 -0
- package/tests/util/envVarHelper.test.ts +318 -0
- 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
|
+
}
|