@flowscripter/dynamic-cli-framework 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/src/cli/BaseCLI.d.ts +8 -1
- package/dist/src/cli/BaseCLI.d.ts.map +1 -1
- package/dist/src/cli/BaseCLI.js +38 -6
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts +1 -0
- package/dist/src/cli/BaseCLIFeatureOptions.d.ts.map +1 -1
- package/dist/src/cli/DefaultRuntimeCLI.d.ts +4 -0
- package/dist/src/cli/DefaultRuntimeCLI.d.ts.map +1 -1
- package/dist/src/cli/DefaultRuntimeCLI.js +29 -1
- package/dist/src/service/plugin/DefaultPluginService.d.ts +1 -1
- package/dist/src/service/plugin/DefaultPluginService.d.ts.map +1 -1
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts +3 -2
- package/dist/src/service/plugin/DefaultPluginServiceProvider.d.ts.map +1 -1
- package/dist/src/service/plugin/DefaultPluginServiceProvider.js +22 -2
- package/dist/src/service/plugin/SpawnInterfaceAdapter.d.ts +19 -0
- package/dist/src/service/plugin/SpawnInterfaceAdapter.d.ts.map +1 -0
- package/dist/src/service/plugin/SpawnInterfaceAdapter.js +40 -0
- package/dist/src/service/printer/DefaultPrinterService.d.ts.map +1 -1
- package/dist/src/service/printer/DefaultPrinterService.js +15 -10
- package/dist/src/service/spawn/DefaultSpawnService.d.ts +7 -0
- package/dist/src/service/spawn/DefaultSpawnService.d.ts.map +1 -0
- package/dist/src/service/spawn/DefaultSpawnService.js +94 -0
- package/dist/src/service/spawn/SpawnServiceProvider.d.ts +12 -0
- package/dist/src/service/spawn/SpawnServiceProvider.d.ts.map +1 -0
- package/dist/src/service/spawn/SpawnServiceProvider.js +24 -0
- package/dist/src/terminal/NonTtyTerminal.d.ts +14 -0
- package/dist/src/terminal/NonTtyTerminal.d.ts.map +1 -0
- package/dist/src/terminal/NonTtyTerminal.js +36 -0
- package/dist/src/terminal/Terminal.d.ts +1 -0
- package/dist/src/terminal/Terminal.d.ts.map +1 -1
- package/dist/src/terminal/TtyStyler.d.ts +1 -0
- package/dist/src/terminal/TtyStyler.d.ts.map +1 -1
- package/dist/src/terminal/TtyStyler.js +6 -2
- package/dist/src/terminal/TtyTerminal.d.ts +1 -0
- package/dist/src/terminal/TtyTerminal.d.ts.map +1 -1
- package/dist/src/terminal/TtyTerminal.js +3 -0
- package/index.ts +10 -1
- package/package.json +3 -3
- package/src/cli/BaseCLI.ts +54 -13
- package/src/cli/BaseCLIFeatureOptions.ts +1 -0
- package/src/cli/DefaultRuntimeCLI.ts +52 -8
- package/src/service/plugin/DefaultPluginService.ts +1 -1
- package/src/service/plugin/DefaultPluginServiceProvider.ts +39 -3
- package/src/service/plugin/SpawnInterfaceAdapter.ts +62 -0
- package/src/service/printer/DefaultPrinterService.ts +16 -10
- package/src/service/spawn/DefaultSpawnService.ts +119 -0
- package/src/service/spawn/SpawnServiceProvider.ts +32 -0
- package/src/terminal/NonTtyTerminal.ts +48 -0
- package/src/terminal/Terminal.ts +1 -0
- package/src/terminal/TtyStyler.ts +6 -2
- package/src/terminal/TtyTerminal.ts +4 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
+
export default class SpawnServiceProvider implements ServiceProvider {
|
|
5
|
+
#private;
|
|
6
|
+
readonly serviceId: string;
|
|
7
|
+
readonly servicePriority: number;
|
|
8
|
+
constructor(servicePriority: number);
|
|
9
|
+
getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo>;
|
|
10
|
+
initService(context: Context): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=SpawnServiceProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpawnServiceProvider.d.ts","sourceRoot":"","sources":["../../../../src/service/spawn/SpawnServiceProvider.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;AAMzE,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,eAAe;;IAClE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAoB;IAC9C,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;gBAGd,eAAe,EAAE,MAAM;IAInC,cAAc,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,CAAC,WAAW,CAAC;IAQ3D,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;CAMpD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PRINTER_SERVICE_ID, SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import { SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
|
+
import DefaultSpawnService from "./DefaultSpawnService.js";
|
|
4
|
+
export default class SpawnServiceProvider {
|
|
5
|
+
serviceId = SPAWN_SERVICE_ID;
|
|
6
|
+
servicePriority;
|
|
7
|
+
#spawnService;
|
|
8
|
+
constructor(servicePriority) {
|
|
9
|
+
this.servicePriority = servicePriority;
|
|
10
|
+
}
|
|
11
|
+
getServiceInfo(_cliConfig) {
|
|
12
|
+
this.#spawnService = new DefaultSpawnService();
|
|
13
|
+
return Promise.resolve({
|
|
14
|
+
service: this.#spawnService,
|
|
15
|
+
commands: [],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
initService(context) {
|
|
19
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID);
|
|
20
|
+
const shutdownService = context.getServiceById(SHUTDOWN_SERVICE_ID);
|
|
21
|
+
this.#spawnService.setDependencies(printerService, shutdownService);
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type Terminal from "./Terminal.ts";
|
|
2
|
+
export default class NonTtyTerminal implements Terminal {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(writeStream: NodeJS.WritableStream);
|
|
5
|
+
write(text: string): Promise<void>;
|
|
6
|
+
columns(): number;
|
|
7
|
+
rows(): number;
|
|
8
|
+
clearLine(): Promise<void>;
|
|
9
|
+
clearUpLines(_count: number): Promise<void>;
|
|
10
|
+
hideCursor(): Promise<void>;
|
|
11
|
+
showCursor(): Promise<void>;
|
|
12
|
+
isTty(): boolean;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=NonTtyTerminal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NonTtyTerminal.d.ts","sourceRoot":"","sources":["../../../src/terminal/NonTtyTerminal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C,MAAM,CAAC,OAAO,OAAO,cAAe,YAAW,QAAQ;;gBAIzC,WAAW,EAAE,MAAM,CAAC,cAAc;IAQ9C,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,OAAO,IAAI,MAAM;IAIjB,IAAI,IAAI,MAAM;IAId,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAI1B,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,KAAK,IAAI,OAAO;CAGjB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WriteStream } from "node:tty";
|
|
2
|
+
export default class NonTtyTerminal {
|
|
3
|
+
#writeStream;
|
|
4
|
+
#encoder = new TextEncoder();
|
|
5
|
+
constructor(writeStream) {
|
|
6
|
+
if (writeStream.isTTY === true) {
|
|
7
|
+
throw new Error("The provided WriteStream is a TTY");
|
|
8
|
+
}
|
|
9
|
+
this.#writeStream = writeStream;
|
|
10
|
+
}
|
|
11
|
+
write(text) {
|
|
12
|
+
this.#writeStream.write(this.#encoder.encode(text));
|
|
13
|
+
return Promise.resolve();
|
|
14
|
+
}
|
|
15
|
+
columns() {
|
|
16
|
+
return 80;
|
|
17
|
+
}
|
|
18
|
+
rows() {
|
|
19
|
+
return 24;
|
|
20
|
+
}
|
|
21
|
+
clearLine() {
|
|
22
|
+
return Promise.reject(new Error("clearLine() is not supported on a non-TTY terminal"));
|
|
23
|
+
}
|
|
24
|
+
clearUpLines(_count) {
|
|
25
|
+
return Promise.reject(new Error("clearUpLines() is not supported on a non-TTY terminal"));
|
|
26
|
+
}
|
|
27
|
+
hideCursor() {
|
|
28
|
+
return Promise.reject(new Error("hideCursor() is not supported on a non-TTY terminal"));
|
|
29
|
+
}
|
|
30
|
+
showCursor() {
|
|
31
|
+
return Promise.reject(new Error("showCursor() is not supported on a non-TTY terminal"));
|
|
32
|
+
}
|
|
33
|
+
isTty() {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Terminal.d.ts","sourceRoot":"","sources":["../../../src/terminal/Terminal.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,WAAW,QAAQ;IAC/B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,IAAI,MAAM,CAAC;IAClB,IAAI,IAAI,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"Terminal.d.ts","sourceRoot":"","sources":["../../../src/terminal/Terminal.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,WAAW,QAAQ;IAC/B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,IAAI,MAAM,CAAC;IAClB,IAAI,IAAI,MAAM,CAAC;IACf,KAAK,IAAI,OAAO,CAAC;CAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TtyStyler.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyStyler.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,MAAM,CAAC,OAAO,OAAO,SAAU,YAAW,MAAM;IAC9C,YAAY,EAAE,OAAO,CAAQ;IAC7B,iBAAiB,EAAE,OAAO,CAAC;IAE3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B
|
|
1
|
+
{"version":3,"file":"TtyStyler.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyStyler.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,MAAM,CAAC,OAAO,OAAO,SAAU,YAAW,MAAM;IAC9C,YAAY,EAAE,OAAO,CAAQ;IAC7B,iBAAiB,EAAE,OAAO,CAAC;IAE3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B;;;;;;;OAOG;gBACS,UAAU,GAAE,MAAU,EAAE,iBAAiB,GAAE,OAAc;IAKrE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAgBnD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;IAyB7D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAOhC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM;CAM7C"}
|
|
@@ -6,6 +6,7 @@ export default class TtyStyler {
|
|
|
6
6
|
/**
|
|
7
7
|
* The color level in use.
|
|
8
8
|
*
|
|
9
|
+
* 0 = no color
|
|
9
10
|
* 1 = 16 color
|
|
10
11
|
* 2 = 256 color
|
|
11
12
|
* 3 = 16 million color
|
|
@@ -15,7 +16,7 @@ export default class TtyStyler {
|
|
|
15
16
|
this.hyperlinksEnabled = hyperlinksEnabled;
|
|
16
17
|
}
|
|
17
18
|
colorText(text, colorValue) {
|
|
18
|
-
if (!this.colorEnabled) {
|
|
19
|
+
if (!this.colorEnabled || this.colorLevel === 0) {
|
|
19
20
|
return text;
|
|
20
21
|
}
|
|
21
22
|
if (this.colorLevel === 3) {
|
|
@@ -27,7 +28,7 @@ export default class TtyStyler {
|
|
|
27
28
|
return Bun.color(colorValue, "ansi-16") + text + FOREGROUND_COLOR_END;
|
|
28
29
|
}
|
|
29
30
|
backgroundColorText(text, colorValue) {
|
|
30
|
-
if (!this.colorEnabled) {
|
|
31
|
+
if (!this.colorEnabled || this.colorLevel === 0) {
|
|
31
32
|
return text;
|
|
32
33
|
}
|
|
33
34
|
if (this.colorLevel === 3) {
|
|
@@ -44,6 +45,9 @@ export default class TtyStyler {
|
|
|
44
45
|
BACKGROUND_COLOR_END);
|
|
45
46
|
}
|
|
46
47
|
italicText(text) {
|
|
48
|
+
if (!this.colorEnabled || this.colorLevel === 0) {
|
|
49
|
+
return text;
|
|
50
|
+
}
|
|
47
51
|
return ITALIC_START + text + ITALIC_END;
|
|
48
52
|
}
|
|
49
53
|
hyperlink(text, url) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TtyTerminal.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyTerminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAI1C,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,QAAQ;;gBAItC,cAAc,EAAE,WAAW;IAcvC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAM1B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,OAAO,IAAI,MAAM;IAQjB,IAAI,IAAI,MAAM;
|
|
1
|
+
{"version":3,"file":"TtyTerminal.d.ts","sourceRoot":"","sources":["../../../src/terminal/TtyTerminal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAI1C,MAAM,CAAC,OAAO,OAAO,WAAY,YAAW,QAAQ;;gBAItC,cAAc,EAAE,WAAW;IAcvC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAM1B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK1C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKlC,OAAO,IAAI,MAAM;IAQjB,IAAI,IAAI,MAAM;IAQd,KAAK,IAAI,OAAO;CAGjB"}
|
package/index.ts
CHANGED
|
@@ -78,6 +78,15 @@ export type { ShutdownService } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
78
78
|
export { default as DefaultShutdownService } from "./src/service/shutdown/DefaultShutdownService.ts";
|
|
79
79
|
export { default as ShutdownServiceProvider } from "./src/service/shutdown/ShutdownServiceProvider.ts";
|
|
80
80
|
|
|
81
|
+
export { SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
82
|
+
export type {
|
|
83
|
+
SpawnOptions,
|
|
84
|
+
SpawnResult,
|
|
85
|
+
SpawnService,
|
|
86
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
87
|
+
export { default as DefaultSpawnService } from "./src/service/spawn/DefaultSpawnService.ts";
|
|
88
|
+
export { default as SpawnServiceProvider } from "./src/service/spawn/SpawnServiceProvider.ts";
|
|
89
|
+
|
|
81
90
|
export { SYNTAX_HIGHLIGHTER_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
82
91
|
export type { SyntaxHighlighterService } from "@flowscripter/dynamic-cli-framework-api";
|
|
83
92
|
export type { ColorScheme } from "@flowscripter/dynamic-cli-framework-api";
|
|
@@ -192,7 +201,7 @@ export { default as TtyKeyReader } from "./src/terminal/TtyKeyReader.ts";
|
|
|
192
201
|
|
|
193
202
|
// Convenience functions
|
|
194
203
|
export {
|
|
204
|
+
launchDynamicPluginMultiCommandCLI,
|
|
195
205
|
launchMultiCommandCLI,
|
|
196
206
|
launchSingleCommandCLI,
|
|
197
|
-
launchDynamicPluginMultiCommandCLI,
|
|
198
207
|
} from "./src/launcher.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowscripter/dynamic-cli-framework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.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",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"build": "tsc -p tsconfig.build.json"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@flowscripter/dynamic-cli-framework-api": "^1.
|
|
52
|
-
"@flowscripter/dynamic-plugin-framework": "1.
|
|
51
|
+
"@flowscripter/dynamic-cli-framework-api": "^1.1.1",
|
|
52
|
+
"@flowscripter/dynamic-plugin-framework": "1.11.0",
|
|
53
53
|
"emphasize": "^7.0.0",
|
|
54
54
|
"fastest-levenshtein": "^1.0.16",
|
|
55
55
|
"figlet": "^1.11.0",
|
package/src/cli/BaseCLI.ts
CHANGED
|
@@ -51,6 +51,7 @@ import ArgumentPrompterServiceProvider from "../service/argumentPrompter/Argumen
|
|
|
51
51
|
import DefaultCompletionService from "../service/completion/DefaultCompletionService.ts";
|
|
52
52
|
import CompletionServiceProvider from "../service/completion/CompletionServiceProvider.ts";
|
|
53
53
|
import ImagePrinterServiceProvider from "../service/imagePrinter/ImagePrinterServiceProvider.ts";
|
|
54
|
+
import SpawnServiceProvider from "../service/spawn/SpawnServiceProvider.ts";
|
|
54
55
|
const logger = getLogger("BaseCLI");
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -69,6 +70,13 @@ const logger = getLogger("BaseCLI");
|
|
|
69
70
|
* Optionally enabled via {@link BaseCLIFeatureOptions}:
|
|
70
71
|
*
|
|
71
72
|
* * {@link ImagePrinterServiceProvider}
|
|
73
|
+
* * {@link SpawnServiceProvider}
|
|
74
|
+
*
|
|
75
|
+
* `keyReader` is optional. If omitted, or if the stderr {@link Terminal} is not a TTY, prompting is
|
|
76
|
+
* unavailable: {@link PrompterServiceProvider} (and {@link ArgumentPrompterServiceProvider}, if
|
|
77
|
+
* enabled) are simply not registered. If `promptingEnabled` is `true` in that situation, {@link run}
|
|
78
|
+
* throws rather than failing later inside a prompt. Similarly {@link ImagePrinterServiceProvider} is
|
|
79
|
+
* only registered if the stdout {@link Terminal} is a TTY.
|
|
72
80
|
*
|
|
73
81
|
* By default the following commands are added:
|
|
74
82
|
*
|
|
@@ -80,7 +88,7 @@ const logger = getLogger("BaseCLI");
|
|
|
80
88
|
export default class BaseCLI implements CLI {
|
|
81
89
|
readonly #cliConfig: CLIConfig;
|
|
82
90
|
readonly #options: Required<BaseCLIFeatureOptions>;
|
|
83
|
-
readonly #keyReader: KeyReader;
|
|
91
|
+
readonly #keyReader: KeyReader | undefined;
|
|
84
92
|
readonly #stderrTerminal: Terminal;
|
|
85
93
|
readonly #addedNonModifierCommands: Array<Command>;
|
|
86
94
|
readonly #serviceProviderRegistry: DefaultServiceProviderRegistry;
|
|
@@ -98,7 +106,7 @@ export default class BaseCLI implements CLI {
|
|
|
98
106
|
stdoutTerminal: Terminal,
|
|
99
107
|
stderrTerminal: Terminal,
|
|
100
108
|
styler: Styler,
|
|
101
|
-
keyReader: KeyReader,
|
|
109
|
+
keyReader: KeyReader | undefined,
|
|
102
110
|
options?: BaseCLIFeatureOptions,
|
|
103
111
|
) {
|
|
104
112
|
if (cliConfig.name.length === 0) {
|
|
@@ -124,6 +132,7 @@ export default class BaseCLI implements CLI {
|
|
|
124
132
|
argumentPrompterServiceEnabled: false,
|
|
125
133
|
completionServiceEnabled: false,
|
|
126
134
|
imagePrinterServiceEnabled: false,
|
|
135
|
+
spawnServiceEnabled: false,
|
|
127
136
|
validateAllCommands: false,
|
|
128
137
|
promptingEnabled: true,
|
|
129
138
|
...options,
|
|
@@ -210,22 +219,54 @@ export default class BaseCLI implements CLI {
|
|
|
210
219
|
this.addServiceProvider(new PrinterServiceProvider(80, this.#printerService));
|
|
211
220
|
this.addServiceProvider(new TableGeneratorServiceProvider(70));
|
|
212
221
|
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
prompterService
|
|
220
|
-
|
|
222
|
+
const canPrompt = this.#keyReader !== undefined && this.#stderrTerminal.isTty();
|
|
223
|
+
|
|
224
|
+
if (this.#options.promptingEnabled && !canPrompt) {
|
|
225
|
+
throw new Error("promptingEnabled requires a keyReader and a TTY stderr terminal");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let prompterService: DefaultPrompterService | undefined;
|
|
229
|
+
if (canPrompt) {
|
|
230
|
+
prompterService = new DefaultPrompterService(
|
|
231
|
+
DEFAULT_PROMPTER_CONFIG,
|
|
232
|
+
this.#stderrTerminal,
|
|
233
|
+
this.#keyReader!,
|
|
234
|
+
this.#printerService,
|
|
235
|
+
);
|
|
236
|
+
prompterService.promptEnabled = this.#options.promptingEnabled;
|
|
237
|
+
this.addServiceProvider(new PrompterServiceProvider(75, prompterService));
|
|
238
|
+
} else {
|
|
239
|
+
logger.debug(
|
|
240
|
+
() =>
|
|
241
|
+
`Skipping PrompterServiceProvider: keyReader present: ${
|
|
242
|
+
this.#keyReader !== undefined
|
|
243
|
+
}, stderr isTty: ${this.#stderrTerminal.isTty()}`,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
221
246
|
|
|
222
247
|
if (this.#options.argumentPrompterServiceEnabled) {
|
|
223
|
-
|
|
224
|
-
|
|
248
|
+
if (prompterService !== undefined) {
|
|
249
|
+
const argumentPrompterService = new DefaultArgumentPrompterService(prompterService);
|
|
250
|
+
this.addServiceProvider(new ArgumentPrompterServiceProvider(65, argumentPrompterService));
|
|
251
|
+
} else {
|
|
252
|
+
logger.debug(
|
|
253
|
+
"Skipping ArgumentPrompterServiceProvider: argumentPrompterServiceEnabled is true but prompting is unavailable",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
225
256
|
}
|
|
226
257
|
|
|
227
258
|
if (this.#options.imagePrinterServiceEnabled) {
|
|
228
|
-
|
|
259
|
+
if (this.#stdoutTerminal.isTty()) {
|
|
260
|
+
this.addServiceProvider(new ImagePrinterServiceProvider(55, this.#stdoutTerminal));
|
|
261
|
+
} else {
|
|
262
|
+
logger.debug(
|
|
263
|
+
"Skipping ImagePrinterServiceProvider: imagePrinterServiceEnabled is true but stdout is not a TTY",
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (this.#options.spawnServiceEnabled) {
|
|
269
|
+
this.addServiceProvider(new SpawnServiceProvider(58));
|
|
229
270
|
}
|
|
230
271
|
|
|
231
272
|
if (this.#options.completionServiceEnabled) {
|
|
@@ -6,6 +6,7 @@ export default interface BaseCLIFeatureOptions {
|
|
|
6
6
|
readonly argumentPrompterServiceEnabled?: boolean;
|
|
7
7
|
readonly completionServiceEnabled?: boolean;
|
|
8
8
|
readonly imagePrinterServiceEnabled?: boolean;
|
|
9
|
+
readonly spawnServiceEnabled?: boolean;
|
|
9
10
|
readonly validateAllCommands?: boolean;
|
|
10
11
|
readonly promptingEnabled?: boolean;
|
|
11
12
|
}
|
|
@@ -5,30 +5,74 @@ import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
|
5
5
|
import type BaseCLIFeatureOptions from "./BaseCLIFeatureOptions.ts";
|
|
6
6
|
import { Writable } from "node:stream";
|
|
7
7
|
import TtyTerminal from "../terminal/TtyTerminal.ts";
|
|
8
|
+
import NonTtyTerminal from "../terminal/NonTtyTerminal.ts";
|
|
8
9
|
import TtyKeyReader from "../terminal/TtyKeyReader.ts";
|
|
9
10
|
import TtyStyler from "../terminal/TtyStyler.ts";
|
|
10
11
|
import supportsColor from "supports-color";
|
|
11
12
|
import supportsHyperlinks from "../terminal/supportsHyperlinks.ts";
|
|
13
|
+
import getLogger from "../util/logger.ts";
|
|
14
|
+
|
|
15
|
+
const logger = getLogger("DefaultRuntimeCLI");
|
|
12
16
|
|
|
13
17
|
/**
|
|
14
18
|
* Default Bun implementation of a {@link CLI} using `process.stdout`, `process.stderr` and `process.argv`.
|
|
19
|
+
*
|
|
20
|
+
* Degrades gracefully when `stdin`, `stdout` or `stderr` are not TTYs (e.g. piped or redirected):
|
|
21
|
+
* color and hyperlinks are disabled, prompting is disabled, and non-TTY streams are wrapped in a
|
|
22
|
+
* {@link NonTtyTerminal} rather than the usual {@link TtyTerminal}.
|
|
15
23
|
*/
|
|
16
24
|
export default class DefaultRuntimeCLI extends BaseCLI {
|
|
17
25
|
constructor(cliConfig: CLIConfig, options?: BaseCLIFeatureOptions) {
|
|
26
|
+
const stdinIsTty = process.stdin.isTTY === true;
|
|
27
|
+
const stdoutIsTty = process.stdout.isTTY === true;
|
|
28
|
+
const stderrIsTty = process.stderr.isTTY === true;
|
|
29
|
+
|
|
30
|
+
logger.debug(
|
|
31
|
+
() =>
|
|
32
|
+
`stdin isTTY: ${stdinIsTty}, stdout isTTY: ${stdoutIsTty}, stderr isTTY: ${stderrIsTty}`,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const stdoutTerminal = stdoutIsTty
|
|
36
|
+
? new TtyTerminal(process.stdout)
|
|
37
|
+
: new NonTtyTerminal(process.stdout);
|
|
38
|
+
const stderrTerminal = stderrIsTty
|
|
39
|
+
? new TtyTerminal(process.stderr)
|
|
40
|
+
: new NonTtyTerminal(process.stderr);
|
|
41
|
+
|
|
42
|
+
logger.debug(
|
|
43
|
+
() =>
|
|
44
|
+
`stdout terminal: ${
|
|
45
|
+
stdoutIsTty ? "TtyTerminal" : "NonTtyTerminal"
|
|
46
|
+
}, stderr terminal: ${stderrIsTty ? "TtyTerminal" : "NonTtyTerminal"}`,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const colorLevel = stderrIsTty
|
|
50
|
+
? supportsColor.stderr === false
|
|
51
|
+
? 1
|
|
52
|
+
: supportsColor.stderr.level
|
|
53
|
+
: 0;
|
|
54
|
+
|
|
55
|
+
logger.debug(() => `color level: ${colorLevel}`);
|
|
56
|
+
|
|
57
|
+
const keyReader = stdinIsTty ? new TtyKeyReader(process.stdin) : undefined;
|
|
58
|
+
|
|
59
|
+
logger.debug(() => `keyReader constructed: ${keyReader !== undefined}`);
|
|
60
|
+
|
|
61
|
+
const promptingEnabled = stdinIsTty && stderrIsTty;
|
|
62
|
+
|
|
63
|
+
logger.debug(() => `promptingEnabled default: ${promptingEnabled}`);
|
|
64
|
+
|
|
18
65
|
super(
|
|
19
66
|
cliConfig,
|
|
20
67
|
Writable.toWeb(process.stdout),
|
|
21
68
|
Writable.toWeb(process.stderr),
|
|
22
69
|
supportsColor.stdout !== false,
|
|
23
70
|
supportsColor.stderr !== false,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
new TtyStyler(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
),
|
|
30
|
-
new TtyKeyReader(process.stdin),
|
|
31
|
-
{ promptingEnabled: process.stdin.isTTY === true, ...options },
|
|
71
|
+
stdoutTerminal,
|
|
72
|
+
stderrTerminal,
|
|
73
|
+
new TtyStyler(colorLevel, supportsHyperlinks(process.stderr)),
|
|
74
|
+
keyReader,
|
|
75
|
+
{ promptingEnabled, ...options },
|
|
32
76
|
);
|
|
33
77
|
}
|
|
34
78
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PluginService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
2
|
import type {
|
|
3
3
|
MarketplacePluginManager,
|
|
4
|
-
VersionedPluginDescriptor,
|
|
5
4
|
SearchQuery,
|
|
5
|
+
VersionedPluginDescriptor,
|
|
6
6
|
} from "@flowscripter/dynamic-plugin-framework";
|
|
7
7
|
|
|
8
8
|
export default class DefaultPluginService implements PluginService {
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
import type { ServiceInfo, ServiceProvider } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
2
|
import type { Context } from "@flowscripter/dynamic-cli-framework-api";
|
|
3
3
|
import type { CLIConfig } from "@flowscripter/dynamic-cli-framework-api";
|
|
4
|
-
import type {
|
|
4
|
+
import type { PrinterService, SpawnService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
import { PRINTER_SERVICE_ID, SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import type {
|
|
7
|
+
MarketplacePluginManager,
|
|
8
|
+
SpawnCapable,
|
|
9
|
+
} from "@flowscripter/dynamic-plugin-framework";
|
|
5
10
|
import { PLUGIN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
11
|
import DefaultPluginService from "./DefaultPluginService.ts";
|
|
7
12
|
import { PluginGroupCommand } from "./command/PluginGroupCommand.ts";
|
|
13
|
+
import SpawnInterfaceAdapter, {
|
|
14
|
+
type SpawnInterfaceAdapterOptions,
|
|
15
|
+
} from "./SpawnInterfaceAdapter.ts";
|
|
16
|
+
import getLogger from "../../util/logger.ts";
|
|
17
|
+
|
|
18
|
+
const logger = getLogger("DefaultPluginServiceProvider");
|
|
8
19
|
|
|
9
20
|
export default class DefaultPluginServiceProvider implements ServiceProvider {
|
|
10
21
|
readonly serviceId = PLUGIN_SERVICE_ID;
|
|
11
22
|
readonly servicePriority: number;
|
|
12
23
|
readonly #pluginManager: MarketplacePluginManager;
|
|
24
|
+
readonly #spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions;
|
|
13
25
|
#pluginService: DefaultPluginService | undefined;
|
|
14
26
|
|
|
15
|
-
constructor(
|
|
27
|
+
constructor(
|
|
28
|
+
pluginManager: MarketplacePluginManager,
|
|
29
|
+
servicePriority = 50,
|
|
30
|
+
spawnInterfaceAdapterOptions: SpawnInterfaceAdapterOptions = {},
|
|
31
|
+
) {
|
|
16
32
|
this.#pluginManager = pluginManager;
|
|
17
33
|
this.servicePriority = servicePriority;
|
|
34
|
+
this.#spawnInterfaceAdapterOptions = spawnInterfaceAdapterOptions;
|
|
18
35
|
}
|
|
19
36
|
|
|
20
37
|
async getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
@@ -25,5 +42,24 @@ export default class DefaultPluginServiceProvider implements ServiceProvider {
|
|
|
25
42
|
};
|
|
26
43
|
}
|
|
27
44
|
|
|
28
|
-
|
|
45
|
+
initService(context: Context): Promise<void> {
|
|
46
|
+
if (!context.doesServiceExist(SPAWN_SERVICE_ID)) {
|
|
47
|
+
logger.debug("SpawnService not available, plugin manager will spawn processes directly");
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
}
|
|
50
|
+
if (!("setSpawn" in this.#pluginManager)) {
|
|
51
|
+
logger.debug("Plugin manager does not support SpawnCapable, skipping spawn injection");
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const spawnService = context.getServiceById(SPAWN_SERVICE_ID) as SpawnService;
|
|
56
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
57
|
+
const adapter = new SpawnInterfaceAdapter(
|
|
58
|
+
spawnService,
|
|
59
|
+
printerService,
|
|
60
|
+
this.#spawnInterfaceAdapterOptions,
|
|
61
|
+
);
|
|
62
|
+
(this.#pluginManager as unknown as SpawnCapable).setSpawn(adapter);
|
|
63
|
+
return Promise.resolve();
|
|
64
|
+
}
|
|
29
65
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { PrinterService, SpawnService } from "@flowscripter/dynamic-cli-framework-api";
|
|
2
|
+
import type { SpawnInterface, SpawnResult } from "@flowscripter/dynamic-plugin-framework";
|
|
3
|
+
|
|
4
|
+
export interface SpawnInterfaceAdapterOptions {
|
|
5
|
+
quoteColor?: string;
|
|
6
|
+
markMinimumDisplayTimeMs?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Adapts a {@link SpawnService} to dynamic-plugin-framework's {@link SpawnInterface}, wrapping
|
|
11
|
+
* spawned process output in a quoted, marked block via {@link PrinterService} that is cleared
|
|
12
|
+
* again once the process exits (subject to {@link SpawnInterfaceAdapterOptions.markMinimumDisplayTimeMs}).
|
|
13
|
+
*/
|
|
14
|
+
export default class SpawnInterfaceAdapter implements SpawnInterface {
|
|
15
|
+
readonly #spawnService: SpawnService;
|
|
16
|
+
readonly #printerService: PrinterService;
|
|
17
|
+
readonly #quoteColor: string | undefined;
|
|
18
|
+
readonly #markMinimumDisplayTimeMs: number;
|
|
19
|
+
|
|
20
|
+
public constructor(
|
|
21
|
+
spawnService: SpawnService,
|
|
22
|
+
printerService: PrinterService,
|
|
23
|
+
options: SpawnInterfaceAdapterOptions = {},
|
|
24
|
+
) {
|
|
25
|
+
this.#spawnService = spawnService;
|
|
26
|
+
this.#printerService = printerService;
|
|
27
|
+
this.#quoteColor = options.quoteColor;
|
|
28
|
+
this.#markMinimumDisplayTimeMs = options.markMinimumDisplayTimeMs ?? 1000;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async spawn(
|
|
32
|
+
command: ReadonlyArray<string>,
|
|
33
|
+
options: { cwd: string },
|
|
34
|
+
): Promise<SpawnResult> {
|
|
35
|
+
this.#printerService.startQuote(this.#quoteColor);
|
|
36
|
+
this.#printerService.startMark();
|
|
37
|
+
|
|
38
|
+
// onOutput is synchronous and may be called concurrently for stdout/stderr lines, but
|
|
39
|
+
// printerService.info() is async and must not be invoked concurrently with itself - queue
|
|
40
|
+
// writes so they're applied one at a time, in call order.
|
|
41
|
+
let writeQueue: Promise<void> = Promise.resolve();
|
|
42
|
+
const onOutput = (line: string): void => {
|
|
43
|
+
writeQueue = writeQueue.then(() => this.#printerService.info(`${line}\n`));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const result = await this.#spawnService.spawn(command, {
|
|
47
|
+
cwd: options.cwd,
|
|
48
|
+
stdio: "wrapped",
|
|
49
|
+
onOutput,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await writeQueue;
|
|
53
|
+
|
|
54
|
+
this.#printerService.endQuote();
|
|
55
|
+
this.#printerService.endMark();
|
|
56
|
+
await this.#printerService.clearMarked(this.#markMinimumDisplayTimeMs);
|
|
57
|
+
|
|
58
|
+
return result.ok
|
|
59
|
+
? { ok: true, exitCode: result.exitCode }
|
|
60
|
+
: { ok: false, exitCode: result.exitCode, error: result.error };
|
|
61
|
+
}
|
|
62
|
+
}
|