mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-10-23 22:00:35 +08:00
85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
import type { ExecaChildProcess, Options } from "execa";
|
|
|
|
import * as Stream from "node:stream";
|
|
import { StringDecoder } from "string_decoder";
|
|
import { execa } from "execa";
|
|
|
|
import { logPrefix } from "./logPrefix.js";
|
|
|
|
// Processes that should be SIGKILLed when the Node process shutsdown.
|
|
// We add all proxy and node instances that we spawn to this list.
|
|
const processes: ExecaChildProcess[] = [];
|
|
|
|
process.on("exit", killAllProcesses);
|
|
process.on("SIGINT", killAllProcesses);
|
|
process.on("SIGTERM", killAllProcesses);
|
|
|
|
// Kill all processes with SIGKILL
|
|
export function killAllProcesses(): void {
|
|
for (const process of processes) {
|
|
if (process.exitCode === null) {
|
|
process.kill("SIGKILL");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Spawn a process with `execa` and register it.
|
|
//
|
|
// The process will be killed by `killAllProcesses`.
|
|
export function spawn(
|
|
bin: string,
|
|
args: string[],
|
|
options?: Options,
|
|
): ExecaChildProcess {
|
|
const child = execa(bin, args, options);
|
|
processes.push(child);
|
|
return child;
|
|
}
|
|
|
|
// Forwards piped `stdout` and `stderr` of a child process to `output`
|
|
// and prefixes it with the given label. The prefix is colored.
|
|
export function prefixOutput(
|
|
childProcess: ExecaChildProcess,
|
|
label: string,
|
|
output: Stream.Writable,
|
|
): ExecaChildProcess {
|
|
const pref = logPrefix(label);
|
|
if (childProcess.stdout) {
|
|
const stdoutPrefix = new LinePrefix(pref);
|
|
childProcess.stdout.pipe(stdoutPrefix).pipe(output, { end: false });
|
|
}
|
|
if (childProcess.stderr) {
|
|
const stderrPrefix = new LinePrefix(pref);
|
|
childProcess.stderr.pipe(stderrPrefix).pipe(output, { end: false });
|
|
}
|
|
|
|
return childProcess;
|
|
}
|
|
|
|
// A transform that prefixes each line from the source with the given
|
|
// string and pushes it to the sink.
|
|
class LinePrefix extends Stream.Transform {
|
|
private buffer: string = "";
|
|
private stringDecoder = new StringDecoder();
|
|
|
|
public constructor(private prefix: string) {
|
|
super();
|
|
}
|
|
|
|
public _transform(data: Buffer, _encoding: string, next: () => void): void {
|
|
const str = this.buffer + this.stringDecoder.write(data);
|
|
const lines = str.split(/\r?\n/);
|
|
this.buffer = lines.pop() || "";
|
|
lines.forEach(line => this.push(`${this.prefix}${line}\n`));
|
|
next();
|
|
}
|
|
|
|
public _flush(done: () => void): void {
|
|
const rest = `${this.buffer}${this.stringDecoder.end()}`;
|
|
if (rest) {
|
|
this.push(`${this.prefix}${rest}\n`);
|
|
}
|
|
done();
|
|
}
|
|
}
|