Something went wrong. Try again.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import { fork } from "node:child_process";import { dirname, join } from "node:path";import { fileURLToPath } from "node:url";
/** The only variables a check's process is given. Nothing else of the runner's environment (tokens, profile folders) is passed on. */const KEEP = ["PATH", "LANG", "LC_ALL", "TZ", "TMPDIR"] as const;export const checkEnv = (env: NodeJS.ProcessEnv): Record<string, string> => Object.fromEntries(KEEP.flatMap((k) => (env[k] !== undefined ? [[k, env[k]!]] : []))) as Record<string, string>;
export interface IsolatedCheck { path: string; cwd: string; args: string[]; vars: Record<string, string>; profile: string; account: string | undefined; read(target: Record<string, string>, options?: { blocks?: boolean }): Promise<string>; url(): Promise<string>; signal: AbortSignal;}export type IsolateCheck = (check: IsolatedCheck) => Promise<unknown>;
const CHILD = join(dirname(fileURLToPath(import.meta.url)), "checkChild.ts");
/** Runs one check in its own process with a stripped environment; resolves with what the check returned, rejects with an error carrying the check's own name/message. */export const isolateCheck: IsolateCheck = (check) => new Promise((resolve, reject) => { const execArgv = process.execArgv.some((a) => a.includes("tsx")) ? process.execArgv : [...process.execArgv, "--import", import.meta.resolve("tsx")]; const child = fork(CHILD, [], { env: checkEnv(process.env), cwd: check.cwd, execArgv, stdio: ["ignore", "inherit", "inherit", "ipc"] }); let settled = false; const finish = (fn: () => void) => { if (!settled) { settled = true; fn(); } }; const stop = () => { if (child.exitCode === null && !child.killed) child.kill("SIGKILL"); }; check.signal.addEventListener("abort", () => { stop(); finish(() => reject(new Error("the check was stopped"))); }, { once: true }); child.on("error", (e) => finish(() => reject(e))); child.on("exit", (code) => finish(() => reject(new Error(`the check's process ended (exit ${code}) without an answer`)))); child.on("message", (m: { type: string; id?: number; what?: string; target?: Record<string, string>; blocks?: boolean; value?: unknown; name?: string; message?: string; expected?: unknown; actual?: unknown }) => { if (m.type === "ask") { const reply = (p: Promise<unknown>) => p.then((value) => child.send({ type: "answer", id: m.id, ok: true, value }), (e: Error) => child.send({ type: "answer", id: m.id, ok: false, message: e.message })); if (m.what === "read") void reply(check.read(m.target ?? {}, m.blocks === true ? { blocks: true } : undefined)); else if (m.what === "url") void reply(check.url()); } else if (m.type === "done") { finish(() => resolve(m.value)); } else if (m.type === "threw") { finish(() => reject(Object.assign(new Error(m.message), { name: m.name, expected: m.expected, actual: m.actual }))); } }); child.send({ type: "run", path: check.path, args: check.args, vars: check.vars, profile: check.profile, account: check.account }); child.once("exit", stop); });