@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.
- 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 +98 -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 +40 -1
- package/src/api/argument/ArgumentValueTypes.ts +1 -1
- package/src/api/command/SubCommand.ts +1 -1
- 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 +20 -32
- package/src/command/SingleCommandCliHelpCommand.ts +10 -1
- package/src/launcher.ts +16 -58
- package/src/runtime/command/CommandValidator.ts +70 -6
- package/src/runtime/runner.ts +112 -31
- package/src/runtime/scanner.ts +18 -15
- package/src/runtime/values/argumentValueValidation.ts +93 -10
- 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/LogLevelCommand.ts +2 -1
- 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 +139 -123
- package/src/util/logger.ts +16 -14
- package/src/util/runnerHelper.ts +41 -0
- 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/command/CommandValidator.test.ts +103 -0
- package/tests/runtime/parser.test.ts +9 -7
- package/tests/runtime/registry/DefaultServiceProviderRegistry.test.ts +3 -3
- package/tests/runtime/runner.test.ts +36 -0
- package/tests/runtime/values/argumentValueValidation.test.ts +215 -1
- 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 +8 -6
- 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/launcher.ts
CHANGED
|
@@ -1,33 +1,20 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import type CLIConfig from "./api/CLIConfig.ts";
|
|
4
|
+
import type BaseCLIFeatureOptions from "./api/BaseCLIFeatureOptions.ts";
|
|
4
5
|
import type SubCommand from "./api/command/SubCommand.ts";
|
|
5
6
|
import DefaultRuntimeCLI from "./cli/DefaultRuntimeCLI.ts";
|
|
6
7
|
import type Command from "./api/command/Command.ts";
|
|
7
8
|
import type RunResult from "./api/RunResult.ts";
|
|
8
9
|
import type { ServiceProvider } from "./api/service/ServiceProvider.ts";
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* Launch a {@link DefaultRuntimeCLI} with a single specified {@link SubCommand} instance.
|
|
12
|
-
*
|
|
13
|
-
* @param command the {@link SubCommand} to add to the CLI.
|
|
14
|
-
* @param description optional description of the CLI.
|
|
15
|
-
* @param name optional name of the CLI. If not provided, `process.execPath` will be used in an attempt to derive the name.
|
|
16
|
-
* @param version optional version of the CLI.
|
|
17
|
-
* @param envVarsEnabled optionally support checking env variables for default argument values.
|
|
18
|
-
* @param configEnabled optionally enable configuration file support for default argument values.
|
|
19
|
-
* @param keyValueServiceEnabled optionally provide a {@link KeyValueService} implementation: `configEnabled` must be true in this case
|
|
20
|
-
* @param serviceProviders optional array of {@link ServiceProvider} instances to add to the CLI.
|
|
21
|
-
*/
|
|
22
11
|
export async function launchSingleCommandCLI(
|
|
23
12
|
command: SubCommand,
|
|
24
13
|
description?: string,
|
|
25
14
|
name?: string,
|
|
26
15
|
version?: string,
|
|
27
|
-
envVarsEnabled = false,
|
|
28
|
-
configEnabled = false,
|
|
29
|
-
keyValueServiceEnabled = false,
|
|
30
16
|
serviceProviders?: ReadonlyArray<ServiceProvider>,
|
|
17
|
+
options?: BaseCLIFeatureOptions,
|
|
31
18
|
): Promise<RunResult> {
|
|
32
19
|
if (!name) {
|
|
33
20
|
name = path.basename(process.execPath);
|
|
@@ -38,20 +25,12 @@ export async function launchSingleCommandCLI(
|
|
|
38
25
|
version: version || "N/A",
|
|
39
26
|
};
|
|
40
27
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
const
|
|
47
|
-
process.env.DYNAMIC_CLI_FRAMEWORK_VALIDATE_ALL !== undefined;
|
|
48
|
-
const cli = new DefaultRuntimeCLI(
|
|
49
|
-
cliConfig,
|
|
50
|
-
envVarsEnabled,
|
|
51
|
-
configEnabled,
|
|
52
|
-
keyValueServiceEnabled,
|
|
53
|
-
validateAllCommands,
|
|
54
|
-
);
|
|
28
|
+
const mergedOptions: BaseCLIFeatureOptions = {
|
|
29
|
+
...options,
|
|
30
|
+
validateAllCommands: (options?.validateAllCommands ?? false) ||
|
|
31
|
+
process.env.DYNAMIC_CLI_FRAMEWORK_VALIDATE_ALL !== undefined,
|
|
32
|
+
};
|
|
33
|
+
const cli = new DefaultRuntimeCLI(cliConfig, mergedOptions);
|
|
55
34
|
|
|
56
35
|
cli.addCommand(command);
|
|
57
36
|
|
|
@@ -60,27 +39,13 @@ export async function launchSingleCommandCLI(
|
|
|
60
39
|
return await cli.run();
|
|
61
40
|
}
|
|
62
41
|
|
|
63
|
-
/**
|
|
64
|
-
* Launch a {@link DefaultRuntimeCLI} with multiple specified {@link Command} instances.
|
|
65
|
-
*
|
|
66
|
-
* @param commands the {@link Command} instances to add to the CLI.
|
|
67
|
-
* @param description optional description of the CLI.
|
|
68
|
-
* @param name optional name of the CLI. If not provided, `process.execPath` will be used in an attempt to derive the name.
|
|
69
|
-
* @param version optional version of the CLI.
|
|
70
|
-
* @param envVarsEnabled optionally support checking env variables for default argument values.
|
|
71
|
-
* @param configEnabled optionally enable configuration file support for default argument values.
|
|
72
|
-
* @param keyValueServiceEnabled optionally provide a {@link KeyValueService} implementation: `configEnabled` must be true in this case
|
|
73
|
-
* @param serviceProviders optional array of {@link ServiceProvider} instances to add to the CLI.
|
|
74
|
-
*/
|
|
75
42
|
export async function launchMultiCommandCLI(
|
|
76
43
|
commands: ReadonlyArray<Command>,
|
|
77
44
|
description?: string,
|
|
78
45
|
name?: string,
|
|
79
46
|
version?: string,
|
|
80
|
-
envVarsEnabled = false,
|
|
81
|
-
configEnabled = false,
|
|
82
|
-
keyValueServiceEnabled = false,
|
|
83
47
|
serviceProviders?: ReadonlyArray<ServiceProvider>,
|
|
48
|
+
options?: BaseCLIFeatureOptions,
|
|
84
49
|
): Promise<RunResult> {
|
|
85
50
|
if (!name) {
|
|
86
51
|
name = path.basename(process.execPath);
|
|
@@ -90,20 +55,13 @@ export async function launchMultiCommandCLI(
|
|
|
90
55
|
name,
|
|
91
56
|
version: version || "N/A",
|
|
92
57
|
};
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const cli = new DefaultRuntimeCLI(
|
|
101
|
-
cliConfig,
|
|
102
|
-
envVarsEnabled,
|
|
103
|
-
configEnabled,
|
|
104
|
-
keyValueServiceEnabled,
|
|
105
|
-
validateAllCommands,
|
|
106
|
-
);
|
|
58
|
+
|
|
59
|
+
const mergedOptions: BaseCLIFeatureOptions = {
|
|
60
|
+
...options,
|
|
61
|
+
validateAllCommands: (options?.validateAllCommands ?? false) ||
|
|
62
|
+
process.env.DYNAMIC_CLI_FRAMEWORK_VALIDATE_ALL !== undefined,
|
|
63
|
+
};
|
|
64
|
+
const cli = new DefaultRuntimeCLI(cliConfig, mergedOptions);
|
|
107
65
|
|
|
108
66
|
commands.forEach((command) => cli.addCommand(command));
|
|
109
67
|
|
|
@@ -36,6 +36,7 @@ function validateValueType(
|
|
|
36
36
|
value: ArgumentSingleValueType,
|
|
37
37
|
argumentName: string,
|
|
38
38
|
isDefaultValue: boolean,
|
|
39
|
+
isRangeValue: boolean,
|
|
39
40
|
): void {
|
|
40
41
|
switch (type) {
|
|
41
42
|
case ArgumentValueTypeName.BOOLEAN:
|
|
@@ -51,7 +52,7 @@ function validateValueType(
|
|
|
51
52
|
if ((typeof value !== "number") || !Number.isFinite(value)) {
|
|
52
53
|
throw new Error(
|
|
53
54
|
`Specified ${
|
|
54
|
-
isDefaultValue ? "default" : "allowable"
|
|
55
|
+
isDefaultValue ? "default" : isRangeValue ? "range" : "allowable"
|
|
55
56
|
} value: '${value}' for argument: '${argumentName}' should be a finite number`,
|
|
56
57
|
);
|
|
57
58
|
}
|
|
@@ -60,7 +61,7 @@ function validateValueType(
|
|
|
60
61
|
if ((typeof value !== "number") || !Number.isInteger(value)) {
|
|
61
62
|
throw new Error(
|
|
62
63
|
`Specified ${
|
|
63
|
-
isDefaultValue ? "default" : "allowable"
|
|
64
|
+
isDefaultValue ? "default" : isRangeValue ? "range" : "allowable"
|
|
64
65
|
} value: '${value}' for argument: '${argumentName}' should be an integer`,
|
|
65
66
|
);
|
|
66
67
|
}
|
|
@@ -97,7 +98,7 @@ function isNameLegal(name: string): boolean {
|
|
|
97
98
|
function isConfigurationKeyLegal(configurationKey: string): boolean {
|
|
98
99
|
return (configurationKey.length > 0) &&
|
|
99
100
|
isUppercaseAlphaNumericOrUnderscore(configurationKey) &&
|
|
100
|
-
!configurationKey[0]
|
|
101
|
+
!configurationKey[0]!.match(/[0-9]/);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
function validateArgument(argument: Argument, name: string): void {
|
|
@@ -112,10 +113,73 @@ function validateArgument(argument: Argument, name: string): void {
|
|
|
112
113
|
);
|
|
113
114
|
}
|
|
114
115
|
if (argument.allowableValues) {
|
|
116
|
+
if (argument.type === ArgumentValueTypeName.BOOLEAN) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Illegal type: '${argument.type}' for argument: '${name}' with allowable values specified`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
115
121
|
argument.allowableValues.forEach((value) => {
|
|
116
|
-
validateValueType(argument.type, value, name, false);
|
|
122
|
+
validateValueType(argument.type, value, name, false, false);
|
|
117
123
|
});
|
|
118
124
|
}
|
|
125
|
+
if (argument.isCaseInsensitive !== undefined) {
|
|
126
|
+
if (
|
|
127
|
+
(argument.type === ArgumentValueTypeName.BOOLEAN) &&
|
|
128
|
+
(argument.isCaseInsensitive === false)
|
|
129
|
+
) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Illegal type: '${argument.type}' for argument: '${name}' must always be case insensitive`,
|
|
132
|
+
);
|
|
133
|
+
} else if (argument.type !== ArgumentValueTypeName.STRING) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Illegal type: '${argument.type}' for argument: '${name}' with case insensitive specified`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (argument.minValueInclusive !== undefined) {
|
|
140
|
+
if (
|
|
141
|
+
(argument.type !== ArgumentValueTypeName.NUMBER) &&
|
|
142
|
+
(argument.type !== ArgumentValueTypeName.INTEGER)
|
|
143
|
+
) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Illegal type: '${argument.type}' for argument: '${name}' with min value specified`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (argument.allowableValues) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Illegal combination of allowable values and min value for argument: '${name}'`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
validateValueType(
|
|
154
|
+
argument.type,
|
|
155
|
+
argument.minValueInclusive,
|
|
156
|
+
name,
|
|
157
|
+
false,
|
|
158
|
+
true,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (argument.maxValueInclusive !== undefined) {
|
|
162
|
+
if (
|
|
163
|
+
(argument.type !== ArgumentValueTypeName.NUMBER) &&
|
|
164
|
+
(argument.type !== ArgumentValueTypeName.INTEGER)
|
|
165
|
+
) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Illegal type: '${argument.type}' for argument: '${name}' with max value specified`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (argument.allowableValues) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Illegal combination of allowable values and max value for argument: '${name}'`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
validateValueType(
|
|
176
|
+
argument.type,
|
|
177
|
+
argument.maxValueInclusive,
|
|
178
|
+
name,
|
|
179
|
+
false,
|
|
180
|
+
true,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
119
183
|
}
|
|
120
184
|
|
|
121
185
|
function validateSubCommandArgument(
|
|
@@ -304,7 +368,7 @@ export default class CommandValidator {
|
|
|
304
368
|
}
|
|
305
369
|
if (subCommand.positionals) {
|
|
306
370
|
for (let i = 0; i < subCommand.positionals.length; i += 1) {
|
|
307
|
-
const positional = subCommand.positionals[i]
|
|
371
|
+
const positional = subCommand.positionals[i]!;
|
|
308
372
|
validateSubCommandArgument(positional);
|
|
309
373
|
if (argumentNames.includes(positional.name)) {
|
|
310
374
|
throw new Error(
|
|
@@ -332,7 +396,7 @@ export default class CommandValidator {
|
|
|
332
396
|
command: Command,
|
|
333
397
|
argumentAncestry: Array<SubCommandArgument>,
|
|
334
398
|
) {
|
|
335
|
-
const argument = argumentAncestry[argumentAncestry.length - 1]
|
|
399
|
+
const argument = argumentAncestry[argumentAncestry.length - 1]!;
|
|
336
400
|
if (command.enableConfiguration === true) {
|
|
337
401
|
if (
|
|
338
402
|
(argument.configurationKey !== undefined) &&
|
package/src/runtime/runner.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
import getLogger from "../util/logger.ts";
|
|
13
13
|
import {
|
|
14
14
|
printCommandExecutionError,
|
|
15
|
+
printNoCommandRecognisedError,
|
|
15
16
|
printNoCommandSpecifiedError,
|
|
16
17
|
printParseResultError,
|
|
17
18
|
printUnusedArgsWarning,
|
|
@@ -32,9 +33,35 @@ import type GroupCommand from "../api/command/GroupCommand.ts";
|
|
|
32
33
|
import type SubCommand from "../api/command/SubCommand.ts";
|
|
33
34
|
import type GlobalCommand from "../api/command/GlobalCommand.ts";
|
|
34
35
|
import type Command from "../api/command/Command.ts";
|
|
36
|
+
import type ArgumentPrompterService from "../api/service/core/ArgumentPrompterService.ts";
|
|
37
|
+
import { ARGUMENT_PROMPTER_SERVICE_ID } from "../api/service/core/ArgumentPrompterService.ts";
|
|
35
38
|
|
|
36
39
|
const logger = getLogger("runner");
|
|
37
40
|
|
|
41
|
+
async function attemptPromptForMissingArguments(
|
|
42
|
+
parseResult: ParseResult,
|
|
43
|
+
context: Context,
|
|
44
|
+
): Promise<ParseResult | undefined> {
|
|
45
|
+
if (!context.doesServiceExist(ARGUMENT_PROMPTER_SERVICE_ID)) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const service = context.getServiceById(
|
|
49
|
+
ARGUMENT_PROMPTER_SERVICE_ID,
|
|
50
|
+
) as ArgumentPrompterService;
|
|
51
|
+
try {
|
|
52
|
+
const updated = await service.promptForMissingArguments(parseResult);
|
|
53
|
+
if (updated.invalidArguments.length === 0) {
|
|
54
|
+
return updated;
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if ((error as Error).message === "Interrupted") {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
38
65
|
/**
|
|
39
66
|
* Execute the {@link Command} with arguments contained in the provided {@link ParseResult}.
|
|
40
67
|
*
|
|
@@ -55,13 +82,19 @@ async function executeParsedCommand(
|
|
|
55
82
|
"Executing group command with name: %s",
|
|
56
83
|
parseResult!.groupCommand!.name,
|
|
57
84
|
);
|
|
58
|
-
if (
|
|
85
|
+
if (
|
|
86
|
+
configurationServiceProvider?.keyValueServiceEnabled ||
|
|
87
|
+
configurationServiceProvider?.secretServiceEnabled
|
|
88
|
+
) {
|
|
59
89
|
configurationServiceProvider.setCommandKeyValueScope(
|
|
60
90
|
parseResult!.groupCommand!.name,
|
|
61
91
|
);
|
|
62
92
|
}
|
|
63
93
|
await parseResult.groupCommand.execute(context);
|
|
64
|
-
if (
|
|
94
|
+
if (
|
|
95
|
+
configurationServiceProvider?.keyValueServiceEnabled ||
|
|
96
|
+
configurationServiceProvider?.secretServiceEnabled
|
|
97
|
+
) {
|
|
65
98
|
await configurationServiceProvider.clearKeyValueScope();
|
|
66
99
|
}
|
|
67
100
|
}
|
|
@@ -72,7 +105,10 @@ async function executeParsedCommand(
|
|
|
72
105
|
parseResult!.populatedArgumentValues,
|
|
73
106
|
);
|
|
74
107
|
|
|
75
|
-
if (
|
|
108
|
+
if (
|
|
109
|
+
configurationServiceProvider?.keyValueServiceEnabled ||
|
|
110
|
+
configurationServiceProvider?.secretServiceEnabled
|
|
111
|
+
) {
|
|
76
112
|
configurationServiceProvider.setCommandKeyValueScope(
|
|
77
113
|
parseResult.command.name,
|
|
78
114
|
);
|
|
@@ -88,10 +124,16 @@ async function executeParsedCommand(
|
|
|
88
124
|
parseResult.populatedArgumentValues as ArgumentSingleValueType,
|
|
89
125
|
);
|
|
90
126
|
}
|
|
91
|
-
if (
|
|
127
|
+
if (
|
|
128
|
+
configurationServiceProvider?.keyValueServiceEnabled ||
|
|
129
|
+
configurationServiceProvider?.secretServiceEnabled
|
|
130
|
+
) {
|
|
92
131
|
await configurationServiceProvider.clearKeyValueScope();
|
|
93
132
|
}
|
|
94
133
|
} catch (err) {
|
|
134
|
+
if ((err as Error).message === "Interrupted") {
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
95
137
|
await printCommandExecutionError(
|
|
96
138
|
context,
|
|
97
139
|
parseResult,
|
|
@@ -154,7 +196,7 @@ async function findAndExecuteGlobalModifierCommands(
|
|
|
154
196
|
) {
|
|
155
197
|
// parse and fast fail on error
|
|
156
198
|
const defaultArgumentValues = configurationServiceProvider
|
|
157
|
-
? configurationServiceProvider
|
|
199
|
+
? await configurationServiceProvider
|
|
158
200
|
.getDefaultArgumentValues(
|
|
159
201
|
context.cliConfig,
|
|
160
202
|
globalModifierCommandClause.command,
|
|
@@ -194,7 +236,7 @@ async function findAndExecuteGlobalModifierCommands(
|
|
|
194
236
|
for (const globalModifierCommand of remainingGlobalModifierCommands) {
|
|
195
237
|
// if there is a config entry run this by default even though no arguments were provided on the command line
|
|
196
238
|
const defaultArgumentValues = configurationServiceProvider
|
|
197
|
-
? configurationServiceProvider
|
|
239
|
+
? await configurationServiceProvider
|
|
198
240
|
.getDefaultArgumentValues(context.cliConfig, globalModifierCommand)
|
|
199
241
|
: undefined;
|
|
200
242
|
|
|
@@ -273,7 +315,7 @@ async function findAndExecuteNonModifierCommand(
|
|
|
273
315
|
|
|
274
316
|
if (scanResult.nonModifierCommandClause) {
|
|
275
317
|
const defaultArgumentValues = configurationServiceProvider
|
|
276
|
-
? configurationServiceProvider
|
|
318
|
+
? await configurationServiceProvider
|
|
277
319
|
.getDefaultArgumentValues(
|
|
278
320
|
context.cliConfig,
|
|
279
321
|
scanResult.nonModifierCommandClause.command,
|
|
@@ -293,12 +335,20 @@ async function findAndExecuteNonModifierCommand(
|
|
|
293
335
|
}
|
|
294
336
|
|
|
295
337
|
if (parseResult.invalidArguments.length > 0) {
|
|
296
|
-
await
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
338
|
+
const prompted = await attemptPromptForMissingArguments(
|
|
339
|
+
parseResult,
|
|
340
|
+
context,
|
|
341
|
+
);
|
|
342
|
+
if (prompted) {
|
|
343
|
+
parseResult = prompted;
|
|
344
|
+
} else {
|
|
345
|
+
await printParseResultError(context, parseResult);
|
|
346
|
+
return {
|
|
347
|
+
runState: RunState.PARSE_ERROR,
|
|
348
|
+
command: parseResult.command,
|
|
349
|
+
invalidArguments: parseResult.invalidArguments,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
302
352
|
}
|
|
303
353
|
|
|
304
354
|
// add any unused args back to the list of still unused args
|
|
@@ -310,7 +360,7 @@ async function findAndExecuteNonModifierCommand(
|
|
|
310
360
|
for (const nonModifierCommand of nonModifierCommandsByName.values()) {
|
|
311
361
|
// if there is a config entry run this by default even though no arguments were provided on the command line
|
|
312
362
|
const defaultArgumentValues = configurationServiceProvider
|
|
313
|
-
? configurationServiceProvider
|
|
363
|
+
? await configurationServiceProvider
|
|
314
364
|
.getDefaultArgumentValues(context.cliConfig, nonModifierCommand)
|
|
315
365
|
: undefined;
|
|
316
366
|
|
|
@@ -328,12 +378,20 @@ async function findAndExecuteNonModifierCommand(
|
|
|
328
378
|
}
|
|
329
379
|
|
|
330
380
|
if (parseResult.invalidArguments.length > 0) {
|
|
331
|
-
await
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
381
|
+
const prompted = await attemptPromptForMissingArguments(
|
|
382
|
+
parseResult,
|
|
383
|
+
context,
|
|
384
|
+
);
|
|
385
|
+
if (prompted) {
|
|
386
|
+
parseResult = prompted;
|
|
387
|
+
} else {
|
|
388
|
+
await printParseResultError(context, parseResult);
|
|
389
|
+
return {
|
|
390
|
+
runState: RunState.PARSE_ERROR,
|
|
391
|
+
command: parseResult.command,
|
|
392
|
+
invalidArguments: parseResult.invalidArguments,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
337
395
|
}
|
|
338
396
|
|
|
339
397
|
break;
|
|
@@ -375,7 +433,7 @@ async function findAndExecuteDefaultNonModifierCommand(
|
|
|
375
433
|
context: Context,
|
|
376
434
|
): Promise<RunResult | undefined> {
|
|
377
435
|
const defaultArgumentValues = configurationServiceProvider
|
|
378
|
-
? configurationServiceProvider
|
|
436
|
+
? await configurationServiceProvider
|
|
379
437
|
.getDefaultArgumentValues(context.cliConfig, defaultNonModifierCommand)
|
|
380
438
|
: undefined;
|
|
381
439
|
const isGlobal = isGlobalCommand(defaultNonModifierCommand);
|
|
@@ -447,14 +505,23 @@ async function findAndExecuteDefaultNonModifierCommand(
|
|
|
447
505
|
}
|
|
448
506
|
}
|
|
449
507
|
|
|
450
|
-
// if there were invalid arguments,
|
|
508
|
+
// if there were invalid arguments, attempt prompting before treating as error
|
|
451
509
|
if (lastErroneousParseResult) {
|
|
452
|
-
await
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
510
|
+
const prompted = await attemptPromptForMissingArguments(
|
|
511
|
+
lastErroneousParseResult,
|
|
512
|
+
context,
|
|
513
|
+
);
|
|
514
|
+
if (prompted) {
|
|
515
|
+
parseResult = prompted;
|
|
516
|
+
lastErroneousParseResult = undefined;
|
|
517
|
+
} else {
|
|
518
|
+
await printParseResultError(context, lastErroneousParseResult, true);
|
|
519
|
+
return {
|
|
520
|
+
runState: RunState.PARSE_ERROR,
|
|
521
|
+
command: defaultNonModifierCommand,
|
|
522
|
+
invalidArguments: lastErroneousParseResult.invalidArguments,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
458
525
|
}
|
|
459
526
|
|
|
460
527
|
// give up if still nothing found
|
|
@@ -603,14 +670,20 @@ export async function run(
|
|
|
603
670
|
|
|
604
671
|
logger.debug("Initialising service with ID: %s", serviceProvider.serviceId);
|
|
605
672
|
|
|
606
|
-
if (
|
|
673
|
+
if (
|
|
674
|
+
configurationServiceProvider?.keyValueServiceEnabled ||
|
|
675
|
+
configurationServiceProvider?.secretServiceEnabled
|
|
676
|
+
) {
|
|
607
677
|
configurationServiceProvider.setServiceKeyValueScope(
|
|
608
678
|
serviceProvider.serviceId,
|
|
609
679
|
);
|
|
610
680
|
}
|
|
611
681
|
|
|
612
682
|
await serviceProvider.initService(context);
|
|
613
|
-
if (
|
|
683
|
+
if (
|
|
684
|
+
configurationServiceProvider?.keyValueServiceEnabled ||
|
|
685
|
+
configurationServiceProvider?.secretServiceEnabled
|
|
686
|
+
) {
|
|
614
687
|
await configurationServiceProvider.clearKeyValueScope();
|
|
615
688
|
}
|
|
616
689
|
}
|
|
@@ -690,7 +763,15 @@ export async function run(
|
|
|
690
763
|
}
|
|
691
764
|
|
|
692
765
|
// give up if we haven't successfully parsed a non-modifier Command by now
|
|
693
|
-
|
|
766
|
+
if (availableArgs.length > 0) {
|
|
767
|
+
await printNoCommandRecognisedError(
|
|
768
|
+
context,
|
|
769
|
+
commandRegistry,
|
|
770
|
+
availableArgs.flat(),
|
|
771
|
+
);
|
|
772
|
+
} else {
|
|
773
|
+
await printNoCommandSpecifiedError(context);
|
|
774
|
+
}
|
|
694
775
|
return {
|
|
695
776
|
runState: RunState.NO_COMMAND,
|
|
696
777
|
};
|
package/src/runtime/scanner.ts
CHANGED
|
@@ -67,15 +67,15 @@ function normaliseFirstArgumentIfRequired(
|
|
|
67
67
|
// --<global_[modifier_]command_name>=<value>
|
|
68
68
|
// -<global_[modifier_]command_short_alias>
|
|
69
69
|
// -<global_[modifier_]command_short_alias>=<value>
|
|
70
|
-
if ((firstArg
|
|
70
|
+
if ((firstArg!.length < 2) || (firstArg!.charAt(0) !== "-")) {
|
|
71
71
|
return args;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// looking for:
|
|
75
75
|
// --<global_[modifier_]command_name>
|
|
76
76
|
// --<global_[modifier_]command_name>=<value>
|
|
77
|
-
if ((firstArg
|
|
78
|
-
let potentialGlobalCommandName = firstArg
|
|
77
|
+
if ((firstArg!.length > 2) && (firstArg!.charAt(1) === "-")) {
|
|
78
|
+
let potentialGlobalCommandName = firstArg!.slice(2);
|
|
79
79
|
|
|
80
80
|
// looking for:
|
|
81
81
|
// <global_command_name>=<value>
|
|
@@ -84,7 +84,7 @@ function normaliseFirstArgumentIfRequired(
|
|
|
84
84
|
if (potentialGlobalCommandName.includes("=")) {
|
|
85
85
|
[potentialGlobalCommandName, nextArg] = potentialGlobalCommandName.split(
|
|
86
86
|
/=(.*)/,
|
|
87
|
-
);
|
|
87
|
+
) as [string, string | undefined];
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
if (!globalCommandsByName.has(potentialGlobalCommandName)) {
|
|
@@ -105,14 +105,17 @@ function normaliseFirstArgumentIfRequired(
|
|
|
105
105
|
// -<global_[modifier_]command_short_alias>
|
|
106
106
|
// -<global_[modifier_]command_short_alias>=<value>
|
|
107
107
|
// (by now we know the arg starts with -<char>)
|
|
108
|
-
let potentialGlobalCommandShortAlias = firstArg
|
|
108
|
+
let potentialGlobalCommandShortAlias = firstArg!.slice(1);
|
|
109
109
|
|
|
110
110
|
// looking for:
|
|
111
111
|
// <global_[modifier_]command_short_alias>=<value>
|
|
112
112
|
let nextArg: string | undefined;
|
|
113
113
|
if (potentialGlobalCommandShortAlias.includes("=")) {
|
|
114
114
|
[potentialGlobalCommandShortAlias, nextArg] =
|
|
115
|
-
potentialGlobalCommandShortAlias.split(/=(.*)/)
|
|
115
|
+
potentialGlobalCommandShortAlias.split(/=(.*)/) as [
|
|
116
|
+
string,
|
|
117
|
+
string | undefined,
|
|
118
|
+
];
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
if (!globalCommandsByShortAlias) {
|
|
@@ -169,8 +172,8 @@ function scanForNextCommandClause(
|
|
|
169
172
|
|
|
170
173
|
// looking for:
|
|
171
174
|
// <global_[modifier_]command_name>
|
|
172
|
-
if (currentArg
|
|
173
|
-
const potentialCommandName = currentArg
|
|
175
|
+
if (currentArg!.startsWith("--")) {
|
|
176
|
+
const potentialCommandName = currentArg!.slice(2);
|
|
174
177
|
|
|
175
178
|
// check for Global[Modifier]Command name
|
|
176
179
|
const command = commandsByName.get(potentialCommandName);
|
|
@@ -188,8 +191,8 @@ function scanForNextCommandClause(
|
|
|
188
191
|
};
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
|
-
if (!includeSubCommands || (currentArg[0] === "-")) {
|
|
192
|
-
unusedArgs.push(currentArg);
|
|
194
|
+
if (!includeSubCommands || (currentArg![0] === "-")) {
|
|
195
|
+
unusedArgs.push(currentArg!);
|
|
193
196
|
continue;
|
|
194
197
|
}
|
|
195
198
|
|
|
@@ -202,11 +205,11 @@ function scanForNextCommandClause(
|
|
|
202
205
|
let potentialMemberSubCommandName: string | undefined;
|
|
203
206
|
let potentialMemberCommandNameArgumentUsed = false;
|
|
204
207
|
|
|
205
|
-
if (potentialGroupCommandName
|
|
208
|
+
if (potentialGroupCommandName!.includes(":")) {
|
|
206
209
|
// looking for:
|
|
207
210
|
// <group_command_name>:<member_sub_command_name>
|
|
208
211
|
[potentialGroupCommandName, potentialMemberSubCommandName] =
|
|
209
|
-
potentialGroupCommandName
|
|
212
|
+
potentialGroupCommandName!.split(":") as [string, string | undefined];
|
|
210
213
|
} else if (pendingArgs.length > 0) {
|
|
211
214
|
// looking for:
|
|
212
215
|
// <group_command_name> <member_sub_command_name>
|
|
@@ -250,9 +253,9 @@ function scanForNextCommandClause(
|
|
|
250
253
|
//
|
|
251
254
|
// if we find it is a group command we skip it as we didn't find a member command
|
|
252
255
|
// following it the logic above
|
|
253
|
-
const command = commandsByName.get(currentArg);
|
|
256
|
+
const command = commandsByName.get(currentArg!);
|
|
254
257
|
if (command && !isGroupCommand(command)) {
|
|
255
|
-
logger.debug("Found sub-command name: %s", currentArg);
|
|
258
|
+
logger.debug("Found sub-command name: %s", currentArg!);
|
|
256
259
|
return {
|
|
257
260
|
commandClause: {
|
|
258
261
|
command,
|
|
@@ -263,7 +266,7 @@ function scanForNextCommandClause(
|
|
|
263
266
|
}
|
|
264
267
|
|
|
265
268
|
// if we got here we haven't found a command so add the current arg to unused args
|
|
266
|
-
unusedArgs.push(currentArg);
|
|
269
|
+
unusedArgs.push(currentArg!);
|
|
267
270
|
}
|
|
268
271
|
// if we got here we haven't found a command and we have processed all provided args
|
|
269
272
|
return {
|