86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
import { spawn } from 'node:child_process';
|
|
|
|
import { killProcessTree } from '@main/utils/childProcess';
|
|
|
|
import type { ChildProcessByStdio } from 'node:child_process';
|
|
import type { Readable } from 'node:stream';
|
|
|
|
export interface TmuxCommandSpec {
|
|
command: string;
|
|
args: string[];
|
|
env: NodeJS.ProcessEnv;
|
|
cwd?: string;
|
|
}
|
|
|
|
interface RunCommandOptions {
|
|
onLine: (line: string) => void;
|
|
}
|
|
|
|
export class TmuxCommandRunner {
|
|
#activeChild: ChildProcessByStdio<null, Readable, Readable> | null = null;
|
|
|
|
get activeChild(): ChildProcessByStdio<null, Readable, Readable> | null {
|
|
return this.#activeChild;
|
|
}
|
|
|
|
async run(spec: TmuxCommandSpec, options: RunCommandOptions): Promise<{ exitCode: number }> {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn(spec.command, spec.args, {
|
|
cwd: spec.cwd,
|
|
env: spec.env,
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
});
|
|
this.#activeChild = child;
|
|
|
|
const createBufferedLineWriter = (): { push: (chunk: string) => void; flush: () => void } => {
|
|
let pending = '';
|
|
|
|
const emitLine = (line: string): void => {
|
|
const normalizedLine = line.replace(/\r$/, '');
|
|
if (normalizedLine.trim()) {
|
|
options.onLine(normalizedLine);
|
|
}
|
|
};
|
|
|
|
return {
|
|
push: (chunk: string): void => {
|
|
pending += chunk;
|
|
const lines = pending.split(/\r?\n/);
|
|
pending = lines.pop() ?? '';
|
|
for (const line of lines) {
|
|
emitLine(line);
|
|
}
|
|
},
|
|
flush: (): void => {
|
|
if (!pending) {
|
|
return;
|
|
}
|
|
emitLine(pending.trimEnd());
|
|
pending = '';
|
|
},
|
|
};
|
|
};
|
|
|
|
const stdoutWriter = createBufferedLineWriter();
|
|
const stderrWriter = createBufferedLineWriter();
|
|
|
|
child.stdout.on('data', (chunk: Buffer | string) => stdoutWriter.push(String(chunk)));
|
|
child.stderr.on('data', (chunk: Buffer | string) => stderrWriter.push(String(chunk)));
|
|
child.on('error', (error) => {
|
|
this.#activeChild = null;
|
|
reject(error);
|
|
});
|
|
child.on('close', (exitCode) => {
|
|
stdoutWriter.flush();
|
|
stderrWriter.flush();
|
|
this.#activeChild = null;
|
|
resolve({ exitCode: exitCode ?? 0 });
|
|
});
|
|
});
|
|
}
|
|
|
|
cancel(): void {
|
|
killProcessTree(this.#activeChild);
|
|
this.#activeChild = null;
|
|
}
|
|
}
|