@oxog/port-terminator
Version:
Cross-platform utility to terminate processes on ports with zero dependencies
210 lines • 7.5 kB
JavaScript
;
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