src / utils / commands.ts
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const execFileAsync = promisify(execFile);
const ALLOWED = new Set([
"npm",
"npx",
"node",
"tsc"
]);
export async function runAllowedCommand(
command: string,
args: string[],
cwd: string,
allowNetworkInstall: boolean,
): Promise<{ ok: true; stdout: string; stderr: string; exitCode: number } | { ok: false; error: string; stdout?: string; stderr?: string }> {
if (!ALLOWED.has(command)) {
return { ok: false, error: `Command not allowed: ${command}. Allowed: ${[...ALLOWED].join(", ")}` };
}
if (!allowNetworkInstall && command === "npm" && args[0] === "install") {
return { ok: false, error: "Network installs are disabled by configuration." };
}
try {
const { stdout, stderr } = await execFileAsync(command, args, {
cwd,
timeout: 120_000,
maxBuffer: 10 * 1024 * 1024,
shell: process.platform === "win32", // needed on Windows for npm
});
return { ok: true, stdout: stdout ?? "", stderr: stderr ?? "", exitCode: 0 };
} catch (err: any) {
return {
ok: false,
error: err?.message ?? String(err),
stdout: err?.stdout ?? "",
stderr: err?.stderr ?? "",
};
}
}