portfree
Version:
A cross-platform CLI tool for managing processes running on specific ports
443 lines (439 loc) • 18.9 kB
JavaScript
;
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