|
| 1 | +// eslint-disable-next-line depend/ban-dependencies |
| 2 | +import { execa } from 'execa'; |
| 3 | + |
| 4 | +const isWindows = process.platform === 'win32'; |
| 5 | + |
| 6 | +/** Kills any process that is listening on the specified port. */ |
| 7 | +export const killProcessOnPort = async (port: number): Promise<void> => { |
| 8 | + try { |
| 9 | + let pids: string[] = []; |
| 10 | + |
| 11 | + if (isWindows) { |
| 12 | + // Windows: use netstat to find the process |
| 13 | + try { |
| 14 | + const { stdout } = await execa('netstat', ['-ano']); |
| 15 | + const lines = stdout.split('\n'); |
| 16 | + const regex = new RegExp(`TCP.*:${port}.*LISTENING\\s+(\\d+)`, 'i'); |
| 17 | + |
| 18 | + for (const line of lines) { |
| 19 | + const match = line.match(regex); |
| 20 | + if (match && match[1]) { |
| 21 | + pids.push(match[1]); |
| 22 | + } |
| 23 | + } |
| 24 | + } catch { |
| 25 | + // netstat failed, ignore |
| 26 | + } |
| 27 | + } else { |
| 28 | + // Unix-like (macOS, Linux): use lsof |
| 29 | + try { |
| 30 | + const { stdout } = await execa('lsof', ['-ti', `:${port}`]); |
| 31 | + pids = stdout.trim().split('\n').filter(Boolean); |
| 32 | + } catch { |
| 33 | + // lsof failed or no process found |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | + if (pids.length > 0) { |
| 38 | + console.log(`☠️ killing process(es) on port ${port}: ${pids.join(', ')}`); |
| 39 | + |
| 40 | + if (isWindows) { |
| 41 | + await Promise.all( |
| 42 | + pids.map((pid) => execa('taskkill', ['/PID', pid, '/F']).catch(() => {})) |
| 43 | + ); |
| 44 | + } else { |
| 45 | + await Promise.all(pids.map((pid) => execa('kill', ['-9', pid]).catch(() => {}))); |
| 46 | + } |
| 47 | + |
| 48 | + // Give the OS a moment to release the port |
| 49 | + await new Promise((resolve) => setTimeout(resolve, 500)); |
| 50 | + } |
| 51 | + } catch { |
| 52 | + // No process found on port or command failed, which is fine |
| 53 | + } |
| 54 | +}; |
0 commit comments