import { spawn } from "node:child_process"; import { accessSync, constants, existsSync, readFileSync } from "node:fs"; import type { Platform } from "./platform.ts"; export interface DetachedChild { once(event: "error", listener: (error: Error) => void): unknown; unref(): void; } export interface Runtime { platform: NodeJS.Platform | Platform | string; env: Readonly>; stdoutIsTTY: boolean; readFileSync(path: string): string; existsSync(path: string): boolean; isExecutable(path: string): boolean; writeStdout(data: string): void; /** Returns false only when the child could not be created synchronously. */ spawnDetached(command: string, args: readonly string[]): boolean; } function productionIsExecutable(path: string): boolean { try { if (process.platform === "win32") { return existsSync(path); } accessSync(path, constants.X_OK); return true; } catch { return false; } } function productionSpawnDetached(command: string, args: readonly string[]): boolean { try { const child = spawn(command, [...args], { detached: true, stdio: "ignore", }); child.once("error", () => {}); child.unref(); return true; } catch { return false; } } /** Production Node/Bun boundary. Tests should inject every side effect. */ export function createRuntime(): Runtime { return { platform: process.platform, env: process.env, stdoutIsTTY: process.stdout.isTTY === true, readFileSync(path) { try { return readFileSync(path, "utf8"); } catch { return ""; } }, existsSync(path) { try { return existsSync(path); } catch { return false; } }, isExecutable: productionIsExecutable, writeStdout(data) { try { process.stdout.write(data); } catch { // A closed pipe must not turn a best-effort notification into an error. } }, spawnDetached: productionSpawnDetached, }; }