UNPKG

rajt

Version:

A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.

119 lines (103 loc) 3.4 kB
import net from 'node:net' import { exec } from 'node:child_process' import { error, warn } from './log' export function withPort(desiredPort: number, cb: (port: number) => void, maxAttempts = 10) { getAvailablePort(desiredPort) .then(([port, pid]) => { if (port != desiredPort) warn( `Port ${desiredPort} is in use by ${pid ? 'process '+ pid : 'an unknown process'}, using available port ${port} instead` ) cb(Number(port)) }).catch(e => error('Error finding available port:', e)) } export async function isPortInUse(port: number) { return new Promise(resolve => { const server = net.createServer() .once('error', () => resolve(true)) .once('listening', () => { server.close(() => resolve(false)) }) .listen(port) }) } export async function getAvailablePort(startPort: number, maxAttempts = 10) { let port = startPort let attempts = 0 while (attempts < maxAttempts) { const inUse = await isPortInUse(port) if (!inUse && attempts) { const pid = await getProcessIdUsingPort(startPort) return [port, pid] } if (!inUse) return [port, null] port++ attempts++ } throw new Error(`No available ports found after ${maxAttempts} attempts`) } export async function getProcessIdUsingPort(port: number): Promise<string | null> { const timeoutMs = 250 const processLookupController = new AbortController() const pidPromise = new Promise<string | null>((resolve) => { const handleError = (e: Error) => { error('Failed to get process ID for port', port, e) resolve(null) } try { // Use lsof on Unix-like systems (macOS, Linux) if (process.platform !== 'win32') { exec( `lsof -ti:${port} -sTCP:LISTEN`, { signal: processLookupController.signal }, (error, stdout) => { if (error) { handleError(error) return } // `-sTCP` will ensure there's only one port, clean up output const pid = stdout.trim() resolve(pid || null) } ) } else { // Use netstat on Windows exec( `netstat -ano | findstr /C:":${port} " | findstr LISTENING`, { signal: processLookupController.signal }, (error, stdout) => { if (error) { handleError(error) return } // Clean up output and extract PID const cleanOutput = stdout.replace(/\s+/g, ' ').trim() if (cleanOutput) { const lines = cleanOutput.split('\n') const firstLine = lines[0].trim() if (firstLine) { const parts = firstLine.split(' ') const pid = parts[parts.length - 1] resolve(pid || null) } else { resolve(null) } } else { resolve(null) } } ) } } catch (cause) { handleError( new Error('Unexpected error during process lookup', { cause }) ) } }) const timeoutId = setTimeout(() => { processLookupController.abort( `PID detection timed out after ${timeoutMs}ms for port ${port}.` ) }, timeoutMs) pidPromise.finally(() => clearTimeout(timeoutId)) return pidPromise }