@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
|
@@ -13,6 +13,7 @@ import { Color } from "./terminal/Color.ts";
|
|
|
13
13
|
import { getDarkModeTheme, getLightModeTheme } from "./terminal/Theme.ts";
|
|
14
14
|
import { WritableStream } from "node:stream/web";
|
|
15
15
|
import type Styler from "../../terminal/Styler.ts";
|
|
16
|
+
|
|
16
17
|
export default class DefaultPrinterService implements PrinterService {
|
|
17
18
|
readonly stdoutWritable: WritableStream;
|
|
18
19
|
readonly stderrWritable: WritableStream;
|
|
@@ -30,18 +31,16 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
30
31
|
#quote: Quote;
|
|
31
32
|
#markedLineCount: number | undefined;
|
|
32
33
|
#markEndedAt: number | undefined;
|
|
33
|
-
#markedTerminal: Terminal | undefined;
|
|
34
34
|
|
|
35
35
|
#countLines(message: string): number {
|
|
36
36
|
return message.split("\n").length - (message.endsWith("\n") ? 1 : 0);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
#recordMarkedWrite(
|
|
39
|
+
#recordMarkedWrite(message: string): void {
|
|
40
40
|
if (this.#markedLineCount === undefined) {
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
this.#markedLineCount += this.#countLines(message);
|
|
44
|
-
this.#markedTerminal = terminal;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
async #log(level: number, message: string, icon?: Icon): Promise<void> {
|
|
@@ -51,7 +50,7 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
51
50
|
await this.#spinner.pause();
|
|
52
51
|
await this.#progress.pause();
|
|
53
52
|
const composed = `${icon !== undefined ? `${this.#iconDefinitions[icon]} ` : ""}${message}`;
|
|
54
|
-
this.#recordMarkedWrite(
|
|
53
|
+
this.#recordMarkedWrite(composed);
|
|
55
54
|
await this.#stderrTerminal.write(this.#quote.prefixLines(composed));
|
|
56
55
|
this.#spinner.resume();
|
|
57
56
|
this.#progress.resume();
|
|
@@ -277,8 +276,7 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
277
276
|
const composed = `${
|
|
278
277
|
icon !== undefined ? `${this.#iconDefinitions[icon]} ` : ""
|
|
279
278
|
}${colorMessage}`;
|
|
280
|
-
this.#
|
|
281
|
-
const encoded = this.#encoder.encode(this.#quote.prefixLines(composed));
|
|
279
|
+
const encoded = this.#encoder.encode(composed);
|
|
282
280
|
|
|
283
281
|
await writer.ready;
|
|
284
282
|
await writer.write(encoded);
|
|
@@ -318,16 +316,18 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
318
316
|
if (this.#markedLineCount === undefined || this.#markEndedAt === undefined) {
|
|
319
317
|
throw new Error("clearMarked() called without a preceding endMark()");
|
|
320
318
|
}
|
|
319
|
+
if (!this.#stderrTerminal.isTty()) {
|
|
320
|
+
this.#markedLineCount = undefined;
|
|
321
|
+
this.#markEndedAt = undefined;
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
321
324
|
const remaining = minimumDisplayTimeMs - (Date.now() - this.#markEndedAt);
|
|
322
325
|
if (remaining > 0) {
|
|
323
326
|
await new Promise((resolve) => setTimeout(resolve, remaining));
|
|
324
327
|
}
|
|
325
|
-
|
|
326
|
-
await this.#markedTerminal.clearUpLines(this.#markedLineCount);
|
|
327
|
-
}
|
|
328
|
+
await this.#stderrTerminal.clearUpLines(this.#markedLineCount);
|
|
328
329
|
this.#markedLineCount = undefined;
|
|
329
330
|
this.#markEndedAt = undefined;
|
|
330
|
-
this.#markedTerminal = undefined;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
public async setLevel(level: Level): Promise<void> {
|
|
@@ -343,6 +343,9 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
public async showSpinner(message?: string, style?: SpinnerStyle): Promise<void> {
|
|
346
|
+
if (!this.#stderrTerminal.isTty()) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
346
349
|
await this.#progress.hideAll();
|
|
347
350
|
if (this.#threshold > Level.INFO) {
|
|
348
351
|
return;
|
|
@@ -364,6 +367,9 @@ export default class DefaultPrinterService implements PrinterService {
|
|
|
364
367
|
current = 0,
|
|
365
368
|
style?: ProgressStyle,
|
|
366
369
|
): Promise<number> {
|
|
370
|
+
if (!this.#stderrTerminal.isTty()) {
|
|
371
|
+
return -1;
|
|
372
|
+
}
|
|
367
373
|
await this.#spinner.hide();
|
|
368
374
|
if (this.#threshold > Level.INFO) {
|
|
369
375
|
return -1;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PrinterService,
|
|
3
|
+
ShutdownService,
|
|
4
|
+
SpawnOptions,
|
|
5
|
+
SpawnResult,
|
|
6
|
+
SpawnService,
|
|
7
|
+
} from "@flowscripter/dynamic-cli-framework-api";
|
|
8
|
+
import getLogger from "../../util/logger.ts";
|
|
9
|
+
|
|
10
|
+
const logger = getLogger("DefaultSpawnService");
|
|
11
|
+
|
|
12
|
+
const SHUTDOWN_GRACE_PERIOD_MS = 5000;
|
|
13
|
+
|
|
14
|
+
export default class DefaultSpawnService implements SpawnService {
|
|
15
|
+
#printerService: PrinterService | undefined;
|
|
16
|
+
#shutdownService: ShutdownService | undefined;
|
|
17
|
+
|
|
18
|
+
public setDependencies(printerService: PrinterService, shutdownService: ShutdownService): void {
|
|
19
|
+
this.#printerService = printerService;
|
|
20
|
+
this.#shutdownService = shutdownService;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async #pipeLines(
|
|
24
|
+
stream: ReadableStream<Uint8Array> | number | undefined,
|
|
25
|
+
streamName: "stdout" | "stderr",
|
|
26
|
+
onOutput: (line: string, stream: "stdout" | "stderr") => void,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
if (typeof stream !== "object" || stream === null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const decoder = new TextDecoder();
|
|
32
|
+
const reader = stream.getReader();
|
|
33
|
+
let buffer = "";
|
|
34
|
+
for (;;) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
buffer += decoder.decode(value, { stream: true });
|
|
40
|
+
let newlineIndex = buffer.indexOf("\n");
|
|
41
|
+
while (newlineIndex !== -1) {
|
|
42
|
+
onOutput(buffer.slice(0, newlineIndex), streamName);
|
|
43
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
44
|
+
newlineIndex = buffer.indexOf("\n");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (buffer.length > 0) {
|
|
48
|
+
onOutput(buffer, streamName);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async spawn(
|
|
53
|
+
command: ReadonlyArray<string>,
|
|
54
|
+
options: SpawnOptions = {},
|
|
55
|
+
): Promise<SpawnResult> {
|
|
56
|
+
if (this.#printerService === undefined || this.#shutdownService === undefined) {
|
|
57
|
+
throw new Error("DefaultSpawnService.spawn() called before setDependencies()");
|
|
58
|
+
}
|
|
59
|
+
const printerService = this.#printerService;
|
|
60
|
+
const shutdownService = this.#shutdownService;
|
|
61
|
+
const stdio = options.stdio ?? "inherit";
|
|
62
|
+
const longRunning = options.longRunning ?? true;
|
|
63
|
+
|
|
64
|
+
if (stdio === "inherit") {
|
|
65
|
+
await printerService.hideSpinner();
|
|
66
|
+
await printerService.hideAllProgressBars();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let proc;
|
|
70
|
+
try {
|
|
71
|
+
proc = Bun.spawn([...command], {
|
|
72
|
+
cwd: options.cwd,
|
|
73
|
+
stdout: stdio === "wrapped" ? "pipe" : "inherit",
|
|
74
|
+
stderr: stdio === "wrapped" ? "pipe" : "inherit",
|
|
75
|
+
});
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logger.debug(() => `Failed to launch command '${command.join(" ")}': ${error}`);
|
|
78
|
+
return { ok: false, error: error as Error };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let settled = false;
|
|
82
|
+
|
|
83
|
+
if (longRunning) {
|
|
84
|
+
shutdownService.enterLongRunningMode();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
shutdownService.addShutdownListener(async () => {
|
|
88
|
+
if (settled) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
logger.debug(() => `Sending SIGTERM to spawned command '${command.join(" ")}'`);
|
|
92
|
+
proc.kill("SIGTERM");
|
|
93
|
+
await Promise.race([
|
|
94
|
+
proc.exited.then(() => {}),
|
|
95
|
+
new Promise<void>((resolve) => setTimeout(resolve, SHUTDOWN_GRACE_PERIOD_MS)),
|
|
96
|
+
]);
|
|
97
|
+
if (!settled) {
|
|
98
|
+
logger.debug(() => `Sending SIGKILL to spawned command '${command.join(" ")}'`);
|
|
99
|
+
proc.kill("SIGKILL");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (stdio === "wrapped" && options.onOutput) {
|
|
104
|
+
const onOutput = options.onOutput;
|
|
105
|
+
void this.#pipeLines(proc.stdout, "stdout", onOutput);
|
|
106
|
+
void this.#pipeLines(proc.stderr, "stderr", onOutput);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const exitCode = await proc.exited;
|
|
111
|
+
settled = true;
|
|
112
|
+
return exitCode === 0 ? { ok: true, exitCode } : { ok: false, exitCode };
|
|
113
|
+
} finally {
|
|
114
|
+
if (longRunning) {
|
|
115
|
+
shutdownService.leaveLongRunningMode();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
import type { PrinterService, ShutdownService } from "@flowscripter/dynamic-cli-framework-api";
|
|
5
|
+
import { PRINTER_SERVICE_ID, SHUTDOWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
6
|
+
import { SPAWN_SERVICE_ID } from "@flowscripter/dynamic-cli-framework-api";
|
|
7
|
+
import DefaultSpawnService from "./DefaultSpawnService.ts";
|
|
8
|
+
|
|
9
|
+
export default class SpawnServiceProvider implements ServiceProvider {
|
|
10
|
+
readonly serviceId: string = SPAWN_SERVICE_ID;
|
|
11
|
+
readonly servicePriority: number;
|
|
12
|
+
#spawnService: DefaultSpawnService | undefined;
|
|
13
|
+
|
|
14
|
+
public constructor(servicePriority: number) {
|
|
15
|
+
this.servicePriority = servicePriority;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public getServiceInfo(_cliConfig: CLIConfig): Promise<ServiceInfo> {
|
|
19
|
+
this.#spawnService = new DefaultSpawnService();
|
|
20
|
+
return Promise.resolve({
|
|
21
|
+
service: this.#spawnService,
|
|
22
|
+
commands: [],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public initService(context: Context): Promise<void> {
|
|
27
|
+
const printerService = context.getServiceById(PRINTER_SERVICE_ID) as PrinterService;
|
|
28
|
+
const shutdownService = context.getServiceById(SHUTDOWN_SERVICE_ID) as ShutdownService;
|
|
29
|
+
this.#spawnService!.setDependencies(printerService, shutdownService);
|
|
30
|
+
return Promise.resolve();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { WriteStream } from "node:tty";
|
|
2
|
+
import type Terminal from "./Terminal.ts";
|
|
3
|
+
|
|
4
|
+
export default class NonTtyTerminal implements Terminal {
|
|
5
|
+
readonly #writeStream: NodeJS.WritableStream;
|
|
6
|
+
readonly #encoder = new TextEncoder();
|
|
7
|
+
|
|
8
|
+
constructor(writeStream: NodeJS.WritableStream) {
|
|
9
|
+
if ((writeStream as WriteStream).isTTY === true) {
|
|
10
|
+
throw new Error("The provided WriteStream is a TTY");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.#writeStream = writeStream;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
write(text: string): Promise<void> {
|
|
17
|
+
this.#writeStream.write(this.#encoder.encode(text));
|
|
18
|
+
return Promise.resolve();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
columns(): number {
|
|
22
|
+
return 80;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
rows(): number {
|
|
26
|
+
return 24;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
clearLine(): Promise<void> {
|
|
30
|
+
return Promise.reject(new Error("clearLine() is not supported on a non-TTY terminal"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
clearUpLines(_count: number): Promise<void> {
|
|
34
|
+
return Promise.reject(new Error("clearUpLines() is not supported on a non-TTY terminal"));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
hideCursor(): Promise<void> {
|
|
38
|
+
return Promise.reject(new Error("hideCursor() is not supported on a non-TTY terminal"));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
showCursor(): Promise<void> {
|
|
42
|
+
return Promise.reject(new Error("showCursor() is not supported on a non-TTY terminal"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
isTty(): boolean {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/terminal/Terminal.ts
CHANGED
|
@@ -17,6 +17,7 @@ export default class TtyStyler implements Styler {
|
|
|
17
17
|
/**
|
|
18
18
|
* The color level in use.
|
|
19
19
|
*
|
|
20
|
+
* 0 = no color
|
|
20
21
|
* 1 = 16 color
|
|
21
22
|
* 2 = 256 color
|
|
22
23
|
* 3 = 16 million color
|
|
@@ -27,7 +28,7 @@ export default class TtyStyler implements Styler {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
colorText(text: string, colorValue: number): string {
|
|
30
|
-
if (!this.colorEnabled) {
|
|
31
|
+
if (!this.colorEnabled || this.colorLevel === 0) {
|
|
31
32
|
return text;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -43,7 +44,7 @@ export default class TtyStyler implements Styler {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
backgroundColorText(text: string, colorValue: number): string {
|
|
46
|
-
if (!this.colorEnabled) {
|
|
47
|
+
if (!this.colorEnabled || this.colorLevel === 0) {
|
|
47
48
|
return text;
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -68,6 +69,9 @@ export default class TtyStyler implements Styler {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
italicText(text: string): string {
|
|
72
|
+
if (!this.colorEnabled || this.colorLevel === 0) {
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
71
75
|
return ITALIC_START + text + ITALIC_END;
|
|
72
76
|
}
|
|
73
77
|
|