Files
OneOS1.2/vite-plugins/utils/portOccupancy.ts
王冕 7ef8852919 Initial commit: OneOS prototype workspace baseline.
Include weekly report and other prototype sources with project tooling config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 15:36:52 +08:00

104 lines
2.9 KiB
TypeScript

import { spawnSync } from 'node:child_process';
export interface PortProcessLookup {
platform?: NodeJS.Platform;
spawnSync?: typeof spawnSync;
}
export interface ReleasePortOptions extends PortProcessLookup {
killPid?: (pid: number, signal?: NodeJS.Signals) => void;
currentPid?: number;
waitMs?: number;
}
function parsePidList(output: string): number[] {
const seen = new Set<number>();
for (const line of output.split(/\r?\n/u)) {
const pid = Number(line.trim());
if (Number.isInteger(pid) && pid > 0) {
seen.add(pid);
}
}
return Array.from(seen);
}
export function findListeningPidsOnPort(port: number, options: PortProcessLookup = {}): number[] {
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
return [];
}
const platform = options.platform || process.platform;
const run = options.spawnSync || spawnSync;
if (platform === 'win32') {
const result = run('powershell.exe', [
'-NoProfile',
'-Command',
`Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess`,
], {
encoding: 'utf8',
windowsHide: true,
timeout: 2000,
});
return parsePidList(String(result.stdout || ''));
}
const result = run('lsof', [
'-tiTCP',
`:${port}`,
'-sTCP:LISTEN',
], {
encoding: 'utf8',
timeout: 2000,
});
return parsePidList(String(result.stdout || ''));
}
function waitForPortRelease(port: number, options: ReleasePortOptions): void {
const deadline = Date.now() + Math.max(0, options.waitMs ?? 1500);
while (Date.now() < deadline) {
if (findListeningPidsOnPort(port, options).length === 0) {
return;
}
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50);
}
}
export function releaseListeningProcessesOnPort(port: number, options: ReleasePortOptions = {}): number[] {
const currentPid = options.currentPid ?? process.pid;
const pids = findListeningPidsOnPort(port, options).filter((pid) => pid !== currentPid);
if (pids.length === 0) {
return [];
}
if ((options.platform || process.platform) === 'win32') {
const run = options.spawnSync || spawnSync;
for (const pid of pids) {
run('taskkill.exe', ['/PID', String(pid), '/T', '/F'], {
encoding: 'utf8',
windowsHide: true,
timeout: 5000,
});
}
} else {
const killPid = options.killPid || process.kill.bind(process);
for (const pid of pids) {
try {
killPid(pid, 'SIGTERM');
} catch {
// Process may have exited between lookup and signal.
}
}
waitForPortRelease(port, options);
for (const pid of findListeningPidsOnPort(port, options).filter((pid) => pid !== currentPid)) {
try {
killPid(pid, 'SIGKILL');
} catch {
// Process may have exited after the second lookup.
}
}
}
return pids;
}