mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-03 18:48:06 +09:00
83 lines
2.3 KiB
TypeScript
83 lines
2.3 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
|
|
export const runAgentCommand = ({
|
|
command,
|
|
cwd,
|
|
env,
|
|
timeoutMs,
|
|
onStdoutLine,
|
|
signal,
|
|
}: {
|
|
command: string;
|
|
cwd: string;
|
|
env: NodeJS.ProcessEnv;
|
|
timeoutMs: number;
|
|
onStdoutLine?: (line: string) => void;
|
|
signal?: AbortSignal;
|
|
}) =>
|
|
new Promise<{ exitCode: number; durationMs: number }>((resolve, reject) => {
|
|
const startedAt = Date.now();
|
|
const child = spawn(process.env.SHELL ?? "/bin/sh", ["-c", command], {
|
|
cwd,
|
|
env,
|
|
stdio: [
|
|
"ignore",
|
|
onStdoutLine === undefined ? "ignore" : "pipe",
|
|
"ignore",
|
|
],
|
|
detached: process.platform !== "win32",
|
|
});
|
|
let stdoutBuffer = "";
|
|
if (onStdoutLine !== undefined && child.stdout !== null) {
|
|
child.stdout.setEncoding("utf8");
|
|
child.stdout.on("data", (chunk: string) => {
|
|
stdoutBuffer += chunk;
|
|
const lines = stdoutBuffer.split("\n");
|
|
stdoutBuffer = lines.pop() ?? "";
|
|
for (const line of lines) {
|
|
onStdoutLine(line);
|
|
}
|
|
});
|
|
}
|
|
const terminate = () => {
|
|
if (child.pid !== undefined) {
|
|
try {
|
|
process.kill(process.platform === "win32" ? child.pid : -child.pid);
|
|
} catch {}
|
|
}
|
|
};
|
|
const onAbort = () => terminate();
|
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
const cleanup = () => {
|
|
clearTimeout(timeout);
|
|
signal?.removeEventListener("abort", onAbort);
|
|
};
|
|
const timeout = setTimeout(() => {
|
|
terminate();
|
|
cleanup();
|
|
reject(new Error(`Agent command timed out after ${timeoutMs}ms.`));
|
|
}, timeoutMs);
|
|
child.once("error", (error) => {
|
|
cleanup();
|
|
reject(error);
|
|
});
|
|
child.once("close", (code, childSignal) => {
|
|
cleanup();
|
|
if (onStdoutLine !== undefined && stdoutBuffer.length > 0) {
|
|
onStdoutLine(stdoutBuffer);
|
|
}
|
|
if (childSignal !== null) {
|
|
reject(new Error(`Agent command exited from signal ${childSignal}.`));
|
|
return;
|
|
}
|
|
if (signal?.aborted) {
|
|
reject(new Error("Agent command was aborted."));
|
|
return;
|
|
}
|
|
resolve({ exitCode: code ?? -1, durationMs: Date.now() - startedAt });
|
|
});
|
|
if (signal?.aborted) {
|
|
terminate();
|
|
}
|
|
});
|