UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

134 lines (132 loc) • 4.61 kB
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { Effect } from "effect"; import { homedir } from "node:os"; import { access } from "node:fs/promises"; import { spawnSync } from "node:child_process"; //#region src/process-registry.ts function getRegistryDir() { return join(homedir(), ".cache", "everything-dev"); } function ensureRegistryDir() { const path = getRegistryPath(); const dir = join(path, ".."); if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); } function readRegistry() { const path = getRegistryPath(); if (!existsSync(path)) return []; try { const raw = JSON.parse(readFileSync(path, "utf-8")); if (!Array.isArray(raw)) return []; return raw.filter((entry) => entry && typeof entry === "object" && typeof entry.pid === "number" && typeof entry.configDir === "string" && typeof entry.role === "string"); } catch { return []; } } function isPidAlive(pid) { try { process.kill(pid, 0); return true; } catch (err) { return err.code !== "ESRCH"; } } /** * Identity of a process generation, stable across PID reuse: Linux reads the * kernel's starttime tick from /proc, macOS falls back to `ps -o lstart=`. * Null when the generation cannot be determined (the caller then treats the * claim as unverifiable and keeps legacy behavior). */ function processStartTime(pid) { if (pid <= 1) return null; try { const stat = readFileSync(`/proc/${pid}/stat`, "utf-8"); const close = stat.lastIndexOf(")"); const starttime = stat.slice(close + 2).split(" ")[19]; return starttime ? `proc:${starttime}` : null; } catch { try { const text = spawnSync("ps", [ "-o", "lstart=", "-p", String(pid) ], { timeout: 5e3 }).stdout?.toString().trim(); return text ? `ps:${text}` : null; } catch { return null; } } } function isEntryLive(entry) { if (entry.pid <= 1) return false; if (!existsSync(entry.configDir)) return false; if (!isPidAlive(entry.pid)) return false; if (entry.processStart !== void 0) return processStartTime(entry.pid) === entry.processStart; return true; } function pruneDead(entries) { return entries.filter(isEntryLive); } function pruneDeadEffect(entries) { return Effect.forEach(entries, (entry) => { if (entry.pid <= 1) return Effect.succeed(null); return Effect.gen(function* () { if (!(yield* Effect.promise(() => access(entry.configDir).then(() => true).catch(() => false)))) return null; if (!isPidAlive(entry.pid)) return null; if (entry.processStart !== void 0) { if ((yield* Effect.promise(() => Promise.resolve(processStartTime(entry.pid)))) !== entry.processStart) return null; } return entry; }); }, { concurrency: "unbounded" }).pipe(Effect.map((results) => results.filter((e) => e !== null))); } function writeRegistry(entries) { ensureRegistryDir(); const path = getRegistryPath(); const tmpPath = `${path}.tmp`; writeFileSync(tmpPath, `${JSON.stringify(entries, null, 2)}\n`); renameSync(tmpPath, path); } function registerStandalone(entry) { const live = pruneDead(readRegistry()); const full = { ...entry, role: "standalone", processStart: processStartTime(entry.pid) ?? entry.processStart }; const withoutSelf = live.filter((existing) => existing.pid !== entry.pid); withoutSelf.push(full); writeRegistry(withoutSelf); return full; } function updateChildPids(pid, childPids) { const live = pruneDead(readRegistry()); const entry = live.find((existing) => existing.pid === pid); if (!entry) return; entry.childPids = childPids; writeRegistry(live); } function unregisterPid(pid) { const entries = readRegistry(); const next = pruneDead(entries).filter((entry) => entry.pid !== pid); if (next.length === entries.length) return; writeRegistry(next); } function getRegistryPath() { return process.env.BO_PID_REGISTRY_PATH ?? join(getRegistryDir(), "pids.json"); } function claimedPorts() { return new Set(claimedPortOwners().keys()); } /** Port → the live registry entry claiming it, for occupancy diagnostics. */ function claimedPortOwners() { const live = pruneDead(readRegistry()); const out = /* @__PURE__ */ new Map(); for (const entry of live) for (const port of Object.values(entry.ports)) if (typeof port === "number") out.set(port, entry); return out; } //#endregion export { claimedPortOwners, claimedPorts, getRegistryPath, isEntryLive, isPidAlive, processStartTime, pruneDead, pruneDeadEffect, readRegistry, registerStandalone, unregisterPid, updateChildPids, writeRegistry }; //# sourceMappingURL=process-registry.mjs.map