import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { resolveDataDir } from "../data-dir.mjs"; const SERVICES = ["server", "mitm", "tunnel/cloudflared", "tunnel/tailscale"]; function getServicePidPath(service) { return join(resolveDataDir(), service, ".pid"); } export function writePidFile(service, pid) { try { const dir = join(resolveDataDir(), service); mkdirSync(dir, { recursive: true }); writeFileSync(getServicePidPath(service), String(pid), "utf8"); return true; } catch { return false; } } export function readPidFile(service) { try { const file = getServicePidPath(service); if (!existsSync(file)) return null; const pid = parseInt(readFileSync(file, "utf8").trim(), 10); return Number.isFinite(pid) ? pid : null; } catch { return null; } } export function cleanupPidFile(service) { try { unlinkSync(getServicePidPath(service)); } catch {} } export function killAllSubprocesses() { for (const service of SERVICES) { const pid = readPidFile(service); if (!pid) continue; try { process.kill(pid, "SIGTERM"); } catch {} cleanupPidFile(service); } } export function isPidRunning(pid) { if (!pid) return false; try { process.kill(pid, 0); return true; } catch { return false; } } export function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } export async function waitForServer(port, timeout = 15000) { const start = Date.now(); while (Date.now() - start < timeout) { try { const res = await fetch(`http://localhost:${port}/api/health`, { signal: AbortSignal.timeout(2000), }); if (res.ok) return true; } catch {} await sleep(500); } return false; }