UNPKG

@oxog/port-terminator

Version:

Cross-platform utility to terminate processes on ports with zero dependencies

210 lines 7.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.WindowsPlatform = void 0; const child_process_1 = require("child_process"); const errors_1 = require("../errors"); class WindowsPlatform { async findProcessesByPort(port, protocol = 'both') { const processes = []; try { const netstatResult = await this.executeCommand('netstat', ['-ano']); const lines = netstatResult.stdout.split('\n'); for (const line of lines) { const trimmedLine = line.trim(); if (!trimmedLine || trimmedLine.startsWith('Active') || trimmedLine.startsWith('Proto')) { continue; } const parts = trimmedLine.split(/\s+/); if (parts.length < 5) continue; const [proto, localAddress, , state, pidStr] = parts; if (protocol !== 'both') { const expectedProto = protocol.toUpperCase(); if (!proto.startsWith(expectedProto)) { continue; } } const portMatch = localAddress.match(/:(\d+)$/); if (!portMatch) continue; const localPort = parseInt(portMatch[1], 10); if (localPort !== port) continue; const pid = parseInt(pidStr, 10); if (isNaN(pid)) continue; if (proto.startsWith('TCP') && state !== 'LISTENING') { continue; } try { const processName = await this.getProcessName(pid); const processCommand = await this.getProcessCommand(pid); const processUser = await this.getProcessUser(pid); processes.push({ pid, name: processName, port: localPort, protocol: proto.toLowerCase(), command: processCommand, user: processUser, }); } catch (error) { continue; } } return processes; } catch (error) { throw new errors_1.CommandExecutionError('netstat -ano', 1, error instanceof Error ? error.message : 'Unknown error'); } } async killProcess(pid, force = false) { try { const args = force ? ['/F', '/PID', pid.toString()] : ['/PID', pid.toString()]; await this.executeCommand('taskkill', args); await this.waitForProcessToExit(pid, 5000); return true; } catch (error) { if (error instanceof errors_1.CommandExecutionError) { if (error.stderr.includes('Access is denied')) { throw new errors_1.PermissionError(`Access denied when trying to kill process ${pid}`, pid); } if (error.stderr.includes('not found') || error.stderr.includes('not running')) { return true; } } throw new errors_1.ProcessKillError(pid, force ? 'SIGKILL' : 'SIGTERM'); } } async isPortAvailable(port, protocol = 'both') { const processes = await this.findProcessesByPort(port, protocol); return processes.length === 0; } async executeCommand(command, args) { return new Promise((resolve, reject) => { const child = (0, child_process_1.spawn)(command, args, { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true, }); let stdout = ''; let stderr = ''; child.stdout?.on('data', (data) => { stdout += data.toString(); }); child.stderr?.on('data', (data) => { stderr += data.toString(); }); child.on('close', (code) => { if (code === 0) { resolve({ stdout, stderr, exitCode: code }); } else { reject(new errors_1.CommandExecutionError(`${command} ${args.join(' ')}`, code || 1, stderr)); } }); child.on('error', (error) => { reject(new errors_1.CommandExecutionError(`${command} ${args.join(' ')}`, 1, error.message)); }); }); } async getProcessName(pid) { try { const result = await this.executeCommand('tasklist', [ '/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH', ]); const lines = result.stdout.trim().split('\n'); if (lines.length > 0) { const csvLine = lines[0]; const parts = this.parseCSVLine(csvLine); if (parts.length > 0) { return parts[0]; } } return 'Unknown'; } catch { return 'Unknown'; } } async getProcessCommand(pid) { try { const result = await this.executeCommand('wmic', [ 'process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/format:value', ]); const lines = result.stdout.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('CommandLine=')) { return trimmed.substring('CommandLine='.length) || undefined; } } return undefined; } catch { return undefined; } } async getProcessUser(pid) { try { await this.executeCommand('wmic', [ 'process', 'where', `ProcessId=${pid}`, 'get', 'ExecutablePath', '/format:value', ]); return undefined; } catch { return undefined; } } async waitForProcessToExit(pid, timeout) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { await this.executeCommand('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV']); await new Promise((resolve) => setTimeout(resolve, 100)); } catch { return; } } } parseCSVLine(line) { const result = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { result.push(current.trim()); current = ''; } else { current += char; } } if (current) { result.push(current.trim()); } return result.map((item) => item.replace(/^"(.*)"$/, '$1')); } } exports.WindowsPlatform = WindowsPlatform; //# sourceMappingURL=windows.js.map