66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
/**
|
|
* Background bash job — spawns a `bash -c` subprocess and tracks its life.
|
|
* Mirrors `apps/infrastructure/src/bgbash/job.rs`.
|
|
*/
|
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
import { randomUUID } from "node:crypto";
|
|
|
|
/** A handle to a spawned background bash job. */
|
|
export class BashJob {
|
|
readonly id: string;
|
|
readonly command: string;
|
|
private process: ChildProcess | null = null;
|
|
private cancelled = false;
|
|
private finished = false;
|
|
|
|
constructor(command: string) {
|
|
this.id = randomUUID();
|
|
this.command = command;
|
|
}
|
|
|
|
/** Spawn the `bash -c` child process and begin tracking it. */
|
|
start(): void {
|
|
const child = spawn("bash", ["-c", this.command], {
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
this.process = child;
|
|
|
|
// Drain stdout/stderr so the process doesn't block on a full pipe, and
|
|
// mark finished when it exits.
|
|
child.stdout?.resume();
|
|
child.stderr?.resume();
|
|
child.on("exit", () => {
|
|
this.finished = true;
|
|
});
|
|
child.on("error", () => {
|
|
this.finished = true;
|
|
});
|
|
}
|
|
|
|
/** Cancel the job: set the cancelled flag and kill the child process. */
|
|
cancel(): void {
|
|
this.cancelled = true;
|
|
if (this.process && !this.finished) {
|
|
try {
|
|
this.process.kill("SIGKILL");
|
|
} catch {
|
|
/* already gone */
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Whether the job is currently running. */
|
|
isRunning(): boolean {
|
|
if (this.cancelled || this.finished) return false;
|
|
if (!this.process) return false;
|
|
return this.process.exitCode === null && !this.finished;
|
|
}
|
|
}
|
|
|
|
/** Spawn a background bash job and return its handle (node-side). */
|
|
export function spawnBashJob(command: string): BashJob {
|
|
const job = new BashJob(command);
|
|
job.start();
|
|
return job;
|
|
}
|