import { spawn, type ChildProcess } from "node:child_process"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { DAEMON_SCRIPT_RELATIVE } from "./constants.ts"; export function packageRootFrom(importMetaUrl: string): string { return path.resolve(path.dirname(fileURLToPath(importMetaUrl)), ".."); } export function resolveDaemonScriptPath(packageRoot: string): string { return path.join(packageRoot, DAEMON_SCRIPT_RELATIVE); } export type SpawnDaemonOptions = { runtimeDir: string; scriptPath: string; /** Injectable spawn for tests. Defaults to detached `xcrun swift`. */ spawnImpl?: typeof spawn; /** * Called if the child emits `error` (e.g. xcrun missing). Attaching this * prevents an async spawn failure from becoming an unhandled error event. */ onError?: (err: Error) => void; }; /** * Start the packaged Swift coordinator detached. Does not wait for readiness; * callers retry connect with backoff. */ export function spawnDaemon(options: SpawnDaemonOptions): ChildProcess { const spawnImpl = options.spawnImpl ?? spawn; const child = spawnImpl( "/usr/bin/xcrun", ["swift", options.scriptPath, "--runtime-dir", options.runtimeDir], { detached: true, stdio: "ignore", windowsHide: true, }, ); child.on("error", (err) => { options.onError?.(err); }); child.unref(); return child; }