everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
134 lines (132 loc) • 4.61 kB
JavaScript
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