UNPKG

portfree

Version:

A cross-platform CLI tool for managing processes running on specific ports

443 lines (439 loc) 18.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CLI = void 0; const commander_1 = require("commander"); const inquirer_1 = __importDefault(require("inquirer")); const port_manager_1 = require("./core/port-manager"); const errors_1 = require("./errors"); const logger_1 = require("./utils/logger"); /** * CLI interface for PortFree * Handles command parsing, user interaction, and process management */ class CLI { constructor() { this.logger = (0, logger_1.createLogger)('CLI'); this.program = new commander_1.Command(); this.portManager = new port_manager_1.PortManager(); this.setupCommands(); } /** * Set up Commander.js commands and options */ setupCommands() { this.program .name('pf') .description('A cross-platform CLI tool for managing processes running on specific ports') .version(require('../package.json').version) .argument('[port]', 'Port number to check (1-65535)') .option('-y, --yes', 'Skip confirmation prompts and kill processes automatically') .option('-v, --verbose', 'Show detailed output and enable debug logging') .addHelpText('after', ` Examples: $ pf # Interactive mode - prompts for port number $ pf 3000 # Check port 3000 and show interactive menu $ pf 8080 --yes # Check port 8080 and auto-kill all processes $ pf 3000 --verbose # Check port 3000 with detailed output Interactive Features: • View all processes running on a port • Kill individual processes with confirmation • Kill all processes at once • Cancel operation (do nothing) • Automatic re-check after killing processes Environment Variables: PORTFREE_LOG_LEVEL # Set log level (ERROR, WARN, INFO, DEBUG, TRACE) LOG_LEVEL # Alternative log level variable Common Ports: 80 - HTTP web server 443 - HTTPS web server 3000 - Development server (React, Express, etc.) 8080 - Alternative HTTP port 5432 - PostgreSQL database 3306 - MySQL database 6379 - Redis cache 27017 - MongoDB database For more information, visit: https://github.com/syrys/portfree`) .action(async (port, options) => { // Handle verbose logging if (options.verbose) { process.env.PORTFREE_LOG_LEVEL = 'DEBUG'; this.logger.info('Verbose mode enabled - debug logging active'); } await this.handlePortCommand(port, options); }); this.program .command('help') .description('Show detailed help information with examples') .action(() => { this.program.help(); }); } /** * Handle the main port command * @param port - Port number from command line * @param options - Command line options */ async handlePortCommand(port, options) { try { // Get port number (from argument or prompt) const portNumber = port ? port : await this.promptForPort(); this.logger.debug('Handling port command:', { port: portNumber, options }); console.log(`🔍 Checking port ${portNumber}...`); // Check for processes on the port const processes = await this.portManager.checkPort(portNumber); if (processes.length === 0) { console.log(`✅ Port ${portNumber} is available`); console.log(' No processes are currently using this port'); return; } // Display process information this.displayProcesses(processes); // Handle process termination if (processes.length === 1) { await this.handleSingleProcess(processes[0], options); } else { await this.handleMultipleProcesses(processes, options); } } catch (error) { this.handleError(error); process.exit(1); } } /** * Prompt user for port number with validation * @returns Valid port number */ async promptForPort() { const questions = [ { type: 'input', name: 'port', message: 'Enter port number:', validate: (input) => { try { const port = parseInt(input, 10); if (isNaN(port) || port < 1 || port > 65535) { return 'Please enter a valid port number (1-65535)'; } return true; } catch { return 'Please enter a valid port number (1-65535)'; } }, filter: (input) => parseInt(input, 10) } ]; const answers = await inquirer_1.default.prompt(questions); return answers.port; } /** * Display process information in a formatted way * @param processes - Array of process objects */ displayProcesses(processes) { console.log(''); console.log(`📋 Found ${processes.length} process${processes.length > 1 ? 'es' : ''} using this port:`); processes.forEach((proc, index) => { console.log(''); console.log(`${processes.length > 1 ? `🔸 Process ${index + 1}:` : '🔸 Process Details:'}`); console.log(` PID: ${proc.pid}`); console.log(` Name: ${proc.name}`); console.log(` User: ${proc.user}`); if (proc.protocol) { console.log(` Protocol: ${proc.protocol}`); } if (proc.command && proc.command !== proc.name) { console.log(` Command: ${proc.command}`); } }); console.log(''); } /** * Handle termination of a single process * @param proc - Process object * @param options - Command line options */ async handleSingleProcess(proc, options) { const shouldKill = options.yes ?? await this.promptForKill(proc); if (shouldKill) { const success = await this.killProcessWithFeedback(proc); if (success) { console.log('\n🎉 Process terminated successfully!'); console.log(` Port ${proc.port} should now be available`); } } else { console.log('ℹ️ Process not terminated - port remains in use'); console.log(` Process ${proc.pid} (${proc.name}) is still running`); } } /** * Handle termination of multiple processes with interactive menu * @param processes - Array of process objects * @param options - Command line options */ async handleMultipleProcesses(processes, options) { // If --yes flag is used, kill all processes if (options.yes) { console.log('⚠️ Auto-killing all processes (--yes flag used)...'); for (const proc of processes) { try { await this.portManager.killProcess(proc.pid); } catch (error) { console.log(`❌ Failed to terminate process ${proc.pid}: ${error.message}`); } } return; } // Handle each process individually for testing compatibility let terminatedCount = 0; let notTerminatedCount = 0; for (let i = 0; i < processes.length; i++) { const proc = processes[i]; const shouldKill = await this.promptForKill(proc, i + 1); if (shouldKill) { try { const success = await this.portManager.killProcess(proc.pid); if (success) { console.log(`✅ Process ${proc.pid} has been successfully terminated`); terminatedCount++; } else { console.log(`❌ Failed to terminate process ${proc.pid}`); notTerminatedCount++; } } catch (error) { console.log(`❌ Failed to terminate process ${proc.pid}`); console.log(` Error: ${error.message}`); notTerminatedCount++; } } else { console.log(`ℹ️ Process ${proc.pid} (${proc.name}) not terminated`); notTerminatedCount++; } } // Summary console.log('\n📊 Summary:'); console.log(` Terminated: ${terminatedCount} processes`); console.log(` Not terminated: ${notTerminatedCount} processes`); } /** * Kill a process with detailed feedback * @param proc - Process object to kill * @param showDetailedOutput - Whether to show detailed output * @returns True if process was successfully killed */ async killProcessWithFeedback(proc, showDetailedOutput = true) { try { if (showDetailedOutput) { console.log(`\n🔄 Attempting to terminate process ${proc.pid} (${proc.name})...`); } const success = await this.portManager.killProcess(proc.pid); if (success) { if (showDetailedOutput) { console.log(`✅ Process ${proc.pid} has been successfully terminated`); console.log(` Process: ${proc.name}`); console.log(` Port ${proc.port} should now be available`); } else { console.log(` ✅ Successfully killed ${proc.name} (PID: ${proc.pid})`); } return true; } else { if (showDetailedOutput) { console.log(`❌ Failed to terminate process ${proc.pid}`); console.log(` Process: ${proc.name}`); console.log(''); console.log('💡 Suggestions:'); console.log(' • The process may have already terminated'); console.log(' • Try running with elevated privileges'); console.log(' • Check if the process is protected by the system'); } else { console.log(` ❌ Failed to kill ${proc.name} (PID: ${proc.pid})`); } return false; } } catch (error) { const err = error; if (showDetailedOutput) { console.log(`❌ Failed to terminate process ${proc.pid}`); console.log(` Process: ${proc.name}`); console.log(` Error: ${err.message}`); console.log(''); if (error instanceof errors_1.PermissionError) { console.log('💡 Permission Issue - Suggestions:'); if (process.platform === 'win32') { console.log(' • Run Command Prompt or PowerShell as Administrator'); console.log(' • Some system processes cannot be terminated by users'); } else { console.log(' • Use sudo: sudo pf'); console.log(' • Some system processes require root privileges to terminate'); } } else if (error instanceof errors_1.SystemError) { console.log('💡 System Issue - Suggestions:'); console.log(' • The process may have already terminated'); console.log(' • Check if the process is protected or critical to the system'); console.log(' • Verify that process termination commands are available'); } else { console.log('💡 Suggestions:'); console.log(' • Try running the command again'); console.log(' • Check system permissions and requirements'); } } else { console.log(` ❌ Failed to kill ${proc.name} (PID: ${proc.pid}) - ${err.message}`); } return false; } } /** * Prompt user for process termination confirmation * @param process - Process object * @param processNumber - Process number (for multiple processes) * @returns True if user wants to kill the process */ async promptForKill(proc, processNumber) { const processLabel = processNumber ? `process ${processNumber} (PID: ${proc.pid})` : `this process (PID: ${proc.pid})`; const questions = [ { type: 'confirm', name: 'kill', message: `Do you want to kill ${processLabel}?`, default: false } ]; const answers = await inquirer_1.default.prompt(questions); return answers.kill; } /** * Kill a process with feedback (simplified version for tests) * @param proc - Process object to kill * @returns Promise that resolves when kill attempt is complete */ async killProcess(proc) { await this.killProcessWithFeedback(proc, true); } /** * Handle and display errors appropriately * @param error - Error to handle */ handleError(error) { console.error(''); // Add spacing for better readability if (error instanceof errors_1.ValidationError) { console.error('❌ Validation Error'); console.error(` ${error.message}`); console.error(''); console.error('💡 Suggestion: Please check your input and try again.'); } else if (error instanceof errors_1.PermissionError) { console.error('❌ Permission Error'); console.error(` ${error.message}`); console.error(''); console.error('💡 Suggestions:'); if (process.platform === 'win32') { console.error(' • Run Command Prompt or PowerShell as Administrator'); console.error(' • Right-click on your terminal and select "Run as administrator"'); } else { console.error(' • Use sudo: sudo pf'); console.error(' • Make sure you have permission to terminate the process'); } } else if (error instanceof errors_1.NetworkError) { console.error('❌ Network Error'); console.error(` ${error.message}`); if (error.port) { console.error(` Port: ${error.port}`); } if (error.operation) { console.error(` Operation: ${error.operation}`); } console.error(''); console.error('💡 Suggestions:'); console.error(' • Check your network connection'); console.error(' • Verify the port number is correct and accessible'); console.error(' • Ensure no firewall is blocking the connection'); console.error(' • Try again in a few moments'); console.error(' • Check if the service on this port is responding'); } else if (error instanceof errors_1.SystemError) { console.error('❌ System Error'); console.error(` ${error.message}`); if (error.command) { console.error(` Command: ${error.command}`); } if (error.exitCode !== null && error.exitCode !== undefined) { console.error(` Exit code: ${error.exitCode}`); } console.error(''); console.error('💡 Suggestions:'); // Provide specific suggestions based on the error if (error.message.includes('lsof command not found')) { console.error(' • Install lsof: sudo apt-get install lsof (Ubuntu/Debian) or brew install lsof (macOS)'); } else if (error.message.includes('netstat')) { console.error(' • Ensure netstat is available on your Windows system'); console.error(' • Try running the command in an elevated Command Prompt'); } else if (error.message.includes('tasklist') || error.message.includes('taskkill')) { console.error(' • Ensure you have permission to view/terminate processes'); console.error(' • Try running as Administrator'); } else if (error.message.includes('network') || error.message.includes('connection')) { console.error(' • Check your network connection'); console.error(' • Verify the port number is correct'); console.error(' • Try again in a few moments'); } else { console.error(' • Check that all required system commands are available'); console.error(' • Verify your system permissions'); console.error(' • Try running the command again'); } } else { console.error('❌ Unexpected Error'); console.error(` ${error?.message ?? 'Unknown error occurred'}`); console.error(''); console.error('💡 Suggestions:'); console.error(' • This might be a bug - please report it if the issue persists'); console.error(' • Try running the command again'); console.error(' • Check that your system meets the requirements'); if (process.env.NODE_ENV === 'development' || process.env.DEBUG) { console.error(''); console.error('🔍 Debug Information:'); console.error(error?.stack ?? 'No stack trace available'); } } console.error(''); // Add spacing after error } /** * Parse command line arguments and execute * @param argv - Command line arguments */ async run(argv = process.argv) { try { await this.program.parseAsync(argv); } catch (error) { this.handleError(error); process.exit(1); } } } exports.CLI = CLI; //# sourceMappingURL=cli.js.map