96 lines
3.1 KiB
JavaScript
96 lines
3.1 KiB
JavaScript
import { _ as sleep } from "./utils-PmTbZoD1.js";
|
|
import { p as resolveLsofCommandSync } from "./errors-DdT2Dtkb.js";
|
|
import { execFileSync } from "node:child_process";
|
|
|
|
//#region src/cli/ports.ts
|
|
function parseLsofOutput(output) {
|
|
const lines = output.split(/\r?\n/).filter(Boolean);
|
|
const results = [];
|
|
let current = {};
|
|
for (const line of lines) if (line.startsWith("p")) {
|
|
if (current.pid) results.push(current);
|
|
current = { pid: Number.parseInt(line.slice(1), 10) };
|
|
} else if (line.startsWith("c")) current.command = line.slice(1);
|
|
if (current.pid) results.push(current);
|
|
return results;
|
|
}
|
|
function listPortListeners(port) {
|
|
try {
|
|
return parseLsofOutput(execFileSync(resolveLsofCommandSync(), [
|
|
"-nP",
|
|
`-iTCP:${port}`,
|
|
"-sTCP:LISTEN",
|
|
"-FpFc"
|
|
], { encoding: "utf-8" }));
|
|
} catch (err) {
|
|
const status = err.status;
|
|
if (err.code === "ENOENT") throw new Error("lsof not found; required for --force", { cause: err });
|
|
if (status === 1) return [];
|
|
throw err instanceof Error ? err : new Error(String(err));
|
|
}
|
|
}
|
|
function forceFreePort(port) {
|
|
const listeners = listPortListeners(port);
|
|
for (const proc of listeners) try {
|
|
process.kill(proc.pid, "SIGTERM");
|
|
} catch (err) {
|
|
throw new Error(`failed to kill pid ${proc.pid}${proc.command ? ` (${proc.command})` : ""}: ${String(err)}`, { cause: err });
|
|
}
|
|
return listeners;
|
|
}
|
|
function killPids(listeners, signal) {
|
|
for (const proc of listeners) try {
|
|
process.kill(proc.pid, signal);
|
|
} catch (err) {
|
|
throw new Error(`failed to kill pid ${proc.pid}${proc.command ? ` (${proc.command})` : ""}: ${String(err)}`, { cause: err });
|
|
}
|
|
}
|
|
async function forceFreePortAndWait(port, opts = {}) {
|
|
const timeoutMs = Math.max(opts.timeoutMs ?? 1500, 0);
|
|
const intervalMs = Math.max(opts.intervalMs ?? 100, 1);
|
|
const sigtermTimeoutMs = Math.min(Math.max(opts.sigtermTimeoutMs ?? 600, 0), timeoutMs);
|
|
const killed = forceFreePort(port);
|
|
if (killed.length === 0) return {
|
|
killed,
|
|
waitedMs: 0,
|
|
escalatedToSigkill: false
|
|
};
|
|
let waitedMs = 0;
|
|
const triesSigterm = intervalMs > 0 ? Math.ceil(sigtermTimeoutMs / intervalMs) : 0;
|
|
for (let i = 0; i < triesSigterm; i++) {
|
|
if (listPortListeners(port).length === 0) return {
|
|
killed,
|
|
waitedMs,
|
|
escalatedToSigkill: false
|
|
};
|
|
await sleep(intervalMs);
|
|
waitedMs += intervalMs;
|
|
}
|
|
if (listPortListeners(port).length === 0) return {
|
|
killed,
|
|
waitedMs,
|
|
escalatedToSigkill: false
|
|
};
|
|
killPids(listPortListeners(port), "SIGKILL");
|
|
const remainingBudget = Math.max(timeoutMs - waitedMs, 0);
|
|
const triesSigkill = intervalMs > 0 ? Math.ceil(remainingBudget / intervalMs) : 0;
|
|
for (let i = 0; i < triesSigkill; i++) {
|
|
if (listPortListeners(port).length === 0) return {
|
|
killed,
|
|
waitedMs,
|
|
escalatedToSigkill: true
|
|
};
|
|
await sleep(intervalMs);
|
|
waitedMs += intervalMs;
|
|
}
|
|
const still = listPortListeners(port);
|
|
if (still.length === 0) return {
|
|
killed,
|
|
waitedMs,
|
|
escalatedToSigkill: true
|
|
};
|
|
throw new Error(`port ${port} still has listeners after --force: ${still.map((p) => p.pid).join(", ")}`);
|
|
}
|
|
|
|
//#endregion
|
|
export { forceFreePortAndWait as n, forceFreePort as t }; |