Files
descrybe/scripts/free-dev-ports.mjs
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

86 lines
2.2 KiB
JavaScript

/**
* Kill whatever is listening on the Descrybe dev ports so `npm run dev` can bind.
* Skip when DESCRYBE_DEV_PORTS_FREED=1 (parent already freed; children must not
* re-kill the just-started sibling).
*
* Usage: node scripts/free-dev-ports.mjs
*/
import { spawnSync } from "node:child_process";
import { pathToFileURL } from "node:url";
import { DEV_API_PORT, DEV_WEB_PORT } from "./dev-ports.mjs";
export function freeDevPorts(ports = [DEV_API_PORT, DEV_WEB_PORT]) {
if (process.env.DESCRYBE_DEV_PORTS_FREED === "1") {
return;
}
for (const port of ports) {
const pids = listenersOnPort(port);
if (pids.length === 0) {
console.log(`[dev-ports] ${port} free`);
continue;
}
for (const pid of pids) {
console.log(`[dev-ports] killing PID ${pid} on :${port}`);
killPidTree(pid);
}
}
}
function listenersOnPort(port) {
if (process.platform === "win32") {
const ps = [
`$conns = @(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue);`,
`if ($conns.Count -eq 0) { exit 0 };`,
`$conns | ForEach-Object { $_.OwningProcess } | Sort-Object -Unique | ForEach-Object { Write-Output $_ }`,
].join(" ");
const r = spawnSync(
"powershell.exe",
["-NoProfile", "-NonInteractive", "-Command", ps],
{ encoding: "utf8" },
);
return parsePids(r.stdout || "");
}
const r = spawnSync("lsof", ["-tiTCP:" + port, "-sTCP:LISTEN"], {
encoding: "utf8",
});
if (r.status !== 0) return [];
return parsePids(r.stdout || "");
}
function parsePids(text) {
const seen = new Set();
const out = [];
for (const raw of text.split(/\r?\n/)) {
const n = Number.parseInt(raw.trim(), 10);
if (!Number.isFinite(n) || n <= 0 || seen.has(n)) continue;
if (n === process.pid) continue;
seen.add(n);
out.push(n);
}
return out;
}
function killPidTree(pid) {
if (process.platform === "win32") {
spawnSync("taskkill.exe", ["/T", "/F", "/PID", String(pid)], {
stdio: "ignore",
});
return;
}
try {
process.kill(pid, "SIGKILL");
} catch {
/* already gone */
}
}
const isDirect =
process.argv[1] &&
import.meta.url === pathToFileURL(process.argv[1]).href;
if (isDirect) {
freeDevPorts();
}