@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,610 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ArgumentSingleValueType,
|
|
3
|
+
ArgumentValues,
|
|
4
|
+
ArgumentValueType,
|
|
5
|
+
PopulatedArgumentSingleValueType,
|
|
6
|
+
PopulatedArgumentValues,
|
|
7
|
+
PopulatedArgumentValueType,
|
|
8
|
+
} from "../../api/argument/ArgumentValueTypes.ts";
|
|
9
|
+
import {
|
|
10
|
+
ArgumentValueTypeName,
|
|
11
|
+
ComplexValueTypeName,
|
|
12
|
+
} from "../../api/argument/ArgumentValueTypes.ts";
|
|
13
|
+
import type Positional from "../../api/argument/Positional.ts";
|
|
14
|
+
import type Option from "../../api/argument/Option.ts";
|
|
15
|
+
import type ComplexOption from "../../api/argument/ComplexOption.ts";
|
|
16
|
+
import { isComplexOption } from "../argument/ArgumentTypeGuards.ts";
|
|
17
|
+
import type { InvalidArgument } from "../../api/RunResult.ts";
|
|
18
|
+
import { InvalidArgumentReason } from "../../api/RunResult.ts";
|
|
19
|
+
import type SubCommandArgument from "../../api/argument/SubCommandArgument.ts";
|
|
20
|
+
import type Argument from "../../api/argument/Argument.ts";
|
|
21
|
+
import type GlobalCommand from "../../api/command/GlobalCommand.ts";
|
|
22
|
+
|
|
23
|
+
interface ValidationResult {
|
|
24
|
+
invalidArgument?: InvalidArgument;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface SingleValueValidationResult extends ValidationResult {
|
|
28
|
+
validValue?: ArgumentSingleValueType;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ArrayValueValidationResult extends ValidationResult {
|
|
32
|
+
validValue?: Array<ArgumentSingleValueType | ArgumentValues>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface ObjectValueValidationResult extends ValidationResult {
|
|
36
|
+
validValue?: ArgumentValues;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validates the provided primitive value against the provided {@link Argument}.
|
|
41
|
+
*
|
|
42
|
+
* @param argument the {@link Argument} to validate against.
|
|
43
|
+
* @param value the value (if any) for the {@link Argument}.
|
|
44
|
+
*/
|
|
45
|
+
function validatePrimitiveValue(
|
|
46
|
+
argument: Argument,
|
|
47
|
+
value: PopulatedArgumentSingleValueType,
|
|
48
|
+
): SingleValueValidationResult {
|
|
49
|
+
let convertedValue;
|
|
50
|
+
|
|
51
|
+
if (value === undefined) {
|
|
52
|
+
return {
|
|
53
|
+
invalidArgument: {
|
|
54
|
+
argument,
|
|
55
|
+
reason: InvalidArgumentReason.MISSING_VALUE,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// type check and conversion
|
|
61
|
+
let castValue: string | undefined;
|
|
62
|
+
switch (argument.type) {
|
|
63
|
+
case ArgumentValueTypeName.BOOLEAN:
|
|
64
|
+
if ((value === true) || (value === false)) {
|
|
65
|
+
convertedValue = value;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
if (typeof value === "string") {
|
|
69
|
+
castValue = value as string;
|
|
70
|
+
castValue = castValue.toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
if (castValue !== "true" && castValue !== "false") {
|
|
73
|
+
return {
|
|
74
|
+
invalidArgument: {
|
|
75
|
+
argument,
|
|
76
|
+
value,
|
|
77
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
convertedValue = castValue === "true";
|
|
82
|
+
break;
|
|
83
|
+
case ArgumentValueTypeName.INTEGER:
|
|
84
|
+
if (!Number.isInteger(Number(value))) {
|
|
85
|
+
return {
|
|
86
|
+
invalidArgument: {
|
|
87
|
+
argument,
|
|
88
|
+
value,
|
|
89
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
convertedValue = Number(value);
|
|
94
|
+
break;
|
|
95
|
+
case ArgumentValueTypeName.NUMBER:
|
|
96
|
+
if (!Number.isFinite(Number(value))) {
|
|
97
|
+
return {
|
|
98
|
+
invalidArgument: {
|
|
99
|
+
argument,
|
|
100
|
+
value,
|
|
101
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
convertedValue = Number(value);
|
|
106
|
+
break;
|
|
107
|
+
case ArgumentValueTypeName.STRING:
|
|
108
|
+
default:
|
|
109
|
+
convertedValue = String(value);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// check if the value is valid
|
|
114
|
+
if (argument.allowableValues && !argument.allowableValues.includes(value)) {
|
|
115
|
+
return {
|
|
116
|
+
invalidArgument: {
|
|
117
|
+
argument,
|
|
118
|
+
value,
|
|
119
|
+
reason: InvalidArgumentReason.ILLEGAL_VALUE,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return { validValue: convertedValue };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function validateArrayValue(
|
|
127
|
+
subCommandArgument: SubCommandArgument | ComplexOption,
|
|
128
|
+
arrayValue: Array<
|
|
129
|
+
PopulatedArgumentSingleValueType | PopulatedArgumentValues | undefined
|
|
130
|
+
>,
|
|
131
|
+
): ArrayValueValidationResult {
|
|
132
|
+
const convertedArrayValue: Array<ArgumentSingleValueType | ArgumentValues> =
|
|
133
|
+
[];
|
|
134
|
+
|
|
135
|
+
for (let i = 0; i < arrayValue.length; i += 1) {
|
|
136
|
+
const singleValue = arrayValue[i];
|
|
137
|
+
|
|
138
|
+
if (singleValue === undefined) {
|
|
139
|
+
return {
|
|
140
|
+
validValue: convertedArrayValue,
|
|
141
|
+
invalidArgument: {
|
|
142
|
+
argument: subCommandArgument,
|
|
143
|
+
name: `[${i}]`,
|
|
144
|
+
reason: InvalidArgumentReason.ILLEGAL_SPARSE_ARRAY,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let validationResult:
|
|
150
|
+
| SingleValueValidationResult
|
|
151
|
+
| ObjectValueValidationResult;
|
|
152
|
+
|
|
153
|
+
if (Array.isArray(singleValue)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`Unexpected array value as array member, arrays of arrays are not supported. Argument: ${subCommandArgument.name}[${i}]`,
|
|
156
|
+
);
|
|
157
|
+
} else if (typeof singleValue === "object") {
|
|
158
|
+
if (subCommandArgument.type !== ComplexValueTypeName.COMPLEX) {
|
|
159
|
+
return {
|
|
160
|
+
validValue: convertedArrayValue,
|
|
161
|
+
invalidArgument: {
|
|
162
|
+
argument: subCommandArgument,
|
|
163
|
+
name: `[${i}]`,
|
|
164
|
+
value: arrayValue,
|
|
165
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
validationResult = validateObjectValue(
|
|
170
|
+
subCommandArgument as ComplexOption,
|
|
171
|
+
singleValue,
|
|
172
|
+
);
|
|
173
|
+
} // if not array and not object, then must be primitive
|
|
174
|
+
else {
|
|
175
|
+
if (isComplexOption(subCommandArgument)) {
|
|
176
|
+
return {
|
|
177
|
+
validValue: convertedArrayValue,
|
|
178
|
+
invalidArgument: {
|
|
179
|
+
argument: subCommandArgument,
|
|
180
|
+
name: `[${i}]`,
|
|
181
|
+
value: arrayValue,
|
|
182
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
validationResult = validatePrimitiveValue(
|
|
187
|
+
subCommandArgument as SubCommandArgument,
|
|
188
|
+
singleValue,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (validationResult.validValue !== undefined) {
|
|
193
|
+
convertedArrayValue.push(validationResult.validValue);
|
|
194
|
+
}
|
|
195
|
+
if (validationResult.invalidArgument) {
|
|
196
|
+
// fast fail
|
|
197
|
+
if (validationResult.invalidArgument.value !== undefined) {
|
|
198
|
+
return {
|
|
199
|
+
validValue: convertedArrayValue,
|
|
200
|
+
invalidArgument: {
|
|
201
|
+
argument: validationResult.invalidArgument.argument,
|
|
202
|
+
name: `[${i}]${
|
|
203
|
+
validationResult.invalidArgument.name
|
|
204
|
+
? validationResult.invalidArgument.name
|
|
205
|
+
: ""
|
|
206
|
+
}`,
|
|
207
|
+
value: validationResult.invalidArgument.value,
|
|
208
|
+
reason: validationResult.invalidArgument.reason,
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
} else {
|
|
212
|
+
return {
|
|
213
|
+
validValue: convertedArrayValue,
|
|
214
|
+
invalidArgument: {
|
|
215
|
+
argument: validationResult.invalidArgument.argument,
|
|
216
|
+
name: `[${i}]${
|
|
217
|
+
validationResult.invalidArgument.name
|
|
218
|
+
? validationResult.invalidArgument.name
|
|
219
|
+
: ""
|
|
220
|
+
}`,
|
|
221
|
+
reason: validationResult.invalidArgument.reason,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
validValue: convertedArrayValue,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function validateObjectValue(
|
|
233
|
+
argument: ComplexOption,
|
|
234
|
+
objectValue: PopulatedArgumentValues,
|
|
235
|
+
): ObjectValueValidationResult {
|
|
236
|
+
const convertedObjectValue: ArgumentValues = {};
|
|
237
|
+
|
|
238
|
+
for (let i = 0; i < argument.properties.length; i++) {
|
|
239
|
+
const propertyArg = argument.properties[i];
|
|
240
|
+
const propertyValue = objectValue[propertyArg.name];
|
|
241
|
+
|
|
242
|
+
if (propertyValue === undefined) {
|
|
243
|
+
return {
|
|
244
|
+
validValue: convertedObjectValue,
|
|
245
|
+
invalidArgument: {
|
|
246
|
+
argument: propertyArg,
|
|
247
|
+
name: `.${propertyArg.name}`,
|
|
248
|
+
reason: InvalidArgumentReason.MISSING_VALUE,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
let validationResult:
|
|
254
|
+
| SingleValueValidationResult
|
|
255
|
+
| ObjectValueValidationResult
|
|
256
|
+
| ArrayValueValidationResult;
|
|
257
|
+
|
|
258
|
+
if (Array.isArray(propertyValue)) {
|
|
259
|
+
if (!propertyArg.isArray) {
|
|
260
|
+
return {
|
|
261
|
+
validValue: convertedObjectValue,
|
|
262
|
+
invalidArgument: {
|
|
263
|
+
argument: propertyArg,
|
|
264
|
+
name: `.${propertyArg.name}`,
|
|
265
|
+
value: propertyValue,
|
|
266
|
+
reason: InvalidArgumentReason.ILLEGAL_MULTIPLE_VALUES,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
validationResult = validateArrayValue(propertyArg, propertyValue);
|
|
271
|
+
} else if (typeof propertyValue === "object") {
|
|
272
|
+
if (propertyArg.type !== ComplexValueTypeName.COMPLEX) {
|
|
273
|
+
return {
|
|
274
|
+
validValue: convertedObjectValue,
|
|
275
|
+
invalidArgument: {
|
|
276
|
+
argument: propertyArg,
|
|
277
|
+
name: `.${propertyArg.name}`,
|
|
278
|
+
value: propertyValue,
|
|
279
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
validationResult = validateObjectValue(
|
|
284
|
+
propertyArg as ComplexOption,
|
|
285
|
+
propertyValue as PopulatedArgumentValues,
|
|
286
|
+
);
|
|
287
|
+
} // if not array and not object, then must be primitive
|
|
288
|
+
else {
|
|
289
|
+
if (isComplexOption(propertyArg)) {
|
|
290
|
+
return {
|
|
291
|
+
validValue: convertedObjectValue,
|
|
292
|
+
invalidArgument: {
|
|
293
|
+
argument: propertyArg,
|
|
294
|
+
name: `.${propertyArg.name}`,
|
|
295
|
+
value: propertyValue,
|
|
296
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
validationResult = validatePrimitiveValue(
|
|
301
|
+
propertyArg as SubCommandArgument,
|
|
302
|
+
propertyValue,
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
if (validationResult.validValue !== undefined) {
|
|
306
|
+
convertedObjectValue[propertyArg.name] = validationResult.validValue as
|
|
307
|
+
| ArgumentValueType
|
|
308
|
+
| ArgumentValues
|
|
309
|
+
| Array<ArgumentValues>;
|
|
310
|
+
}
|
|
311
|
+
// fast fail
|
|
312
|
+
if (validationResult.invalidArgument !== undefined) {
|
|
313
|
+
if (validationResult.invalidArgument.value !== undefined) {
|
|
314
|
+
return {
|
|
315
|
+
validValue: convertedObjectValue,
|
|
316
|
+
invalidArgument: {
|
|
317
|
+
argument: validationResult.invalidArgument.argument,
|
|
318
|
+
name: `.${propertyArg.name}${
|
|
319
|
+
validationResult.invalidArgument.name
|
|
320
|
+
? validationResult.invalidArgument.name
|
|
321
|
+
: ""
|
|
322
|
+
}`,
|
|
323
|
+
value: validationResult.invalidArgument.value,
|
|
324
|
+
reason: validationResult.invalidArgument.reason,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
} else {
|
|
328
|
+
return {
|
|
329
|
+
validValue: convertedObjectValue,
|
|
330
|
+
invalidArgument: {
|
|
331
|
+
argument: validationResult.invalidArgument.argument,
|
|
332
|
+
name: `.${propertyArg.name}${
|
|
333
|
+
validationResult.invalidArgument.name
|
|
334
|
+
? validationResult.invalidArgument.name
|
|
335
|
+
: ""
|
|
336
|
+
}`,
|
|
337
|
+
reason: validationResult.invalidArgument.reason,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
validValue: convertedObjectValue,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function doSubCommandArgumentValidation(
|
|
349
|
+
argument: SubCommandArgument | ComplexOption,
|
|
350
|
+
value:
|
|
351
|
+
| PopulatedArgumentValueType
|
|
352
|
+
| PopulatedArgumentValues
|
|
353
|
+
| Array<PopulatedArgumentValues | undefined>,
|
|
354
|
+
isArray: boolean,
|
|
355
|
+
isOptional: boolean,
|
|
356
|
+
invalidArguments: Array<InvalidArgument>,
|
|
357
|
+
):
|
|
358
|
+
| PopulatedArgumentValueType
|
|
359
|
+
| PopulatedArgumentValues
|
|
360
|
+
| Array<PopulatedArgumentValues> {
|
|
361
|
+
// if there is a value, check if it is valid
|
|
362
|
+
if (value !== undefined) {
|
|
363
|
+
let validationResult;
|
|
364
|
+
|
|
365
|
+
if (Array.isArray(value)) {
|
|
366
|
+
if (!isArray) {
|
|
367
|
+
invalidArguments.push({
|
|
368
|
+
argument,
|
|
369
|
+
name: argument.name,
|
|
370
|
+
value,
|
|
371
|
+
reason: InvalidArgumentReason.ILLEGAL_MULTIPLE_VALUES,
|
|
372
|
+
});
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
validationResult = validateArrayValue(argument, value);
|
|
376
|
+
} else if (typeof value === "object") {
|
|
377
|
+
if (!isComplexOption(argument)) {
|
|
378
|
+
invalidArguments.push({
|
|
379
|
+
argument,
|
|
380
|
+
name: argument.name,
|
|
381
|
+
value,
|
|
382
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
383
|
+
});
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
validationResult = validateObjectValue(argument, value);
|
|
387
|
+
} else {
|
|
388
|
+
if (isComplexOption(argument)) {
|
|
389
|
+
invalidArguments.push({
|
|
390
|
+
argument,
|
|
391
|
+
name: argument.name,
|
|
392
|
+
value,
|
|
393
|
+
reason: InvalidArgumentReason.INCORRECT_VALUE_TYPE,
|
|
394
|
+
});
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
validationResult = validatePrimitiveValue(
|
|
398
|
+
argument as SubCommandArgument,
|
|
399
|
+
value,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (validationResult.invalidArgument !== undefined) {
|
|
404
|
+
if (validationResult.invalidArgument.value !== undefined) {
|
|
405
|
+
validationResult.invalidArgument = {
|
|
406
|
+
argument: validationResult.invalidArgument.argument,
|
|
407
|
+
name: `${argument.name}${
|
|
408
|
+
validationResult.invalidArgument.name
|
|
409
|
+
? validationResult.invalidArgument.name
|
|
410
|
+
: ""
|
|
411
|
+
}`,
|
|
412
|
+
value: validationResult.invalidArgument.value,
|
|
413
|
+
reason: validationResult.invalidArgument.reason,
|
|
414
|
+
};
|
|
415
|
+
} else {
|
|
416
|
+
validationResult.invalidArgument = {
|
|
417
|
+
argument: validationResult.invalidArgument.argument,
|
|
418
|
+
name: `${argument.name}${
|
|
419
|
+
validationResult.invalidArgument.name
|
|
420
|
+
? validationResult.invalidArgument.name
|
|
421
|
+
: ""
|
|
422
|
+
}`,
|
|
423
|
+
reason: validationResult.invalidArgument.reason,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (validationResult.invalidArgument) {
|
|
429
|
+
invalidArguments.push(validationResult.invalidArgument);
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
return validationResult.validValue as
|
|
433
|
+
| PopulatedArgumentValueType
|
|
434
|
+
| PopulatedArgumentValues
|
|
435
|
+
| undefined;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// if there is no value, check if it was optional
|
|
439
|
+
if (!isOptional) {
|
|
440
|
+
invalidArguments.push({
|
|
441
|
+
argument: argument,
|
|
442
|
+
name: argument.name,
|
|
443
|
+
reason: InvalidArgumentReason.MISSING_VALUE,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Validates the provided value against the provided {@link Option}.
|
|
451
|
+
*
|
|
452
|
+
* @param option the {@link Option} to validate against.
|
|
453
|
+
* @param value the value (if any) for the {@link Option}.
|
|
454
|
+
* @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
|
|
455
|
+
*/
|
|
456
|
+
export function validateOptionValue(
|
|
457
|
+
option: Option | ComplexOption,
|
|
458
|
+
value:
|
|
459
|
+
| PopulatedArgumentValueType
|
|
460
|
+
| PopulatedArgumentValues
|
|
461
|
+
| Array<PopulatedArgumentValues | undefined>,
|
|
462
|
+
invalidArguments: Array<InvalidArgument>,
|
|
463
|
+
):
|
|
464
|
+
| PopulatedArgumentValueType
|
|
465
|
+
| PopulatedArgumentValues
|
|
466
|
+
| Array<PopulatedArgumentValues> {
|
|
467
|
+
return doSubCommandArgumentValidation(
|
|
468
|
+
option,
|
|
469
|
+
value,
|
|
470
|
+
option.isArray || false,
|
|
471
|
+
option.isOptional || false,
|
|
472
|
+
invalidArguments,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Validates the provided value against the provided {@link Positional}.
|
|
478
|
+
*
|
|
479
|
+
* @param positional the {@link Positional} to validate against.
|
|
480
|
+
* @param value the value (if any) for the {@link Positional}.
|
|
481
|
+
* @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
|
|
482
|
+
*/
|
|
483
|
+
export function validatePositionalValue(
|
|
484
|
+
positional: Positional,
|
|
485
|
+
value: PopulatedArgumentValueType,
|
|
486
|
+
invalidArguments: Array<InvalidArgument>,
|
|
487
|
+
): PopulatedArgumentValueType {
|
|
488
|
+
return doSubCommandArgumentValidation(
|
|
489
|
+
positional,
|
|
490
|
+
value,
|
|
491
|
+
positional.isVarargMultiple || false,
|
|
492
|
+
positional.isVarargOptional || false,
|
|
493
|
+
invalidArguments,
|
|
494
|
+
) as PopulatedArgumentValueType;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Validates the provided value against the {@link GlobalCommandArgument} provided by the {@link GlobalCommand} and returns the validated
|
|
499
|
+
* value or undefined if the value was invalid.
|
|
500
|
+
*
|
|
501
|
+
* @param globalCommand the {@link GlobalCommand} providing the {@link GlobalCommandArgument}.
|
|
502
|
+
* @param value the value (if any) for the {@link GlobalCommandArgument}.
|
|
503
|
+
* @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
|
|
504
|
+
*/
|
|
505
|
+
export function validateGlobalCommandArgumentValue(
|
|
506
|
+
globalCommand: GlobalCommand,
|
|
507
|
+
value: PopulatedArgumentSingleValueType,
|
|
508
|
+
invalidArguments: Array<InvalidArgument>,
|
|
509
|
+
): PopulatedArgumentSingleValueType {
|
|
510
|
+
// if this function is called it is because the argument is defined
|
|
511
|
+
const globalCommandArgument = globalCommand.argument!;
|
|
512
|
+
|
|
513
|
+
// if there is a value, check if it is valid
|
|
514
|
+
if (value !== undefined) {
|
|
515
|
+
const validationResult = validatePrimitiveValue(
|
|
516
|
+
globalCommandArgument,
|
|
517
|
+
value,
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
if (validationResult.invalidArgument) {
|
|
521
|
+
if (validationResult.invalidArgument.value !== undefined) {
|
|
522
|
+
invalidArguments.push({
|
|
523
|
+
argument: globalCommandArgument,
|
|
524
|
+
name: globalCommand.name,
|
|
525
|
+
value: validationResult.invalidArgument.value,
|
|
526
|
+
reason: validationResult.invalidArgument.reason,
|
|
527
|
+
});
|
|
528
|
+
} else {
|
|
529
|
+
invalidArguments.push({
|
|
530
|
+
argument: globalCommandArgument,
|
|
531
|
+
name: globalCommand.name,
|
|
532
|
+
reason: validationResult.invalidArgument.reason,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return undefined;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return validationResult.validValue as PopulatedArgumentSingleValueType;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// if there is no value, check if it was optional
|
|
542
|
+
if (!globalCommandArgument.isOptional) {
|
|
543
|
+
invalidArguments.push({
|
|
544
|
+
argument: globalCommandArgument,
|
|
545
|
+
name: globalCommand.name,
|
|
546
|
+
reason: InvalidArgumentReason.MISSING_VALUE,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function getInvalidArgumentString(
|
|
553
|
+
invalidArgument: InvalidArgument,
|
|
554
|
+
skipArgName: boolean,
|
|
555
|
+
): string {
|
|
556
|
+
let nameString = "";
|
|
557
|
+
if (!skipArgName && (invalidArgument.name !== undefined)) {
|
|
558
|
+
nameString = invalidArgument.name;
|
|
559
|
+
}
|
|
560
|
+
let valueString = "";
|
|
561
|
+
if (invalidArgument.value !== undefined) {
|
|
562
|
+
valueString = `'${invalidArgument.value}'`;
|
|
563
|
+
}
|
|
564
|
+
let argString = "";
|
|
565
|
+
if (nameString !== "") {
|
|
566
|
+
if (valueString !== "") {
|
|
567
|
+
argString = `${nameString}=${valueString} `;
|
|
568
|
+
} else {
|
|
569
|
+
argString = `${nameString} `;
|
|
570
|
+
}
|
|
571
|
+
} else if (valueString !== "") {
|
|
572
|
+
argString = `${valueString} `;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
let invalidString;
|
|
576
|
+
|
|
577
|
+
switch (invalidArgument.reason) {
|
|
578
|
+
case InvalidArgumentReason.MISSING_VALUE:
|
|
579
|
+
invalidString = "(missing value)";
|
|
580
|
+
break;
|
|
581
|
+
case InvalidArgumentReason.INCORRECT_VALUE_TYPE:
|
|
582
|
+
invalidString = "(incorrect type)";
|
|
583
|
+
break;
|
|
584
|
+
case InvalidArgumentReason.ILLEGAL_MULTIPLE_VALUES:
|
|
585
|
+
invalidString = "(illegal multiple values)";
|
|
586
|
+
break;
|
|
587
|
+
case InvalidArgumentReason.ILLEGAL_VALUE:
|
|
588
|
+
invalidString = "(illegal value)";
|
|
589
|
+
break;
|
|
590
|
+
case InvalidArgumentReason.ILLEGAL_SPARSE_ARRAY:
|
|
591
|
+
invalidString = "(sparse array values)";
|
|
592
|
+
break;
|
|
593
|
+
case InvalidArgumentReason.UNKNOWN_PROPERTY:
|
|
594
|
+
invalidString = "(unknown property)";
|
|
595
|
+
break;
|
|
596
|
+
case InvalidArgumentReason.NESTING_DEPTH_EXCEEDED:
|
|
597
|
+
invalidString = "(nesting depth exceeded)";
|
|
598
|
+
break;
|
|
599
|
+
case InvalidArgumentReason.ARRAY_SIZE_EXCEEDED:
|
|
600
|
+
invalidString = "(array size exceeded)";
|
|
601
|
+
break;
|
|
602
|
+
case InvalidArgumentReason.OPTION_IS_COMPLEX:
|
|
603
|
+
invalidString = "(specified option is complex)";
|
|
604
|
+
break;
|
|
605
|
+
default:
|
|
606
|
+
invalidString = "";
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
return `${argString}${invalidString}`;
|
|
610
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type GlobalCommand from "../../api/command/GlobalCommand.ts";
|
|
2
|
+
import type { GlobalCommandValuePopulationResult } from "./ValuePopulationResult.ts";
|
|
3
|
+
import {
|
|
4
|
+
ArgumentValueTypeName,
|
|
5
|
+
} from "../../api/argument/ArgumentValueTypes.ts";
|
|
6
|
+
import type {
|
|
7
|
+
PopulatedArgumentSingleValueType,
|
|
8
|
+
} from "../../api/argument/ArgumentValueTypes.ts";
|
|
9
|
+
import getLogger from "../../util/logger.ts";
|
|
10
|
+
import { InvalidArgumentReason } from "../../api/RunResult.ts";
|
|
11
|
+
|
|
12
|
+
const logger = getLogger("globalCommandValuePopulation");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Attempt to populate a {@link ArgumentValueType} for the provided {@link GlobalCommand} using the provided potential args.
|
|
16
|
+
*
|
|
17
|
+
* @param globalCommand the {@link GlobalCommand} for which {@link ArgumentValues} values should be populated.
|
|
18
|
+
* @param potentialArgs the potential args to use for population.
|
|
19
|
+
* @param defaultValue optional default value to use for population before parsing the provided arguments.
|
|
20
|
+
*/
|
|
21
|
+
export default function populateGlobalCommandValue(
|
|
22
|
+
globalCommand: GlobalCommand,
|
|
23
|
+
potentialArgs: ReadonlyArray<string>,
|
|
24
|
+
defaultValue: PopulatedArgumentSingleValueType,
|
|
25
|
+
): GlobalCommandValuePopulationResult {
|
|
26
|
+
logger.debug(() => {
|
|
27
|
+
const message =
|
|
28
|
+
`Populating value for global command: '${globalCommand.name}' using potential args: ${
|
|
29
|
+
potentialArgs.join(" ")
|
|
30
|
+
}`;
|
|
31
|
+
if (defaultValue !== undefined) {
|
|
32
|
+
return `${message} and configured value: '${defaultValue}'`;
|
|
33
|
+
}
|
|
34
|
+
return message;
|
|
35
|
+
});
|
|
36
|
+
const argument = globalCommand.argument!;
|
|
37
|
+
|
|
38
|
+
// default to defined argument default value
|
|
39
|
+
let populatedArgumentValue = argument.defaultValue;
|
|
40
|
+
|
|
41
|
+
// override with provided configured value
|
|
42
|
+
if (defaultValue !== undefined) {
|
|
43
|
+
populatedArgumentValue = defaultValue;
|
|
44
|
+
}
|
|
45
|
+
let unusedArgs: Array<string> = [];
|
|
46
|
+
|
|
47
|
+
if (potentialArgs.length > 0) {
|
|
48
|
+
const firstPotentialArg = potentialArgs[0];
|
|
49
|
+
|
|
50
|
+
// don't use potentialArg if it cannot be a boolean value...
|
|
51
|
+
if (argument.type === ArgumentValueTypeName.BOOLEAN) {
|
|
52
|
+
const firstPotentialArgLower = firstPotentialArg.toLowerCase();
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
(firstPotentialArgLower !== "true") &&
|
|
56
|
+
(firstPotentialArgLower !== "false")
|
|
57
|
+
) {
|
|
58
|
+
// ...instead, use the fact that the argument is present as the value true
|
|
59
|
+
return {
|
|
60
|
+
populatedArgumentValue: "true",
|
|
61
|
+
unusedArgs: potentialArgs,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// otherwise override with parsed argument value
|
|
66
|
+
populatedArgumentValue = firstPotentialArg;
|
|
67
|
+
unusedArgs = potentialArgs.slice(1);
|
|
68
|
+
} // check if argument type is boolean and therefore command being specified is an implicit value of true
|
|
69
|
+
else if (
|
|
70
|
+
(argument.type === ArgumentValueTypeName.BOOLEAN) &&
|
|
71
|
+
((populatedArgumentValue === undefined) ||
|
|
72
|
+
(argument.defaultValue === false))
|
|
73
|
+
) {
|
|
74
|
+
return {
|
|
75
|
+
populatedArgumentValue: "true",
|
|
76
|
+
unusedArgs,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// check if there is no value and the global command argument is not optional
|
|
81
|
+
if ((populatedArgumentValue === undefined) && !argument.isOptional) {
|
|
82
|
+
return {
|
|
83
|
+
populatedArgumentValue,
|
|
84
|
+
unusedArgs,
|
|
85
|
+
invalidArgument: {
|
|
86
|
+
name: globalCommand.name,
|
|
87
|
+
reason: InvalidArgumentReason.MISSING_VALUE,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
populatedArgumentValue,
|
|
94
|
+
unusedArgs,
|
|
95
|
+
};
|
|
96
|
+
}
|