shAsync
uses node:child_process
sh, without waiting: run a program, resolving with whether it passed and what it printed, so several can
run side by side. onLine hears each line as it's printed.
import { spawn } from "node:child_process"; export function shAsync( program: string, args: string[], cwd: string, env: NodeJS.ProcessEnv = {}, onLine?: (line: string) => void, ): Promise<{ ok: boolean; out: string }> { return new Promise((resolve) => { const child = spawn(program, args, { cwd, env: { ...process.env, NO_COLOR: "1", FORCE_COLOR: "0", ...env }, }); let out = ""; let partial = ""; const heard = (d: string) => { out += d; if (!onLine) return; const lines = (partial + d).split("\n"); partial = lines.pop()!; for (const l of lines) onLine(l); }; child.stdout.setEncoding("utf8").on("data", heard); child.stderr.setEncoding("utf8").on("data", heard); child.on("error", (e) => (out += String(e))); child.on("close", (code) => resolve({ ok: code === 0, out })); }); }