@flowscripter/dynamic-cli-framework 11.0.0 → 12.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/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +10 -3
- package/dist/src/runtime/DefaultContext.d.ts +2 -1
- package/dist/src/runtime/DefaultContext.d.ts.map +1 -1
- package/dist/src/runtime/DefaultContext.js +3 -1
- package/dist/src/runtime/command/CommandValidator.d.ts.map +1 -1
- package/dist/src/runtime/command/CommandValidator.js +18 -6
- package/dist/src/runtime/parser.d.ts.map +1 -1
- package/dist/src/runtime/parser.js +13 -4
- package/dist/src/runtime/values/argumentValueValidation.d.ts +26 -4
- package/dist/src/runtime/values/argumentValueValidation.d.ts.map +1 -1
- package/dist/src/runtime/values/argumentValueValidation.js +53 -50
- package/dist/src/service/argumentPrompter/DefaultArgumentPrompterService.d.ts +4 -0
- package/dist/src/service/argumentPrompter/DefaultArgumentPrompterService.d.ts.map +1 -1
- package/dist/src/service/argumentPrompter/DefaultArgumentPrompterService.js +53 -21
- package/dist/src/service/configuration/KeyValueServiceProvider.d.ts.map +1 -1
- package/dist/src/service/configuration/KeyValueServiceProvider.js +1 -0
- package/dist/src/service/printer/PrinterServiceProvider.d.ts +3 -1
- package/dist/src/service/printer/PrinterServiceProvider.d.ts.map +1 -1
- package/dist/src/service/printer/PrinterServiceProvider.js +7 -4
- package/dist/src/service/printer/command/ThemeCommand.d.ts +24 -0
- package/dist/src/service/printer/command/ThemeCommand.d.ts.map +1 -0
- package/dist/src/service/printer/command/ThemeCommand.js +40 -0
- package/dist/src/terminal/Ansi.d.ts +4 -0
- package/dist/src/terminal/Ansi.d.ts.map +1 -1
- package/dist/src/terminal/Ansi.js +7 -3
- package/dist/src/terminal/BackgroundDetector.d.ts +25 -0
- package/dist/src/terminal/BackgroundDetector.d.ts.map +1 -0
- package/dist/src/terminal/BackgroundDetector.js +63 -0
- package/dist/src/terminal/KeyReader.d.ts +7 -0
- package/dist/src/terminal/KeyReader.d.ts.map +1 -1
- package/dist/src/terminal/TtyKeyReader.d.ts +1 -0
- package/dist/src/terminal/TtyKeyReader.d.ts.map +1 -1
- package/dist/src/terminal/TtyKeyReader.js +30 -0
- package/package.json +2 -2
- package/src/cli/BaseCLI.ts +15 -4
- package/src/runtime/DefaultContext.ts +4 -1
- package/src/runtime/command/CommandValidator.ts +35 -5
- package/src/runtime/parser.ts +14 -0
- package/src/runtime/values/argumentValueValidation.ts +82 -50
- package/src/service/argumentPrompter/DefaultArgumentPrompterService.ts +92 -26
- package/src/service/configuration/KeyValueServiceProvider.ts +1 -0
- package/src/service/printer/PrinterServiceProvider.ts +11 -4
- package/src/service/printer/command/ThemeCommand.ts +47 -0
- package/src/terminal/Ansi.ts +7 -3
- package/src/terminal/BackgroundDetector.ts +78 -0
- package/src/terminal/KeyReader.ts +7 -0
- package/src/terminal/TtyKeyReader.ts +35 -0
- package/dist/src/service/printer/command/DarkModeCommand.d.ts +0 -17
- package/dist/src/service/printer/command/DarkModeCommand.d.ts.map +0 -1
- package/dist/src/service/printer/command/DarkModeCommand.js +0 -24
- package/src/service/printer/command/DarkModeCommand.ts +0 -32
|
@@ -4,7 +4,7 @@ import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
4
4
|
import type { Argument } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
5
|
import type { GroupCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
6
|
import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
-
import type { SingleValueType } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
+
import type { PopulatedValues, SingleValueType } from "@flowscripter/dynamic-cli-framework-api";
|
|
8
8
|
import { ValueTypeName, ComplexValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
9
9
|
import type { Option } from "@flowscripter/dynamic-cli-framework-api";
|
|
10
10
|
import type { ComplexOption } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
getInvalidArgumentString,
|
|
14
14
|
validateGlobalCommandArgumentValue,
|
|
15
15
|
validateOptionValue,
|
|
16
|
+
runCustomValidation,
|
|
17
|
+
type DeferredCustomValidation,
|
|
16
18
|
} from "../values/argumentValueValidation.ts";
|
|
17
19
|
import getLogger from "../../util/logger.ts";
|
|
18
20
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -219,7 +221,16 @@ export default class CommandValidator {
|
|
|
219
221
|
validateArgument(argument, globalCommand.name);
|
|
220
222
|
if (argument.defaultValue) {
|
|
221
223
|
const invalidArguments: Array<InvalidArgument> = [];
|
|
222
|
-
|
|
224
|
+
const deferred: Array<DeferredCustomValidation> = [];
|
|
225
|
+
validateGlobalCommandArgumentValue(
|
|
226
|
+
globalCommand,
|
|
227
|
+
argument.defaultValue,
|
|
228
|
+
invalidArguments,
|
|
229
|
+
deferred,
|
|
230
|
+
);
|
|
231
|
+
if (invalidArguments.length === 0) {
|
|
232
|
+
runCustomValidation(deferred, globalCommand, argument.defaultValue, invalidArguments);
|
|
233
|
+
}
|
|
223
234
|
if (invalidArguments.length > 0) {
|
|
224
235
|
throw new Error(
|
|
225
236
|
`Illegal default value for global command: '${globalCommand.name}' => ${invalidArguments
|
|
@@ -440,12 +451,13 @@ export default class CommandValidator {
|
|
|
440
451
|
option as unknown as SubCommandArgument,
|
|
441
452
|
]);
|
|
442
453
|
} else {
|
|
443
|
-
this.#validateOption(option, currentOptionPaths, allOptionPaths);
|
|
454
|
+
this.#validateOption(subCommand, option, currentOptionPaths, allOptionPaths);
|
|
444
455
|
this.#validateSubCommandArgumentConfigurationKey(subCommand, [...argumentAncestry, option]);
|
|
445
456
|
}
|
|
446
457
|
}
|
|
447
458
|
|
|
448
459
|
#validateOption(
|
|
460
|
+
subCommand: SubCommand,
|
|
449
461
|
option: Option,
|
|
450
462
|
currentOptionPaths: Array<string>,
|
|
451
463
|
allOptionPaths: Array<string>,
|
|
@@ -460,7 +472,16 @@ export default class CommandValidator {
|
|
|
460
472
|
}
|
|
461
473
|
if (option.defaultValue) {
|
|
462
474
|
const invalidArguments: Array<InvalidArgument> = [];
|
|
463
|
-
|
|
475
|
+
const deferred: Array<DeferredCustomValidation> = [];
|
|
476
|
+
validateOptionValue(option, option.defaultValue, invalidArguments, deferred);
|
|
477
|
+
if (invalidArguments.length === 0) {
|
|
478
|
+
runCustomValidation(
|
|
479
|
+
deferred,
|
|
480
|
+
subCommand,
|
|
481
|
+
{ [option.name]: option.defaultValue } as PopulatedValues,
|
|
482
|
+
invalidArguments,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
464
485
|
if (invalidArguments.length > 0) {
|
|
465
486
|
throw new Error(
|
|
466
487
|
`Illegal default value for option: '${option.name}' => ${invalidArguments
|
|
@@ -515,7 +536,16 @@ export default class CommandValidator {
|
|
|
515
536
|
}
|
|
516
537
|
if (complexOption.defaultValue) {
|
|
517
538
|
const invalidArguments: Array<InvalidArgument> = [];
|
|
518
|
-
|
|
539
|
+
const deferred: Array<DeferredCustomValidation> = [];
|
|
540
|
+
validateOptionValue(complexOption, complexOption.defaultValue, invalidArguments, deferred);
|
|
541
|
+
if (invalidArguments.length === 0) {
|
|
542
|
+
runCustomValidation(
|
|
543
|
+
deferred,
|
|
544
|
+
subCommand,
|
|
545
|
+
{ [complexOption.name]: complexOption.defaultValue } as PopulatedValues,
|
|
546
|
+
invalidArguments,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
519
549
|
if (invalidArguments.length > 0) {
|
|
520
550
|
throw new Error(
|
|
521
551
|
`Illegal default value for option: '${complexOption.name}' => ${invalidArguments
|
package/src/runtime/parser.ts
CHANGED
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
validateGlobalCommandArgumentValue,
|
|
12
12
|
validateOptionValue,
|
|
13
13
|
validatePositionalValue,
|
|
14
|
+
runCustomValidation,
|
|
15
|
+
type DeferredCustomValidation,
|
|
14
16
|
} from "./values/argumentValueValidation.ts";
|
|
15
17
|
import getLogger from "../util/logger.ts";
|
|
16
18
|
import { redactGlobalCommandValue, redactSubCommandValues } from "../util/secretRedaction.ts";
|
|
@@ -64,12 +66,14 @@ export function parseSubCommandClause(
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
if (invalidArguments.length === 0) {
|
|
69
|
+
const deferred: Array<DeferredCustomValidation> = [];
|
|
67
70
|
// validate the option values
|
|
68
71
|
command.options.forEach((option) => {
|
|
69
72
|
const validatedValue = validateOptionValue(
|
|
70
73
|
option,
|
|
71
74
|
populatedArgumentValues[option.name] || option.defaultValue,
|
|
72
75
|
invalidArguments,
|
|
76
|
+
deferred,
|
|
73
77
|
);
|
|
74
78
|
if (validatedValue !== undefined) {
|
|
75
79
|
populatedArgumentValues[option.name] = validatedValue;
|
|
@@ -89,11 +93,16 @@ export function parseSubCommandClause(
|
|
|
89
93
|
positional,
|
|
90
94
|
populatedArgumentValues[positional.name] as ValueType,
|
|
91
95
|
invalidArguments,
|
|
96
|
+
deferred,
|
|
92
97
|
);
|
|
93
98
|
if (validatedValue !== undefined) {
|
|
94
99
|
populatedArgumentValues[positional.name] = validatedValue;
|
|
95
100
|
}
|
|
96
101
|
});
|
|
102
|
+
if (invalidArguments.length === 0) {
|
|
103
|
+
// custom validators run once all built-in validation has passed and see all values
|
|
104
|
+
runCustomValidation(deferred, command, populatedArgumentValues, invalidArguments);
|
|
105
|
+
}
|
|
97
106
|
logger.debug(
|
|
98
107
|
"Command arguments after positionals validated: %O with invalid args: %O",
|
|
99
108
|
redactSubCommandValues(command, populatedArgumentValues),
|
|
@@ -162,14 +171,19 @@ export function parseGlobalCommandClause(
|
|
|
162
171
|
|
|
163
172
|
// don't validate the value if the argument is already invalid
|
|
164
173
|
if (invalidArguments.length === 0) {
|
|
174
|
+
const deferred: Array<DeferredCustomValidation> = [];
|
|
165
175
|
const validatedValue = validateGlobalCommandArgumentValue(
|
|
166
176
|
command,
|
|
167
177
|
populatedArgumentValue,
|
|
168
178
|
invalidArguments,
|
|
179
|
+
deferred,
|
|
169
180
|
);
|
|
170
181
|
if (validatedValue !== undefined) {
|
|
171
182
|
populatedArgumentValue = validatedValue;
|
|
172
183
|
}
|
|
184
|
+
if (invalidArguments.length === 0) {
|
|
185
|
+
runCustomValidation(deferred, command, populatedArgumentValue, invalidArguments);
|
|
186
|
+
}
|
|
173
187
|
}
|
|
174
188
|
|
|
175
189
|
logger.debug(
|
|
@@ -16,6 +16,7 @@ import { InvalidArgumentReason } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
16
16
|
import type { SubCommandArgument } from "@flowscripter/dynamic-cli-framework-api";
|
|
17
17
|
import type { Argument } from "@flowscripter/dynamic-cli-framework-api";
|
|
18
18
|
import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
19
|
+
import type { Command } from "@flowscripter/dynamic-cli-framework-api";
|
|
19
20
|
|
|
20
21
|
interface ValidationResult {
|
|
21
22
|
invalidArgument?: InvalidArgument;
|
|
@@ -41,32 +42,61 @@ function withPrefix(invalidArgument: InvalidArgument, prefix: string): InvalidAr
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
*
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
* A custom validation to be invoked once all built-in validation has passed.
|
|
46
|
+
*/
|
|
47
|
+
export interface DeferredCustomValidation {
|
|
48
|
+
readonly argument: Argument | ComplexOption;
|
|
49
|
+
readonly path: string;
|
|
50
|
+
readonly value: SingleValueType | Values | Array<SingleValueType | Values>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Records a custom validation of the provided {@link Argument} (if it has one) for later invocation by
|
|
55
|
+
* {@link runCustomValidation}. Deferred validations are recorded bottom-up and depth-first.
|
|
50
56
|
*/
|
|
51
|
-
function
|
|
57
|
+
function deferCustomValidation(
|
|
58
|
+
deferred: Array<DeferredCustomValidation> | undefined,
|
|
52
59
|
argument: Argument | ComplexOption,
|
|
53
|
-
|
|
60
|
+
path: string,
|
|
54
61
|
value: SingleValueType | Values | Array<SingleValueType | Values>,
|
|
55
|
-
):
|
|
56
|
-
if (argument.validate
|
|
57
|
-
|
|
62
|
+
): void {
|
|
63
|
+
if (deferred !== undefined && argument.validate !== undefined) {
|
|
64
|
+
deferred.push({ argument, path, value });
|
|
58
65
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Invokes the recorded custom validations in order, stopping at the first failure.
|
|
70
|
+
*
|
|
71
|
+
* @param deferred the validations recorded while validating the values of the command.
|
|
72
|
+
* @param command the {@link Command} the validated values belong to.
|
|
73
|
+
* @param values all validated and converted values of the command.
|
|
74
|
+
* @param invalidArguments an array of {@link InvalidArgument} which is added to if a custom validation fails.
|
|
75
|
+
*/
|
|
76
|
+
export function runCustomValidation(
|
|
77
|
+
deferred: ReadonlyArray<DeferredCustomValidation>,
|
|
78
|
+
command: Command,
|
|
79
|
+
values: PopulatedValues | PopulatedSingleValueType,
|
|
80
|
+
invalidArguments: Array<InvalidArgument>,
|
|
81
|
+
): void {
|
|
82
|
+
for (const { argument, path, value } of deferred) {
|
|
83
|
+
const message = argument.validate!(value as ValueType | Values | Array<Values>, {
|
|
84
|
+
argument: argument as Argument,
|
|
85
|
+
path,
|
|
86
|
+
values,
|
|
87
|
+
command,
|
|
88
|
+
});
|
|
89
|
+
if (message !== undefined) {
|
|
90
|
+
invalidArguments.push({
|
|
91
|
+
argument,
|
|
92
|
+
name: path,
|
|
93
|
+
value: value as PopulatedValueType | PopulatedValues,
|
|
94
|
+
reason: InvalidArgumentReason.CUSTOM_VALIDATION,
|
|
95
|
+
message,
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
62
99
|
}
|
|
63
|
-
return {
|
|
64
|
-
argument,
|
|
65
|
-
name,
|
|
66
|
-
value: value as PopulatedValueType | PopulatedValues,
|
|
67
|
-
reason: InvalidArgumentReason.CUSTOM_VALIDATION,
|
|
68
|
-
message,
|
|
69
|
-
};
|
|
70
100
|
}
|
|
71
101
|
|
|
72
102
|
/**
|
|
@@ -193,6 +223,8 @@ function validatePrimitiveValue(
|
|
|
193
223
|
function validateArrayValue(
|
|
194
224
|
subCommandArgument: SubCommandArgument | ComplexOption,
|
|
195
225
|
arrayValue: Array<PopulatedSingleValueType | PopulatedValues | undefined>,
|
|
226
|
+
path: string,
|
|
227
|
+
deferred: Array<DeferredCustomValidation> | undefined,
|
|
196
228
|
): ArrayValueValidationResult {
|
|
197
229
|
const convertedArrayValue: Array<SingleValueType | Values> = [];
|
|
198
230
|
|
|
@@ -228,7 +260,12 @@ function validateArrayValue(
|
|
|
228
260
|
},
|
|
229
261
|
};
|
|
230
262
|
}
|
|
231
|
-
validationResult = validateObjectValue(
|
|
263
|
+
validationResult = validateObjectValue(
|
|
264
|
+
subCommandArgument as ComplexOption,
|
|
265
|
+
singleValue,
|
|
266
|
+
`${path}[${i}]`,
|
|
267
|
+
deferred,
|
|
268
|
+
);
|
|
232
269
|
} // if not array and not object, then must be primitive
|
|
233
270
|
else {
|
|
234
271
|
if (isComplexOption(subCommandArgument)) {
|
|
@@ -267,11 +304,14 @@ function validateArrayValue(
|
|
|
267
304
|
function validateObjectValue(
|
|
268
305
|
argument: ComplexOption,
|
|
269
306
|
objectValue: PopulatedValues,
|
|
307
|
+
path: string,
|
|
308
|
+
deferred: Array<DeferredCustomValidation> | undefined,
|
|
270
309
|
): ObjectValueValidationResult {
|
|
271
310
|
const convertedObjectValue: Values = {};
|
|
272
311
|
|
|
273
312
|
for (let i = 0; i < argument.properties.length; i++) {
|
|
274
313
|
const propertyArg = argument.properties[i]!;
|
|
314
|
+
const propertyPath = `${path}.${propertyArg.name}`;
|
|
275
315
|
let propertyValue = objectValue[propertyArg.name];
|
|
276
316
|
|
|
277
317
|
if (propertyValue === undefined && propertyArg.defaultValue !== undefined) {
|
|
@@ -311,6 +351,8 @@ function validateObjectValue(
|
|
|
311
351
|
validationResult = validateArrayValue(
|
|
312
352
|
propertyArg as ComplexOption | SubCommandArgument,
|
|
313
353
|
propertyValue,
|
|
354
|
+
propertyPath,
|
|
355
|
+
deferred,
|
|
314
356
|
);
|
|
315
357
|
} else if (typeof propertyValue === "object") {
|
|
316
358
|
if (propertyArg.type !== ComplexValueTypeName.COMPLEX) {
|
|
@@ -327,6 +369,8 @@ function validateObjectValue(
|
|
|
327
369
|
validationResult = validateObjectValue(
|
|
328
370
|
propertyArg as ComplexOption,
|
|
329
371
|
propertyValue as PopulatedValues,
|
|
372
|
+
propertyPath,
|
|
373
|
+
deferred,
|
|
330
374
|
);
|
|
331
375
|
} // if not array and not object, then must be primitive
|
|
332
376
|
else {
|
|
@@ -356,18 +400,8 @@ function validateObjectValue(
|
|
|
356
400
|
invalidArgument: withPrefix(validationResult.invalidArgument, `.${propertyArg.name}`),
|
|
357
401
|
};
|
|
358
402
|
}
|
|
359
|
-
// custom validation
|
|
360
|
-
|
|
361
|
-
propertyArg,
|
|
362
|
-
`.${propertyArg.name}`,
|
|
363
|
-
validationResult.validValue!,
|
|
364
|
-
);
|
|
365
|
-
if (customInvalidArgument !== undefined) {
|
|
366
|
-
return {
|
|
367
|
-
validValue: convertedObjectValue,
|
|
368
|
-
invalidArgument: customInvalidArgument,
|
|
369
|
-
};
|
|
370
|
-
}
|
|
403
|
+
// custom validation is deferred until all built-in validation has passed
|
|
404
|
+
deferCustomValidation(deferred, propertyArg, propertyPath, validationResult.validValue!);
|
|
371
405
|
}
|
|
372
406
|
return {
|
|
373
407
|
validValue: convertedObjectValue,
|
|
@@ -380,6 +414,7 @@ function doSubCommandArgumentValidation(
|
|
|
380
414
|
isArray: boolean,
|
|
381
415
|
isOptional: boolean,
|
|
382
416
|
invalidArguments: Array<InvalidArgument>,
|
|
417
|
+
deferred: Array<DeferredCustomValidation> | undefined,
|
|
383
418
|
): PopulatedValueType | PopulatedValues | Array<PopulatedValues> {
|
|
384
419
|
// if there is a value, check if it is valid
|
|
385
420
|
if (value !== undefined) {
|
|
@@ -395,7 +430,7 @@ function doSubCommandArgumentValidation(
|
|
|
395
430
|
});
|
|
396
431
|
return undefined;
|
|
397
432
|
}
|
|
398
|
-
validationResult = validateArrayValue(argument, value);
|
|
433
|
+
validationResult = validateArrayValue(argument, value, argument.name, deferred);
|
|
399
434
|
} else if (typeof value === "object") {
|
|
400
435
|
if (!isComplexOption(argument)) {
|
|
401
436
|
invalidArguments.push({
|
|
@@ -406,7 +441,7 @@ function doSubCommandArgumentValidation(
|
|
|
406
441
|
});
|
|
407
442
|
return undefined;
|
|
408
443
|
}
|
|
409
|
-
validationResult = validateObjectValue(argument, value);
|
|
444
|
+
validationResult = validateObjectValue(argument, value, argument.name, deferred);
|
|
410
445
|
} else {
|
|
411
446
|
if (isComplexOption(argument)) {
|
|
412
447
|
invalidArguments.push({
|
|
@@ -424,15 +459,7 @@ function doSubCommandArgumentValidation(
|
|
|
424
459
|
invalidArguments.push(withPrefix(validationResult.invalidArgument, argument.name));
|
|
425
460
|
return undefined;
|
|
426
461
|
}
|
|
427
|
-
|
|
428
|
-
argument,
|
|
429
|
-
argument.name,
|
|
430
|
-
validationResult.validValue!,
|
|
431
|
-
);
|
|
432
|
-
if (customInvalidArgument !== undefined) {
|
|
433
|
-
invalidArguments.push(customInvalidArgument);
|
|
434
|
-
return undefined;
|
|
435
|
-
}
|
|
462
|
+
deferCustomValidation(deferred, argument, argument.name, validationResult.validValue!);
|
|
436
463
|
return validationResult.validValue as PopulatedValueType | PopulatedValues | undefined;
|
|
437
464
|
}
|
|
438
465
|
|
|
@@ -453,11 +480,13 @@ function doSubCommandArgumentValidation(
|
|
|
453
480
|
* @param option the {@link Option} to validate against.
|
|
454
481
|
* @param value the value (if any) for the {@link Option}.
|
|
455
482
|
* @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
|
|
483
|
+
* @param deferred if provided, custom validations are recorded here for later invocation by {@link runCustomValidation}. If omitted, custom validations are not invoked.
|
|
456
484
|
*/
|
|
457
485
|
export function validateOptionValue(
|
|
458
486
|
option: Option | ComplexOption,
|
|
459
487
|
value: PopulatedValueType | PopulatedValues | Array<PopulatedValues | undefined>,
|
|
460
488
|
invalidArguments: Array<InvalidArgument>,
|
|
489
|
+
deferred?: Array<DeferredCustomValidation>,
|
|
461
490
|
): PopulatedValueType | PopulatedValues | Array<PopulatedValues> {
|
|
462
491
|
return doSubCommandArgumentValidation(
|
|
463
492
|
option,
|
|
@@ -465,6 +494,7 @@ export function validateOptionValue(
|
|
|
465
494
|
option.isArray || false,
|
|
466
495
|
option.isOptional || false,
|
|
467
496
|
invalidArguments,
|
|
497
|
+
deferred,
|
|
468
498
|
);
|
|
469
499
|
}
|
|
470
500
|
|
|
@@ -474,11 +504,13 @@ export function validateOptionValue(
|
|
|
474
504
|
* @param positional the {@link Positional} to validate against.
|
|
475
505
|
* @param value the value (if any) for the {@link Positional}.
|
|
476
506
|
* @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
|
|
507
|
+
* @param deferred if provided, custom validations are recorded here for later invocation by {@link runCustomValidation}. If omitted, custom validations are not invoked.
|
|
477
508
|
*/
|
|
478
509
|
export function validatePositionalValue(
|
|
479
510
|
positional: Positional,
|
|
480
511
|
value: PopulatedValueType,
|
|
481
512
|
invalidArguments: Array<InvalidArgument>,
|
|
513
|
+
deferred?: Array<DeferredCustomValidation>,
|
|
482
514
|
): PopulatedValueType {
|
|
483
515
|
return doSubCommandArgumentValidation(
|
|
484
516
|
positional,
|
|
@@ -486,6 +518,7 @@ export function validatePositionalValue(
|
|
|
486
518
|
positional.isVarargMultiple || false,
|
|
487
519
|
positional.isVarargOptional || false,
|
|
488
520
|
invalidArguments,
|
|
521
|
+
deferred,
|
|
489
522
|
) as PopulatedValueType;
|
|
490
523
|
}
|
|
491
524
|
|
|
@@ -496,11 +529,13 @@ export function validatePositionalValue(
|
|
|
496
529
|
* @param globalCommand the {@link GlobalCommand} providing the {@link GlobalCommandArgument}.
|
|
497
530
|
* @param value the value (if any) for the {@link GlobalCommandArgument}.
|
|
498
531
|
* @param invalidArguments an array of {@link InvalidArgument} which may be added to if the provided value is invalid.
|
|
532
|
+
* @param deferred if provided, custom validations are recorded here for later invocation by {@link runCustomValidation}. If omitted, custom validations are not invoked.
|
|
499
533
|
*/
|
|
500
534
|
export function validateGlobalCommandArgumentValue(
|
|
501
535
|
globalCommand: GlobalCommand,
|
|
502
536
|
value: PopulatedSingleValueType,
|
|
503
537
|
invalidArguments: Array<InvalidArgument>,
|
|
538
|
+
deferred?: Array<DeferredCustomValidation>,
|
|
504
539
|
): PopulatedSingleValueType {
|
|
505
540
|
// if this function is called it is because the argument is defined
|
|
506
541
|
const globalCommandArgument = globalCommand.argument!;
|
|
@@ -527,15 +562,12 @@ export function validateGlobalCommandArgumentValue(
|
|
|
527
562
|
return undefined;
|
|
528
563
|
}
|
|
529
564
|
|
|
530
|
-
|
|
565
|
+
deferCustomValidation(
|
|
566
|
+
deferred,
|
|
531
567
|
globalCommandArgument,
|
|
532
568
|
globalCommand.name,
|
|
533
569
|
validationResult.validValue!,
|
|
534
570
|
);
|
|
535
|
-
if (customInvalidArgument !== undefined) {
|
|
536
|
-
invalidArguments.push(customInvalidArgument);
|
|
537
|
-
return undefined;
|
|
538
|
-
}
|
|
539
571
|
return validationResult.validValue as PopulatedSingleValueType;
|
|
540
572
|
}
|
|
541
573
|
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
ComplexValueTypeName,
|
|
15
15
|
type PopulatedSingleValueType,
|
|
16
16
|
type PopulatedValues,
|
|
17
|
+
type Values,
|
|
17
18
|
} from "@flowscripter/dynamic-cli-framework-api";
|
|
19
|
+
import type { Command } from "@flowscripter/dynamic-cli-framework-api";
|
|
18
20
|
import type { Argument } from "@flowscripter/dynamic-cli-framework-api";
|
|
19
21
|
import type { Option } from "@flowscripter/dynamic-cli-framework-api";
|
|
20
22
|
import type { Positional } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -23,6 +25,20 @@ import type { SubCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
23
25
|
import type { GlobalCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
24
26
|
import { isGlobalCommand, isSubCommand } from "../../runtime/command/CommandTypeGuards.ts";
|
|
25
27
|
|
|
28
|
+
/**
|
|
29
|
+
* The command and values visible to {@link Argument.validate} functions while prompting.
|
|
30
|
+
*/
|
|
31
|
+
interface PromptScope {
|
|
32
|
+
readonly command: Command;
|
|
33
|
+
readonly values: PopulatedValues | PopulatedSingleValueType;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Used as {@link ValidationContext.command} when prompting via {@link ArgumentPrompterService.promptForArguments},
|
|
38
|
+
* which is not associated with a specific command.
|
|
39
|
+
*/
|
|
40
|
+
const NO_COMMAND = { name: "" } as Command;
|
|
41
|
+
|
|
26
42
|
export default class DefaultArgumentPrompterService implements ArgumentPrompterService {
|
|
27
43
|
readonly #prompterService: PrompterService;
|
|
28
44
|
|
|
@@ -63,6 +79,53 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
63
79
|
return parseResult;
|
|
64
80
|
}
|
|
65
81
|
|
|
82
|
+
async promptForArguments(
|
|
83
|
+
args: ReadonlyArray<Option | ComplexOption>,
|
|
84
|
+
existing: Values = {},
|
|
85
|
+
): Promise<Values | undefined> {
|
|
86
|
+
if (!this.#prompterService.promptEnabled) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const prompted: Record<string, Values[string]> = {};
|
|
90
|
+
const merged: Record<string, Values[string]> = { ...existing };
|
|
91
|
+
const scope: PromptScope = { command: NO_COMMAND, values: merged as PopulatedValues };
|
|
92
|
+
|
|
93
|
+
for (const arg of args) {
|
|
94
|
+
if (arg.name in existing) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const value = (await this.#promptForArgument(arg.name, arg, scope)) as Values[string];
|
|
98
|
+
prompted[arg.name] = value;
|
|
99
|
+
merged[arg.name] = value;
|
|
100
|
+
}
|
|
101
|
+
return prompted as Values;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #promptForArgument(
|
|
105
|
+
name: string,
|
|
106
|
+
arg: Option | Positional | ComplexOption,
|
|
107
|
+
scope: PromptScope,
|
|
108
|
+
): Promise<PopulatedValues[string]> {
|
|
109
|
+
if ("type" in arg && (arg as ComplexOption).type === ComplexValueTypeName.COMPLEX) {
|
|
110
|
+
return await this.#promptForComplexOption(name, arg as ComplexOption, scope);
|
|
111
|
+
}
|
|
112
|
+
if (DefaultArgumentPrompterService.#isArrayArgument(arg)) {
|
|
113
|
+
return await this.#promptForArrayArgument(name, arg as Option | Positional, scope);
|
|
114
|
+
}
|
|
115
|
+
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(
|
|
116
|
+
name,
|
|
117
|
+
(arg as Option | Positional).description ?? name,
|
|
118
|
+
arg as Argument,
|
|
119
|
+
name,
|
|
120
|
+
scope,
|
|
121
|
+
);
|
|
122
|
+
const result = await this.#prompterService.prompt(prompt);
|
|
123
|
+
return DefaultArgumentPrompterService.#coerceValue(
|
|
124
|
+
result.value as SingleValueType,
|
|
125
|
+
(arg as Argument).type,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
66
129
|
async #promptForGlobalCommand(parseResult: ParseResult): Promise<ParseResult> {
|
|
67
130
|
const command = parseResult.command as GlobalCommand;
|
|
68
131
|
if (!command.argument) {
|
|
@@ -73,6 +136,8 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
73
136
|
command.name,
|
|
74
137
|
command.name,
|
|
75
138
|
command.argument,
|
|
139
|
+
command.name,
|
|
140
|
+
{ command, values: undefined },
|
|
76
141
|
);
|
|
77
142
|
const result = await this.#prompterService.prompt(prompt);
|
|
78
143
|
const value = DefaultArgumentPrompterService.#coerceValue(
|
|
@@ -96,6 +161,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
96
161
|
...existingValues,
|
|
97
162
|
};
|
|
98
163
|
const remainingInvalid: InvalidArgument[] = [];
|
|
164
|
+
const scope: PromptScope = { command, values: newValues as PopulatedValues };
|
|
99
165
|
|
|
100
166
|
for (const invalid of parseResult.invalidArguments) {
|
|
101
167
|
if (invalid.reason !== InvalidArgumentReason.MISSING_VALUE || !invalid.name) {
|
|
@@ -109,30 +175,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
109
175
|
continue;
|
|
110
176
|
}
|
|
111
177
|
|
|
112
|
-
|
|
113
|
-
const complexValues = await this.#promptForComplexOption(
|
|
114
|
-
invalid.name,
|
|
115
|
-
arg as ComplexOption,
|
|
116
|
-
);
|
|
117
|
-
newValues[invalid.name] = complexValues;
|
|
118
|
-
} else if (DefaultArgumentPrompterService.#isArrayArgument(arg)) {
|
|
119
|
-
const arrayValues = await this.#promptForArrayArgument(
|
|
120
|
-
invalid.name,
|
|
121
|
-
arg as Option | Positional,
|
|
122
|
-
);
|
|
123
|
-
newValues[invalid.name] = arrayValues;
|
|
124
|
-
} else {
|
|
125
|
-
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(
|
|
126
|
-
invalid.name,
|
|
127
|
-
(arg as Option | Positional).description ?? invalid.name,
|
|
128
|
-
arg as Argument,
|
|
129
|
-
);
|
|
130
|
-
const result = await this.#prompterService.prompt(prompt);
|
|
131
|
-
newValues[invalid.name] = DefaultArgumentPrompterService.#coerceValue(
|
|
132
|
-
result.value as SingleValueType,
|
|
133
|
-
(arg as Argument).type,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
178
|
+
newValues[invalid.name] = await this.#promptForArgument(invalid.name, arg, scope);
|
|
136
179
|
}
|
|
137
180
|
|
|
138
181
|
return {
|
|
@@ -147,6 +190,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
147
190
|
async #promptForComplexOption(
|
|
148
191
|
name: string,
|
|
149
192
|
complexOption: ComplexOption,
|
|
193
|
+
scope: PromptScope,
|
|
150
194
|
): Promise<PopulatedValues> {
|
|
151
195
|
const values: Record<string, PopulatedValues[string]> = {};
|
|
152
196
|
|
|
@@ -155,6 +199,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
155
199
|
values[(prop as ComplexOption).name] = await this.#promptForComplexOption(
|
|
156
200
|
`${name}.${(prop as ComplexOption).name}`,
|
|
157
201
|
prop as ComplexOption,
|
|
202
|
+
scope,
|
|
158
203
|
);
|
|
159
204
|
} else {
|
|
160
205
|
const option = prop as Option;
|
|
@@ -165,6 +210,8 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
165
210
|
`${name}.${option.name}`,
|
|
166
211
|
option.description ?? `${name}.${option.name}`,
|
|
167
212
|
option,
|
|
213
|
+
`${name}.${option.name}`,
|
|
214
|
+
scope,
|
|
168
215
|
);
|
|
169
216
|
const result = await this.#prompterService.prompt(prompt);
|
|
170
217
|
values[option.name] = DefaultArgumentPrompterService.#coerceValue(
|
|
@@ -180,6 +227,7 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
180
227
|
async #promptForArrayArgument(
|
|
181
228
|
name: string,
|
|
182
229
|
arg: Option | Positional,
|
|
230
|
+
scope: PromptScope,
|
|
183
231
|
): Promise<Array<SingleValueType>> {
|
|
184
232
|
const values: SingleValueType[] = [];
|
|
185
233
|
|
|
@@ -188,6 +236,8 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
188
236
|
name,
|
|
189
237
|
`${arg.description ?? name}${values.length > 0 ? ` [${values.length + 1}]` : ""}`,
|
|
190
238
|
arg as Argument,
|
|
239
|
+
name,
|
|
240
|
+
scope,
|
|
191
241
|
);
|
|
192
242
|
const result = await this.#prompterService.prompt(prompt);
|
|
193
243
|
values.push(
|
|
@@ -212,7 +262,13 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
212
262
|
return values;
|
|
213
263
|
}
|
|
214
264
|
|
|
215
|
-
static #argumentToPrompt(
|
|
265
|
+
static #argumentToPrompt(
|
|
266
|
+
name: string,
|
|
267
|
+
description: string,
|
|
268
|
+
arg: Argument,
|
|
269
|
+
path: string,
|
|
270
|
+
scope: PromptScope,
|
|
271
|
+
): Prompt {
|
|
216
272
|
if (arg.type === ValueTypeName.BOOLEAN) {
|
|
217
273
|
return {
|
|
218
274
|
name,
|
|
@@ -240,7 +296,17 @@ export default class DefaultArgumentPrompterService implements ArgumentPrompterS
|
|
|
240
296
|
returnedValue: "",
|
|
241
297
|
min: arg.minValueInclusive,
|
|
242
298
|
max: arg.maxValueInclusive,
|
|
243
|
-
validate: arg.validate
|
|
299
|
+
validate: arg.validate
|
|
300
|
+
? (v) => {
|
|
301
|
+
const value = DefaultArgumentPrompterService.#coerceValue(v, arg.type);
|
|
302
|
+
return arg.validate!(value, {
|
|
303
|
+
argument: arg,
|
|
304
|
+
path,
|
|
305
|
+
values: scope.values === undefined ? value : scope.values,
|
|
306
|
+
command: scope.command,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
: undefined,
|
|
244
310
|
};
|
|
245
311
|
|
|
246
312
|
return {
|
|
@@ -226,6 +226,7 @@ export default class KeyValueServiceProvider implements ServiceProvider {
|
|
|
226
226
|
const scopedKeyValueService = this.getScopedKeyValueService(scopeType, scopeKey);
|
|
227
227
|
return {
|
|
228
228
|
cliConfig: context.cliConfig,
|
|
229
|
+
isTty: context.isTty,
|
|
229
230
|
getServiceById: (id: string): unknown =>
|
|
230
231
|
id === KEY_VALUE_SERVICE_ID ? scopedKeyValueService : context.getServiceById(id),
|
|
231
232
|
doesServiceExist: (id: string): boolean => context.doesServiceExist(id),
|
|
@@ -3,7 +3,7 @@ import LogLevelCommand from "./command/LogLevelCommand.ts";
|
|
|
3
3
|
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
4
|
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
5
|
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
-
import
|
|
6
|
+
import ThemeCommand from "./command/ThemeCommand.ts";
|
|
7
7
|
import NoColorCommand from "./command/NoColorCommand.ts";
|
|
8
8
|
import NoHyperlinksCommand from "./command/NoHyperlinksCommand.ts";
|
|
9
9
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -18,16 +18,23 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
18
18
|
readonly serviceId: string = PRINTER_SERVICE_ID;
|
|
19
19
|
readonly servicePriority: number;
|
|
20
20
|
readonly printerService: PrinterService;
|
|
21
|
+
readonly detectDarkBackground: (() => Promise<boolean | undefined>) | undefined;
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Create an instance of the service provider with the specified details.
|
|
24
25
|
*
|
|
25
26
|
* @param servicePriority the priority of the service.
|
|
26
27
|
* @param printerService the PrinterService implementation to use.
|
|
28
|
+
* @param detectDarkBackground optional function detecting whether the terminal background is dark.
|
|
27
29
|
*/
|
|
28
|
-
public constructor(
|
|
30
|
+
public constructor(
|
|
31
|
+
servicePriority: number,
|
|
32
|
+
printerService: PrinterService,
|
|
33
|
+
detectDarkBackground?: () => Promise<boolean | undefined>,
|
|
34
|
+
) {
|
|
29
35
|
this.servicePriority = servicePriority;
|
|
30
36
|
this.printerService = printerService;
|
|
37
|
+
this.detectDarkBackground = detectDarkBackground;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
public getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
@@ -35,8 +42,8 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
35
42
|
service: this.printerService,
|
|
36
43
|
commands: [
|
|
37
44
|
new NoHyperlinksCommand(this.servicePriority + 3),
|
|
38
|
-
new
|
|
39
|
-
new NoColorCommand(this.servicePriority +
|
|
45
|
+
new ThemeCommand(this.servicePriority + 1, this.detectDarkBackground),
|
|
46
|
+
new NoColorCommand(this.servicePriority + 2),
|
|
40
47
|
new LogLevelCommand(this.servicePriority),
|
|
41
48
|
],
|
|
42
49
|
});
|