@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
|
@@ -2,6 +2,11 @@ import { PromptType, } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
2
2
|
import { InvalidArgumentReason } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
3
|
import { ValueTypeName, ComplexValueTypeName, } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
4
|
import { isGlobalCommand, isSubCommand } from "../../runtime/command/CommandTypeGuards.js";
|
|
5
|
+
/**
|
|
6
|
+
* Used as {@link ValidationContext.command} when prompting via {@link ArgumentPrompterService.promptForArguments},
|
|
7
|
+
* which is not associated with a specific command.
|
|
8
|
+
*/
|
|
9
|
+
const NO_COMMAND = { name: "" };
|
|
5
10
|
export default class DefaultArgumentPrompterService {
|
|
6
11
|
#prompterService;
|
|
7
12
|
constructor(prompterService) {
|
|
@@ -34,12 +39,40 @@ export default class DefaultArgumentPrompterService {
|
|
|
34
39
|
}
|
|
35
40
|
return parseResult;
|
|
36
41
|
}
|
|
42
|
+
async promptForArguments(args, existing = {}) {
|
|
43
|
+
if (!this.#prompterService.promptEnabled) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const prompted = {};
|
|
47
|
+
const merged = { ...existing };
|
|
48
|
+
const scope = { command: NO_COMMAND, values: merged };
|
|
49
|
+
for (const arg of args) {
|
|
50
|
+
if (arg.name in existing) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const value = (await this.#promptForArgument(arg.name, arg, scope));
|
|
54
|
+
prompted[arg.name] = value;
|
|
55
|
+
merged[arg.name] = value;
|
|
56
|
+
}
|
|
57
|
+
return prompted;
|
|
58
|
+
}
|
|
59
|
+
async #promptForArgument(name, arg, scope) {
|
|
60
|
+
if ("type" in arg && arg.type === ComplexValueTypeName.COMPLEX) {
|
|
61
|
+
return await this.#promptForComplexOption(name, arg, scope);
|
|
62
|
+
}
|
|
63
|
+
if (DefaultArgumentPrompterService.#isArrayArgument(arg)) {
|
|
64
|
+
return await this.#promptForArrayArgument(name, arg, scope);
|
|
65
|
+
}
|
|
66
|
+
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(name, arg.description ?? name, arg, name, scope);
|
|
67
|
+
const result = await this.#prompterService.prompt(prompt);
|
|
68
|
+
return DefaultArgumentPrompterService.#coerceValue(result.value, arg.type);
|
|
69
|
+
}
|
|
37
70
|
async #promptForGlobalCommand(parseResult) {
|
|
38
71
|
const command = parseResult.command;
|
|
39
72
|
if (!command.argument) {
|
|
40
73
|
return parseResult;
|
|
41
74
|
}
|
|
42
|
-
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(command.name, command.name, command.argument);
|
|
75
|
+
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(command.name, command.name, command.argument, command.name, { command, values: undefined });
|
|
43
76
|
const result = await this.#prompterService.prompt(prompt);
|
|
44
77
|
const value = DefaultArgumentPrompterService.#coerceValue(result.value, command.argument.type);
|
|
45
78
|
return {
|
|
@@ -57,6 +90,7 @@ export default class DefaultArgumentPrompterService {
|
|
|
57
90
|
...existingValues,
|
|
58
91
|
};
|
|
59
92
|
const remainingInvalid = [];
|
|
93
|
+
const scope = { command, values: newValues };
|
|
60
94
|
for (const invalid of parseResult.invalidArguments) {
|
|
61
95
|
if (invalid.reason !== InvalidArgumentReason.MISSING_VALUE || !invalid.name) {
|
|
62
96
|
remainingInvalid.push(invalid);
|
|
@@ -67,19 +101,7 @@ export default class DefaultArgumentPrompterService {
|
|
|
67
101
|
remainingInvalid.push(invalid);
|
|
68
102
|
continue;
|
|
69
103
|
}
|
|
70
|
-
|
|
71
|
-
const complexValues = await this.#promptForComplexOption(invalid.name, arg);
|
|
72
|
-
newValues[invalid.name] = complexValues;
|
|
73
|
-
}
|
|
74
|
-
else if (DefaultArgumentPrompterService.#isArrayArgument(arg)) {
|
|
75
|
-
const arrayValues = await this.#promptForArrayArgument(invalid.name, arg);
|
|
76
|
-
newValues[invalid.name] = arrayValues;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(invalid.name, arg.description ?? invalid.name, arg);
|
|
80
|
-
const result = await this.#prompterService.prompt(prompt);
|
|
81
|
-
newValues[invalid.name] = DefaultArgumentPrompterService.#coerceValue(result.value, arg.type);
|
|
82
|
-
}
|
|
104
|
+
newValues[invalid.name] = await this.#promptForArgument(invalid.name, arg, scope);
|
|
83
105
|
}
|
|
84
106
|
return {
|
|
85
107
|
command: parseResult.command,
|
|
@@ -89,28 +111,28 @@ export default class DefaultArgumentPrompterService {
|
|
|
89
111
|
unusedArgs: parseResult.unusedArgs,
|
|
90
112
|
};
|
|
91
113
|
}
|
|
92
|
-
async #promptForComplexOption(name, complexOption) {
|
|
114
|
+
async #promptForComplexOption(name, complexOption, scope) {
|
|
93
115
|
const values = {};
|
|
94
116
|
for (const prop of complexOption.properties) {
|
|
95
117
|
if ("type" in prop && prop.type === ComplexValueTypeName.COMPLEX) {
|
|
96
|
-
values[prop.name] = await this.#promptForComplexOption(`${name}.${prop.name}`, prop);
|
|
118
|
+
values[prop.name] = await this.#promptForComplexOption(`${name}.${prop.name}`, prop, scope);
|
|
97
119
|
}
|
|
98
120
|
else {
|
|
99
121
|
const option = prop;
|
|
100
122
|
if (option.isOptional && option.defaultValue !== undefined) {
|
|
101
123
|
continue;
|
|
102
124
|
}
|
|
103
|
-
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(`${name}.${option.name}`, option.description ?? `${name}.${option.name}`, option);
|
|
125
|
+
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(`${name}.${option.name}`, option.description ?? `${name}.${option.name}`, option, `${name}.${option.name}`, scope);
|
|
104
126
|
const result = await this.#prompterService.prompt(prompt);
|
|
105
127
|
values[option.name] = DefaultArgumentPrompterService.#coerceValue(result.value, option.type);
|
|
106
128
|
}
|
|
107
129
|
}
|
|
108
130
|
return values;
|
|
109
131
|
}
|
|
110
|
-
async #promptForArrayArgument(name, arg) {
|
|
132
|
+
async #promptForArrayArgument(name, arg, scope) {
|
|
111
133
|
const values = [];
|
|
112
134
|
while (true) {
|
|
113
|
-
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(name, `${arg.description ?? name}${values.length > 0 ? ` [${values.length + 1}]` : ""}`, arg);
|
|
135
|
+
const prompt = DefaultArgumentPrompterService.#argumentToPrompt(name, `${arg.description ?? name}${values.length > 0 ? ` [${values.length + 1}]` : ""}`, arg, name, scope);
|
|
114
136
|
const result = await this.#prompterService.prompt(prompt);
|
|
115
137
|
values.push(DefaultArgumentPrompterService.#coerceValue(result.value, arg.type));
|
|
116
138
|
const addAnother = {
|
|
@@ -126,7 +148,7 @@ export default class DefaultArgumentPrompterService {
|
|
|
126
148
|
}
|
|
127
149
|
return values;
|
|
128
150
|
}
|
|
129
|
-
static #argumentToPrompt(name, description, arg) {
|
|
151
|
+
static #argumentToPrompt(name, description, arg, path, scope) {
|
|
130
152
|
if (arg.type === ValueTypeName.BOOLEAN) {
|
|
131
153
|
return {
|
|
132
154
|
name,
|
|
@@ -152,7 +174,17 @@ export default class DefaultArgumentPrompterService {
|
|
|
152
174
|
returnedValue: "",
|
|
153
175
|
min: arg.minValueInclusive,
|
|
154
176
|
max: arg.maxValueInclusive,
|
|
155
|
-
validate: arg.validate
|
|
177
|
+
validate: arg.validate
|
|
178
|
+
? (v) => {
|
|
179
|
+
const value = DefaultArgumentPrompterService.#coerceValue(v, arg.type);
|
|
180
|
+
return arg.validate(value, {
|
|
181
|
+
argument: arg,
|
|
182
|
+
path,
|
|
183
|
+
values: scope.values === undefined ? value : scope.values,
|
|
184
|
+
command: scope.command,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
: undefined,
|
|
156
188
|
};
|
|
157
189
|
return {
|
|
158
190
|
name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeyValueServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/KeyValueServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,yCAAyC,CAAC;AAIjD,OAAO,KAAK,4BAA4B,MAAM,mCAAmC,CAAC;AAClF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAoClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAwB,YAAW,eAAe;;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAwB;IAClD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAIjC,SAAgB,sBAAsB,EAAE,OAAO,CAAC;IAChD,SAAgB,oBAAoB,EAAE,OAAO,CAAC;IAW9C;;;;;;;;OAQG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,4BAA4B,EAAE,4BAA4B,EAC1D,sBAAsB,UAAQ,EAC9B,oBAAoB,UAAQ,EAY7B;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAUhE;IAED;;;;;;;;;;OAUG;IACI,wBAAwB,CAC7B,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,eAAe,CAwBjB;IAED;;;;;;;;;;;;;;OAcG;IACI,kBAAkB,CACvB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,OAAO,
|
|
1
|
+
{"version":3,"file":"KeyValueServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/configuration/KeyValueServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AACvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,eAAe,EAGhB,MAAM,yCAAyC,CAAC;AAIjD,OAAO,KAAK,4BAA4B,MAAM,mCAAmC,CAAC;AAClF,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAoClF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,MAAM,CAAC,OAAO,OAAO,uBAAwB,YAAW,eAAe;;IACrE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAwB;IAClD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAIjC,SAAgB,sBAAsB,EAAE,OAAO,CAAC;IAChD,SAAgB,oBAAoB,EAAE,OAAO,CAAC;IAW9C;;;;;;;;OAQG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,4BAA4B,EAAE,4BAA4B,EAC1D,sBAAsB,UAAQ,EAC9B,oBAAoB,UAAQ,EAY7B;IAEM,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAUhE;IAED;;;;;;;;;;OAUG;IACI,wBAAwB,CAC7B,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,eAAe,CAwBjB;IAED;;;;;;;;;;;;;;OAcG;IACI,kBAAkB,CACvB,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,wBAAwB,EACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAcT;IAqBY,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CASxD;CACF"}
|
|
@@ -180,6 +180,7 @@ export default class KeyValueServiceProvider {
|
|
|
180
180
|
const scopedKeyValueService = this.getScopedKeyValueService(scopeType, scopeKey);
|
|
181
181
|
return {
|
|
182
182
|
cliConfig: context.cliConfig,
|
|
183
|
+
isTty: context.isTty,
|
|
183
184
|
getServiceById: (id) => id === KEY_VALUE_SERVICE_ID ? scopedKeyValueService : context.getServiceById(id),
|
|
184
185
|
doesServiceExist: (id) => context.doesServiceExist(id),
|
|
185
186
|
addServiceInstance: (id, serviceInstance) => context.addServiceInstance(id, serviceInstance),
|
|
@@ -9,13 +9,15 @@ export default class PrinterServiceProvider implements ServiceProvider {
|
|
|
9
9
|
readonly serviceId: string;
|
|
10
10
|
readonly servicePriority: number;
|
|
11
11
|
readonly printerService: PrinterService;
|
|
12
|
+
readonly detectDarkBackground: (() => Promise<boolean | undefined>) | undefined;
|
|
12
13
|
/**
|
|
13
14
|
* Create an instance of the service provider with the specified details.
|
|
14
15
|
*
|
|
15
16
|
* @param servicePriority the priority of the service.
|
|
16
17
|
* @param printerService the PrinterService implementation to use.
|
|
18
|
+
* @param detectDarkBackground optional function detecting whether the terminal background is dark.
|
|
17
19
|
*/
|
|
18
|
-
constructor(servicePriority: number, printerService: PrinterService);
|
|
20
|
+
constructor(servicePriority: number, printerService: PrinterService, detectDarkBackground?: () => Promise<boolean | undefined>);
|
|
19
21
|
getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo>;
|
|
20
22
|
initService(context: Context): Promise<void>;
|
|
21
23
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PrinterServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/printer/PrinterServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAK9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAsB;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"PrinterServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/printer/PrinterServiceProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAK9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,sBAAuB,YAAW,eAAe;IACpE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAsB;IAChD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IAEhF;;;;;;OAMG;IACH,YACE,eAAe,EAAE,MAAM,EACvB,cAAc,EAAE,cAAc,EAC9B,oBAAoB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,EAK1D;IAEM,cAAc,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC,CAUjE;IAED,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3C;CACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import LogLevelCommand from "./command/LogLevelCommand.js";
|
|
2
2
|
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
-
import
|
|
3
|
+
import ThemeCommand from "./command/ThemeCommand.js";
|
|
4
4
|
import NoColorCommand from "./command/NoColorCommand.js";
|
|
5
5
|
import NoHyperlinksCommand from "./command/NoHyperlinksCommand.js";
|
|
6
6
|
import { SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -12,23 +12,26 @@ export default class PrinterServiceProvider {
|
|
|
12
12
|
serviceId = PRINTER_SERVICE_ID;
|
|
13
13
|
servicePriority;
|
|
14
14
|
printerService;
|
|
15
|
+
detectDarkBackground;
|
|
15
16
|
/**
|
|
16
17
|
* Create an instance of the service provider with the specified details.
|
|
17
18
|
*
|
|
18
19
|
* @param servicePriority the priority of the service.
|
|
19
20
|
* @param printerService the PrinterService implementation to use.
|
|
21
|
+
* @param detectDarkBackground optional function detecting whether the terminal background is dark.
|
|
20
22
|
*/
|
|
21
|
-
constructor(servicePriority, printerService) {
|
|
23
|
+
constructor(servicePriority, printerService, detectDarkBackground) {
|
|
22
24
|
this.servicePriority = servicePriority;
|
|
23
25
|
this.printerService = printerService;
|
|
26
|
+
this.detectDarkBackground = detectDarkBackground;
|
|
24
27
|
}
|
|
25
28
|
getServiceInfo(_cliConfig) {
|
|
26
29
|
return Promise.resolve({
|
|
27
30
|
service: this.printerService,
|
|
28
31
|
commands: [
|
|
29
32
|
new NoHyperlinksCommand(this.servicePriority + 3),
|
|
30
|
-
new
|
|
31
|
-
new NoColorCommand(this.servicePriority +
|
|
33
|
+
new ThemeCommand(this.servicePriority + 1, this.detectDarkBackground),
|
|
34
|
+
new NoColorCommand(this.servicePriority + 2),
|
|
32
35
|
new LogLevelCommand(this.servicePriority),
|
|
33
36
|
],
|
|
34
37
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GlobalModifierCommand } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { GlobalCommandArgument } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import { type SingleValueType } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
+
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
/**
|
|
6
|
+
* Command allowing the specification of the dark/light theme for the {@link PrinterService}.
|
|
7
|
+
*
|
|
8
|
+
* With the `auto` value the terminal background is detected and light is used if it cannot be determined.
|
|
9
|
+
*/
|
|
10
|
+
export default class ThemeCommand implements GlobalModifierCommand {
|
|
11
|
+
#private;
|
|
12
|
+
readonly name = "theme";
|
|
13
|
+
readonly description = "Theme for output";
|
|
14
|
+
readonly enableConfiguration = true;
|
|
15
|
+
readonly argument: GlobalCommandArgument;
|
|
16
|
+
readonly executePriority: number;
|
|
17
|
+
/**
|
|
18
|
+
* @param executePriority the priority of the command.
|
|
19
|
+
* @param detect optional function detecting whether the terminal background is dark.
|
|
20
|
+
*/
|
|
21
|
+
constructor(executePriority: number, detect?: () => Promise<boolean | undefined>);
|
|
22
|
+
execute(context: Context, argumentValue: SingleValueType): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=ThemeCommand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeCommand.d.ts","sourceRoot":"","sources":["../../../../../src/service/printer/command/ThemeCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAC;AACrF,OAAO,EAAE,KAAK,eAAe,EAAiB,MAAM,yCAAyC,CAAC;AAC9F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yCAAyC,CAAC;AAIvE;;;;GAIG;AACH,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,qBAAqB;;IAChE,QAAQ,CAAC,IAAI,WAAW;IACxB,QAAQ,CAAC,WAAW,sBAAsB;IAC1C,QAAQ,CAAC,mBAAmB,QAAQ;IACpC,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAKtC;IACF,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAGjC;;;OAGG;IACH,YAAmB,eAAe,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,EAGtF;IAEY,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpF;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ValueTypeName } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { PRINTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
/**
|
|
4
|
+
* Command allowing the specification of the dark/light theme for the {@link PrinterService}.
|
|
5
|
+
*
|
|
6
|
+
* With the `auto` value the terminal background is detected and light is used if it cannot be determined.
|
|
7
|
+
*/
|
|
8
|
+
export default class ThemeCommand {
|
|
9
|
+
name = "theme";
|
|
10
|
+
description = "Theme for output";
|
|
11
|
+
enableConfiguration = true;
|
|
12
|
+
argument = {
|
|
13
|
+
type: ValueTypeName.STRING,
|
|
14
|
+
defaultValue: "auto",
|
|
15
|
+
allowableValues: ["auto", "dark", "light"],
|
|
16
|
+
configurationKey: "THEME",
|
|
17
|
+
};
|
|
18
|
+
executePriority;
|
|
19
|
+
#detect;
|
|
20
|
+
/**
|
|
21
|
+
* @param executePriority the priority of the command.
|
|
22
|
+
* @param detect optional function detecting whether the terminal background is dark.
|
|
23
|
+
*/
|
|
24
|
+
constructor(executePriority, detect) {
|
|
25
|
+
this.executePriority = executePriority;
|
|
26
|
+
this.#detect = detect;
|
|
27
|
+
}
|
|
28
|
+
async execute(context, argumentValue) {
|
|
29
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
30
|
+
if (argumentValue === "auto") {
|
|
31
|
+
printerService.darkMode =
|
|
32
|
+
printerService.colorEnabled && this.#detect !== undefined
|
|
33
|
+
? ((await this.#detect()) ?? false)
|
|
34
|
+
: false;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
printerService.darkMode = argumentValue === "dark";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export declare const ESC = "\u001B";
|
|
2
|
+
export declare const BEL = "\u0007";
|
|
1
3
|
export declare const CLEAR_LINE: string;
|
|
2
4
|
export declare const CURSOR_LEFT: string;
|
|
3
5
|
export declare const CURSOR_UP: string;
|
|
@@ -7,6 +9,8 @@ export declare const ITALIC_START: string;
|
|
|
7
9
|
export declare const ITALIC_END: string;
|
|
8
10
|
export declare const FOREGROUND_COLOR_END: string;
|
|
9
11
|
export declare const BACKGROUND_COLOR_END: string;
|
|
12
|
+
export declare const QUERY_BACKGROUND_COLOR = "\u001B]11;?\u001B\\";
|
|
13
|
+
export declare const QUERY_DEVICE_ATTRIBUTES: string;
|
|
10
14
|
export declare const HYPERLINK_END = "\u001B]8;;\u0007";
|
|
11
15
|
export declare function foregroundColorStart(r: number, g: number, b: number): string;
|
|
12
16
|
export declare function backgroundColorStart(r: number, g: number, b: number): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Ansi.d.ts","sourceRoot":"","sources":["../../../src/terminal/Ansi.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Ansi.d.ts","sourceRoot":"","sources":["../../../src/terminal/Ansi.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,GAAG,WAAS,CAAC;AAC1B,eAAO,MAAM,GAAG,WAAS,CAAC;AAM1B,eAAO,MAAM,UAAU,QAAa,CAAC;AACrC,eAAO,MAAM,WAAW,QAAY,CAAC;AACrC,eAAO,MAAM,SAAS,QAAa,CAAC;AACpC,eAAO,MAAM,WAAW,QAAe,CAAC;AACxC,eAAO,MAAM,WAAW,QAAe,CAAC;AACxC,eAAO,MAAM,YAAY,QAAa,CAAC;AACvC,eAAO,MAAM,UAAU,QAAc,CAAC;AACtC,eAAO,MAAM,oBAAoB,QAAc,CAAC;AAChD,eAAO,MAAM,oBAAoB,QAAc,CAAC;AAChD,eAAO,MAAM,sBAAsB,wBAAoB,CAAC;AACxD,eAAO,MAAM,uBAAuB,QAAY,CAAC;AACjD,eAAO,MAAM,aAAa,qBAA8B,CAAC;AAEzD,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5E;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
3
|
-
const
|
|
1
|
+
export const ESC = "\x1b";
|
|
2
|
+
export const BEL = "\x07";
|
|
3
|
+
const CSI = ESC + "[";
|
|
4
|
+
const OSC = ESC + "]";
|
|
5
|
+
const ST = ESC + "\\";
|
|
4
6
|
const SEP = ";";
|
|
5
7
|
export const CLEAR_LINE = CSI + "2K";
|
|
6
8
|
export const CURSOR_LEFT = CSI + "G";
|
|
@@ -11,6 +13,8 @@ export const ITALIC_START = CSI + "3m";
|
|
|
11
13
|
export const ITALIC_END = CSI + "23m";
|
|
12
14
|
export const FOREGROUND_COLOR_END = CSI + "39m";
|
|
13
15
|
export const BACKGROUND_COLOR_END = CSI + "49m";
|
|
16
|
+
export const QUERY_BACKGROUND_COLOR = `${OSC}11;?${ST}`;
|
|
17
|
+
export const QUERY_DEVICE_ATTRIBUTES = CSI + "c";
|
|
14
18
|
export const HYPERLINK_END = `${OSC}8${SEP}${SEP}${BEL}`;
|
|
15
19
|
export function foregroundColorStart(r, g, b) {
|
|
16
20
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type KeyReader from "./KeyReader.ts";
|
|
2
|
+
import type Terminal from "./Terminal.ts";
|
|
3
|
+
export declare const DEFAULT_DETECTION_TIMEOUT_MS = 150;
|
|
4
|
+
/**
|
|
5
|
+
* Parse the COLORFGBG environment variable value into a dark background flag.
|
|
6
|
+
*
|
|
7
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseColorFgBg(value: string | undefined): boolean | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Parse an OSC 11 background colour reply into a dark background flag.
|
|
12
|
+
*
|
|
13
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if no reply is present.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseOsc11Reply(data: string): boolean | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Detect whether the terminal background is dark.
|
|
18
|
+
*
|
|
19
|
+
* Checks COLORFGBG first and then queries the terminal for its background colour (OSC 11).
|
|
20
|
+
* The query is skipped unless the terminal is a TTY and is bounded by a timeout.
|
|
21
|
+
*
|
|
22
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
23
|
+
*/
|
|
24
|
+
export declare function detectDarkBackground(keyReader: KeyReader, terminal: Terminal, env?: Record<string, string | undefined>, timeoutMs?: number): Promise<boolean | undefined>;
|
|
25
|
+
//# sourceMappingURL=BackgroundDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackgroundDetector.d.ts","sourceRoot":"","sources":["../../../src/terminal/BackgroundDetector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAQ1C,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAc7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CASjE;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,SAAS,GAAE,MAAqC,GAC/C,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAe9B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { BEL, ESC, QUERY_BACKGROUND_COLOR, QUERY_DEVICE_ATTRIBUTES } from "./Ansi.js";
|
|
3
|
+
const OSC_11_REPLY = new RegExp(`${ESC}\\]11;rgb:([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})/([0-9a-fA-F]{1,4})(?:${BEL}|${ESC}\\\\)`);
|
|
4
|
+
const DA1_REPLY = new RegExp(`${ESC}\\[\\?[0-9;]*c`);
|
|
5
|
+
export const DEFAULT_DETECTION_TIMEOUT_MS = 150;
|
|
6
|
+
/**
|
|
7
|
+
* Parse the COLORFGBG environment variable value into a dark background flag.
|
|
8
|
+
*
|
|
9
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
10
|
+
*/
|
|
11
|
+
export function parseColorFgBg(value) {
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const parts = value.split(";");
|
|
16
|
+
const background = parts[parts.length - 1];
|
|
17
|
+
if (!/^\d{1,2}$/.test(background)) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const index = Number(background);
|
|
21
|
+
if (index > 15) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return index <= 6 || index === 8;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse an OSC 11 background colour reply into a dark background flag.
|
|
28
|
+
*
|
|
29
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if no reply is present.
|
|
30
|
+
*/
|
|
31
|
+
export function parseOsc11Reply(data) {
|
|
32
|
+
const match = OSC_11_REPLY.exec(data);
|
|
33
|
+
if (match === null) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const [r, g, b] = [match[1], match[2], match[3]].map((channel) => parseInt(channel, 16) / (16 ** channel.length - 1));
|
|
37
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b < 0.5;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Detect whether the terminal background is dark.
|
|
41
|
+
*
|
|
42
|
+
* Checks COLORFGBG first and then queries the terminal for its background colour (OSC 11).
|
|
43
|
+
* The query is skipped unless the terminal is a TTY and is bounded by a timeout.
|
|
44
|
+
*
|
|
45
|
+
* @returns `true` for a dark background, `false` for a light background or `undefined` if not determinable.
|
|
46
|
+
*/
|
|
47
|
+
export async function detectDarkBackground(keyReader, terminal, env = process.env, timeoutMs = DEFAULT_DETECTION_TIMEOUT_MS) {
|
|
48
|
+
if (!terminal.isTty() || env.TERM === "dumb") {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
const fromEnv = parseColorFgBg(env.COLORFGBG);
|
|
52
|
+
if (fromEnv !== undefined) {
|
|
53
|
+
return fromEnv;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const reply = keyReader.readRaw((received) => DA1_REPLY.test(received), timeoutMs);
|
|
57
|
+
await terminal.write(QUERY_BACKGROUND_COLOR + QUERY_DEVICE_ATTRIBUTES);
|
|
58
|
+
return parseOsc11Reply(await reply);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -18,5 +18,12 @@ export default interface KeyReader {
|
|
|
18
18
|
enableRawMode(): void;
|
|
19
19
|
disableRawMode(): void;
|
|
20
20
|
readKey(): Promise<KeyEvent>;
|
|
21
|
+
/**
|
|
22
|
+
* Read raw input until `isComplete` returns `true` for the accumulated input or `timeoutMs` elapses.
|
|
23
|
+
* Raw mode and paused state are restored afterwards.
|
|
24
|
+
*
|
|
25
|
+
* @returns the accumulated input.
|
|
26
|
+
*/
|
|
27
|
+
readRaw(isComplete: (received: string) => boolean, timeoutMs: number): Promise<string>;
|
|
21
28
|
}
|
|
22
29
|
//# sourceMappingURL=KeyReader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/KeyReader.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IACpB,EAAE,IAAI;IACN,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,SAAS,IAAI;IACb,MAAM,IAAI;IACV,GAAG,IAAI;IACP,SAAS,IAAI;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC;AAED,MAAM,CAAC,OAAO,WAAW,SAAS;IAChC,aAAa,IAAI,IAAI,CAAC;IACtB,cAAc,IAAI,IAAI,CAAC;IACvB,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"KeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/KeyReader.ts"],"names":[],"mappings":"AAAA,oBAAY,UAAU;IACpB,EAAE,IAAI;IACN,IAAI,IAAI;IACR,IAAI,IAAI;IACR,KAAK,IAAI;IACT,KAAK,IAAI;IACT,KAAK,IAAI;IACT,SAAS,IAAI;IACb,MAAM,IAAI;IACV,GAAG,IAAI;IACP,SAAS,IAAI;CACd;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;CAClC;AAED,MAAM,CAAC,OAAO,WAAW,SAAS;IAChC,aAAa,IAAI,IAAI,CAAC;IACtB,cAAc,IAAI,IAAI,CAAC;IACvB,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7B;;;;;OAKG;IACH,OAAO,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACxF"}
|
|
@@ -7,5 +7,6 @@ export default class TtyKeyReader implements KeyReader {
|
|
|
7
7
|
enableRawMode(): void;
|
|
8
8
|
disableRawMode(): void;
|
|
9
9
|
readKey(): Promise<KeyEvent>;
|
|
10
|
+
readRaw(isComplete: (received: string) => boolean, timeoutMs: number): Promise<string>;
|
|
10
11
|
}
|
|
11
12
|
//# sourceMappingURL=TtyKeyReader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TtyKeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyKeyReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,SAAS;;IAGpD,YAAY,aAAa,EAAE,UAAU,EAKpC;IAED,aAAa,IAAI,IAAI,CAGpB;IAED,cAAc,IAAI,IAAI,CAGrB;IAED,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CA2B3B;CAiDF"}
|
|
1
|
+
{"version":3,"file":"TtyKeyReader.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyKeyReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,gBAAgB,CAAC;AAE3D,MAAM,CAAC,OAAO,OAAO,YAAa,YAAW,SAAS;;IAGpD,YAAY,aAAa,EAAE,UAAU,EAKpC;IAED,aAAa,IAAI,IAAI,CAGpB;IAED,cAAc,IAAI,IAAI,CAGrB;IAED,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC,CA2B3B;IAED,OAAO,CAAC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCrF;CAiDF"}
|
|
@@ -41,6 +41,36 @@ export default class TtyKeyReader {
|
|
|
41
41
|
this.#ttyReadStream.on("data", onData);
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
|
+
readRaw(isComplete, timeoutMs) {
|
|
45
|
+
const stream = this.#ttyReadStream;
|
|
46
|
+
const wasRaw = stream.isRaw === true;
|
|
47
|
+
const wasPaused = stream.isPaused();
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
let received = "";
|
|
50
|
+
let timer;
|
|
51
|
+
const finish = () => {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
stream.removeListener("data", onData);
|
|
54
|
+
if (!wasRaw) {
|
|
55
|
+
stream.setRawMode(false);
|
|
56
|
+
}
|
|
57
|
+
if (wasPaused) {
|
|
58
|
+
stream.pause();
|
|
59
|
+
}
|
|
60
|
+
resolve(received);
|
|
61
|
+
};
|
|
62
|
+
const onData = (data) => {
|
|
63
|
+
received += data.toString();
|
|
64
|
+
if (isComplete(received)) {
|
|
65
|
+
finish();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
stream.setRawMode(true);
|
|
69
|
+
stream.on("data", onData);
|
|
70
|
+
stream.resume();
|
|
71
|
+
timer = setTimeout(finish, timeoutMs);
|
|
72
|
+
});
|
|
73
|
+
}
|
|
44
74
|
static #decodeKeyEvent(data) {
|
|
45
75
|
if (data.length === 0) {
|
|
46
76
|
return {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowscripter/dynamic-cli-framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "A framework for developing CLI applications which supports dynamic discovery and installation of new commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"build": "tsc -p tsconfig.build.json"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@flowscripter/dynamic-cli-framework-api": "^5.0
|
|
52
|
+
"@flowscripter/dynamic-cli-framework-api": "^5.1.0",
|
|
53
53
|
"@flowscripter/dynamic-plugin-framework": "^2.2.22",
|
|
54
54
|
"emphasize": "^7.0.0",
|
|
55
55
|
"fastest-levenshtein": "^1.0.16",
|
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type Terminal from "../terminal/Terminal.ts";
|
|
|
44
44
|
import type { PrinterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
45
45
|
import DefaultPrinterService from "../service/printer/DefaultPrinterService.ts";
|
|
46
46
|
import type Styler from "../terminal/Styler.ts";
|
|
47
|
+
import { detectDarkBackground } from "../terminal/BackgroundDetector.ts";
|
|
47
48
|
import type KeyReader from "../terminal/KeyReader.ts";
|
|
48
49
|
import type BaseCLIFeatureOptions from "./BaseCLIFeatureOptions.ts";
|
|
49
50
|
import DefaultPrompterService, {
|
|
@@ -196,7 +197,11 @@ export default class BaseCLI implements CLI {
|
|
|
196
197
|
this.#commandRegistry = new DefaultCommandRegistry([], commandValidator);
|
|
197
198
|
|
|
198
199
|
// create a context
|
|
199
|
-
this.#context = new DefaultContext(this.#cliConfig
|
|
200
|
+
this.#context = new DefaultContext(this.#cliConfig, {
|
|
201
|
+
stdin: keyReader !== undefined,
|
|
202
|
+
stdout: stdoutTerminal.isTty(),
|
|
203
|
+
stderr: stderrTerminal.isTty(),
|
|
204
|
+
});
|
|
200
205
|
|
|
201
206
|
this.#startupServiceProvider = new StartupServiceProvider(STARTUP_SERVICE_PRIORITY);
|
|
202
207
|
|
|
@@ -272,13 +277,19 @@ export default class BaseCLI implements CLI {
|
|
|
272
277
|
// create and add core services
|
|
273
278
|
this.addServiceProvider(new ShutdownServiceProvider(SHUTDOWN_SERVICE_PRIORITY));
|
|
274
279
|
this.addServiceProvider(this.#startupServiceProvider);
|
|
280
|
+
const canPrompt = this.#context.isTty.stdin && this.#context.isTty.stderr;
|
|
281
|
+
|
|
275
282
|
this.addServiceProvider(
|
|
276
|
-
new PrinterServiceProvider(
|
|
283
|
+
new PrinterServiceProvider(
|
|
284
|
+
PRINTER_SERVICE_PRIORITY,
|
|
285
|
+
this.#printerService,
|
|
286
|
+
canPrompt && this.#keyReader !== undefined
|
|
287
|
+
? () => detectDarkBackground(this.#keyReader!, this.#stderrTerminal)
|
|
288
|
+
: undefined,
|
|
289
|
+
),
|
|
277
290
|
);
|
|
278
291
|
this.addServiceProvider(new TableGeneratorServiceProvider(TABLE_GENERATOR_SERVICE_PRIORITY));
|
|
279
292
|
|
|
280
|
-
const canPrompt = this.#keyReader !== undefined && this.#stderrTerminal.isTty();
|
|
281
|
-
|
|
282
293
|
if (this.#options.promptingEnabled && !canPrompt) {
|
|
283
294
|
throw new Error("promptingEnabled requires a keyReader and a TTY stderr terminal");
|
|
284
295
|
}
|
|
@@ -7,7 +7,10 @@ import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
7
7
|
export default class DefaultContext implements Context {
|
|
8
8
|
readonly #serviceInstancesById: Map<string, unknown> = new Map();
|
|
9
9
|
|
|
10
|
-
public constructor(
|
|
10
|
+
public constructor(
|
|
11
|
+
readonly cliConfig: CLIConfig,
|
|
12
|
+
readonly isTty: Context["isTty"] = { stdin: false, stdout: false, stderr: false },
|
|
13
|
+
) {}
|
|
11
14
|
|
|
12
15
|
public addServiceInstance(id: string, serviceInstance: unknown) {
|
|
13
16
|
if (this.#serviceInstancesById.has(id)) {
|