import process from 'node:process'; import {execa} from 'execa'; const netstat = async type => { const {stdout} = await execa('netstat', ['-anv', '-p', type]); return stdout; }; const macos = async () => { const result = await Promise.all([ netstat('tcp'), netstat('udp'), ]); return result.join('\n'); }; const linux = async () => { const {stdout} = await execa('ss', ['-tunlp']); return stdout; }; const windows = async () => { const {stdout} = await execa('netstat', ['-ano']); return stdout; }; const getListFunction = process.platform === 'darwin' ? macos : (process.platform === 'linux' ? linux : windows); const addressColumn = process.platform === 'darwin' ? 3 : (process.platform === 'linux' ? 4 : 1); const portColumn = process.platform === 'darwin' ? 8 : (process.platform === 'linux' ? 6 : 4); const isProtocol = value => /^\s*(tcp|udp)/i.test(value); const parsePid = pid => { if (typeof pid !== 'string') { return; } const {groups} = /(?:^|",|",pid=)(?\d+)/.exec(pid) || {}; if (groups) { return Number.parseInt(groups.pid, 10); } }; const getPort = (port, list) => { const regex = new RegExp(`[.:]${port}$`); const foundPort = list.find(line => regex.test(line[addressColumn])); if (!foundPort) { throw new Error(`Could not find a process that uses port \`${port}\``); } return parsePid(foundPort[portColumn]); }; const getList = async () => { const list = await getListFunction(); return list .split('\n') .filter(line => isProtocol(line)) .map(line => line.match(/\S+/g) || []); }; export async function portToPid(port) { if (Array.isArray(port)) { const list = await getList(); const tuples = await Promise.all(port.map(port_ => [port_, getPort(port_, list)])); return new Map(tuples); } if (!Number.isInteger(port)) { throw new TypeError(`Expected an integer, got ${typeof port}`); } return getPort(port, await getList()); } export async function pidToPorts(pid) { if (Array.isArray(pid)) { const returnValue = new Map(pid.map(pid_ => [pid_, new Set()])); for (const [port, pid_] of await allPortsWithPid()) { if (returnValue.has(pid_)) { returnValue.get(pid_).add(port); } } return returnValue; } if (!Number.isInteger(pid)) { throw new TypeError(`Expected an integer, got ${typeof pid}`); } const returnValue = new Set(); for (const [port, pid_] of await allPortsWithPid()) { if (pid_ === pid) { returnValue.add(port); } } return returnValue; } export async function allPortsWithPid() { const list = await getList(); const returnValue = new Map(); for (const line of list) { const {groups} = /[^]*[.:](?\d+)$/.exec(line[addressColumn]) || {}; if (groups) { returnValue.set(Number.parseInt(groups.port, 10), parsePid(line[portColumn])); } } return returnValue; }