npm-killer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # npm-killer
2
+
3
+ Interactive CLI tool to find and kill Node.js processes running on ports. Unlike other port killers, this tool is designed to be **interactive and developer-friendly**.
4
+
5
+ ## Features
6
+
7
+ - 🎯 **Interactive Mode** - Visual process selection with checkboxes
8
+ - 🔍 **Smart Detection** - Finds only Node.js processes (node, npm, yarn, pnpm, bun)
9
+ - 📋 **List Mode** - See all Node.js processes with their ports
10
+ - ⚡ **Quick Kill** - Kill by port number or kill all at once
11
+ - 🛡️ **Safe by Default** - Confirmation prompts before killing
12
+ - 🎨 **Beautiful Output** - Color-coded, easy to read
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install -g npm-killer
18
+ ```
19
+
20
+ Or use with npx:
21
+ ```bash
22
+ npx npm-killer
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Interactive Mode (Default)
28
+ ```bash
29
+ npm-killer
30
+ ```
31
+ Shows a checklist of all Node.js processes with open ports. Select multiple processes with spacebar, press Enter to confirm.
32
+
33
+ ### List Processes
34
+ ```bash
35
+ npm-killer --list
36
+ # or
37
+ npm-killer -l
38
+ ```
39
+
40
+ ### Kill by Port
41
+ ```bash
42
+ npm-killer --port 3000
43
+ # or
44
+ npm-killer -p 3000
45
+ ```
46
+
47
+ ### Kill All Node.js Processes
48
+ ```bash
49
+ npm-killer --all
50
+ # or
51
+ npm-killer -a
52
+ ```
53
+
54
+ ### Force Kill (No Confirmation)
55
+ ```bash
56
+ npm-killer --port 3000 --force
57
+ npm-killer -p 3000 -f
58
+
59
+ npm-killer --all --force
60
+ npm-killer -a -f
61
+ ```
62
+
63
+ ## Example Output
64
+
65
+ ```
66
+ $ npm-killer
67
+
68
+ Select processes to kill:
69
+
70
+ ✓ 1. node (PID: 12345) - Ports: 3000, 3001
71
+ ✓ 2. npm (PID: 12346) - Ports: 8080
72
+ ──────────────
73
+ ◯ Kill ALL Node.js processes
74
+ ◯ Exit without killing
75
+
76
+ Kill 2 process(es)? (y/N) y
77
+
78
+ ✓ Killed node (PID: 12345)
79
+ ✓ Killed npm (PID: 12346)
80
+ ```
81
+
82
+ ## Why npm-killer?
83
+
84
+ Existing tools like `fuser`, `lsof`, or `kill-port` require you to know the port or PID upfront. `npm-killer` is built for developers who:
85
+ - Run multiple Node.js services and forget which is on which port
86
+ - Want a visual overview before killing anything
87
+ - Prefer interactive selection over memorizing commands
88
+ - Work with npm, yarn, pnpm, bun - all detected automatically
89
+
90
+ ## Requirements
91
+
92
+ - Node.js 18+
93
+ - macOS, Linux, or Windows (WSL)
94
+
95
+ ## License
96
+
97
+ MIT
package/bin/index.js ADDED
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { program } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { findNodeProcesses, killProcess, interactiveKill } from '../lib/port-killer.js';
6
+
7
+ program
8
+ .name('npm-killer')
9
+ .description('Interactive CLI tool to kill Node.js processes on ports')
10
+ .version('1.0.0')
11
+ .option('-p, --port <port>', 'Kill process on specific port')
12
+ .option('-a, --all', 'Kill all Node.js processes')
13
+ .option('-l, --list', 'List all Node.js processes with ports')
14
+ .option('-f, --force', 'Force kill without confirmation')
15
+ .action(async (options) => {
16
+ try {
17
+ if (options.list) {
18
+ await listProcesses();
19
+ return;
20
+ }
21
+
22
+ if (options.port) {
23
+ await killByPort(options.port, options.force);
24
+ return;
25
+ }
26
+
27
+ if (options.all) {
28
+ await killAll(options.force);
29
+ return;
30
+ }
31
+
32
+ // Default: interactive mode
33
+ await interactiveMode();
34
+ } catch (error) {
35
+ console.error(chalk.red('Error:'), error.message);
36
+ process.exit(1);
37
+ }
38
+ });
39
+
40
+ async function listProcesses() {
41
+ const processes = await findNodeProcesses();
42
+
43
+ if (processes.length === 0) {
44
+ console.log(chalk.yellow('No Node.js processes found with open ports.'));
45
+ return;
46
+ }
47
+
48
+ console.log(chalk.bold('\n Node.js Processes with Open Ports:\n'));
49
+
50
+ // Calculate column widths
51
+ const maxNameLen = Math.max(...processes.map(p => p.name.length), 4);
52
+ const maxPidLen = Math.max(...processes.map(p => String(p.pid).length), 3);
53
+
54
+ // Header
55
+ console.log(` ${chalk.cyan('#'.padEnd(3))} ${chalk.bold('Name'.padEnd(maxNameLen))} ${chalk.bold('PID'.padStart(maxPidLen))} ${chalk.bold('Ports')} ${chalk.gray('Command')}`);
56
+ console.log(` ${chalk.gray('─'.repeat(3))} ${chalk.gray('─'.repeat(maxNameLen))} ${chalk.gray('─'.repeat(maxPidLen))} ${chalk.gray('─────')} ${chalk.gray('───────')}`);
57
+
58
+ processes.forEach((proc, index) => {
59
+ const ports = proc.ports.length > 0 ? proc.ports.join(',') : '—';
60
+ // Truncate command to fit
61
+ const cmd = proc.cmd.length > 60 ? proc.cmd.substring(0, 57) + '...' : proc.cmd;
62
+ const name = proc.name.length > maxNameLen ? proc.name.substring(0, maxNameLen - 1) + '…' : proc.name.padEnd(maxNameLen);
63
+ console.log(` ${chalk.cyan(String(index + 1).padEnd(3))} ${chalk.white(name)} ${chalk.green(String(proc.pid).padStart(maxPidLen))} ${chalk.yellow(ports.padEnd(5))} ${chalk.gray(cmd)}`);
64
+ });
65
+ console.log();
66
+ }
67
+
68
+ async function killByPort(port, force) {
69
+ const processes = await findNodeProcesses();
70
+ const target = processes.find(p => p.ports.includes(parseInt(port)));
71
+
72
+ if (!target) {
73
+ console.log(chalk.yellow(`No Node.js process found on port ${port}`));
74
+ return;
75
+ }
76
+
77
+ if (!force) {
78
+ const inquirer = (await import('inquirer')).default;
79
+ const answer = await inquirer.prompt([{
80
+ type: 'confirm',
81
+ name: 'confirm',
82
+ message: `Kill ${target.name} (PID: ${target.pid}) on port ${port}?`,
83
+ default: false
84
+ }]);
85
+
86
+ if (!answer.confirm) {
87
+ console.log(chalk.gray('Cancelled.'));
88
+ return;
89
+ }
90
+ }
91
+
92
+ await killProcess(target.pid);
93
+ console.log(chalk.green(`✓ Killed ${target.name} (PID: ${target.pid}) on port ${port}`));
94
+ }
95
+
96
+ async function killAll(force) {
97
+ const processes = await findNodeProcesses();
98
+
99
+ if (processes.length === 0) {
100
+ console.log(chalk.yellow('No Node.js processes found with open ports.'));
101
+ return;
102
+ }
103
+
104
+ if (!force) {
105
+ const inquirer = (await import('inquirer')).default;
106
+ const answer = await inquirer.prompt([{
107
+ type: 'confirm',
108
+ name: 'confirm',
109
+ message: `Kill all ${processes.length} Node.js process(es)?`,
110
+ default: false
111
+ }]);
112
+
113
+ if (!answer.confirm) {
114
+ console.log(chalk.gray('Cancelled.'));
115
+ return;
116
+ }
117
+ }
118
+
119
+ for (const proc of processes) {
120
+ try {
121
+ await killProcess(proc.pid);
122
+ console.log(chalk.green(`✓ Killed ${proc.name} (PID: ${proc.pid})`));
123
+ } catch (error) {
124
+ console.error(chalk.red(`✗ Failed to kill ${proc.name} (PID: ${proc.pid}):`), error.message);
125
+ }
126
+ }
127
+ }
128
+
129
+ async function interactiveMode() {
130
+ await interactiveKill();
131
+ }
132
+
133
+ program.parse();
@@ -0,0 +1,166 @@
1
+ import { execSync } from 'child_process';
2
+ import psList from 'ps-list';
3
+ import inquirer from 'inquirer';
4
+ import chalk from 'chalk';
5
+
6
+ export async function findNodeProcesses() {
7
+ const processes = await psList();
8
+
9
+ const nodeProcesses = processes.filter(proc => {
10
+ const name = proc.name.toLowerCase();
11
+ const cmd = proc.cmd.toLowerCase();
12
+ return name === 'node' ||
13
+ name.includes('node') ||
14
+ name.includes('next-server') ||
15
+ cmd.includes('node') ||
16
+ cmd.includes('npm') ||
17
+ cmd.includes('yarn') ||
18
+ cmd.includes('pnpm') ||
19
+ cmd.includes('bun');
20
+ });
21
+
22
+ const processesWithPorts = [];
23
+
24
+ for (const proc of nodeProcesses) {
25
+ const ports = await getProcessPorts(proc.pid);
26
+ if (ports.length > 0) {
27
+ processesWithPorts.push({
28
+ pid: proc.pid,
29
+ name: proc.name,
30
+ cmd: proc.cmd,
31
+ ports
32
+ });
33
+ }
34
+ }
35
+
36
+ return processesWithPorts;
37
+ }
38
+
39
+ export async function getProcessPorts(pid) {
40
+ try {
41
+ const output = execSync(`lsof -i -P -p ${pid} 2>/dev/null`, {
42
+ encoding: 'utf-8',
43
+ stdio: ['ignore', 'pipe', 'ignore']
44
+ });
45
+
46
+ const ports = [];
47
+ const lines = output.trim().split('\n');
48
+ const pidStr = String(pid);
49
+
50
+ for (const line of lines) {
51
+ // Match lines where column 2 (PID) matches our target PID and it's LISTENing
52
+ // lsof format: COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
53
+ const parts = line.trim().split(/\s+/);
54
+ if (parts.length >= 2 && parts[1] === pidStr) {
55
+ const match = line.match(/:(\d+)\s+\(LISTEN\)$/);
56
+ if (match) {
57
+ ports.push(parseInt(match[1]));
58
+ }
59
+ }
60
+ }
61
+
62
+ return [...new Set(ports)];
63
+ } catch (error) {
64
+ return [];
65
+ }
66
+ }
67
+
68
+ export async function killProcess(pid, signal = 'SIGTERM') {
69
+ try {
70
+ process.kill(pid, signal);
71
+
72
+ // Wait a bit and check if process is still alive
73
+ await new Promise(resolve => setTimeout(resolve, 500));
74
+
75
+ try {
76
+ process.kill(pid, 0); // Check if process exists
77
+ // If we get here, process still exists, force kill
78
+ if (signal !== 'SIGKILL') {
79
+ return killProcess(pid, 'SIGKILL');
80
+ }
81
+ } catch (e) {
82
+ // Process is dead, success
83
+ return true;
84
+ }
85
+ } catch (error) {
86
+ throw new Error(`Failed to kill process ${pid}: ${error.message}`);
87
+ }
88
+ }
89
+
90
+ export async function interactiveKill() {
91
+ const processes = await findNodeProcesses();
92
+
93
+ if (processes.length === 0) {
94
+ console.log(chalk.yellow('\n No Node.js processes found with open ports.\n'));
95
+ return;
96
+ }
97
+
98
+ console.log(chalk.bold('\n Select processes to kill:\n'));
99
+
100
+ const choices = processes.map((proc, index) => {
101
+ const ports = proc.ports.length > 0 ? proc.ports.join(', ') : 'none';
102
+ return {
103
+ name: `${chalk.cyan(index + 1)}. ${chalk.white(proc.name)} ${chalk.gray(`(PID: ${proc.pid})`)} - ${chalk.yellow('Ports:')} ${chalk.yellow(ports)}`,
104
+ value: proc,
105
+ short: `${proc.name} (${proc.pid})`
106
+ };
107
+ });
108
+
109
+ choices.push(new inquirer.Separator());
110
+ choices.push({
111
+ name: chalk.red('Kill ALL Node.js processes'),
112
+ value: 'ALL'
113
+ });
114
+ choices.push({
115
+ name: chalk.gray('Exit without killing'),
116
+ value: 'EXIT'
117
+ });
118
+
119
+ const { selected } = await inquirer.prompt([
120
+ {
121
+ type: 'checkbox',
122
+ name: 'selected',
123
+ message: 'Use space to select, enter to confirm:',
124
+ choices,
125
+ validate: (answer) => {
126
+ if (answer.length === 0) {
127
+ return 'Select at least one process or choose Exit';
128
+ }
129
+ return true;
130
+ }
131
+ }
132
+ ]);
133
+
134
+ if (selected.includes('EXIT') || selected.length === 0) {
135
+ console.log(chalk.gray('\n Cancelled.\n'));
136
+ return;
137
+ }
138
+
139
+ const toKill = selected.includes('ALL') ? processes : selected.filter(s => s !== 'ALL');
140
+
141
+ // Final confirmation
142
+ const { confirm } = await inquirer.prompt([
143
+ {
144
+ type: 'confirm',
145
+ name: 'confirm',
146
+ message: `Kill ${toKill.length} process(es)?`,
147
+ default: false
148
+ }
149
+ ]);
150
+
151
+ if (!confirm) {
152
+ console.log(chalk.gray('\n Cancelled.\n'));
153
+ return;
154
+ }
155
+
156
+ console.log('');
157
+ for (const proc of toKill) {
158
+ try {
159
+ await killProcess(proc.pid);
160
+ console.log(chalk.green(` ✓ Killed ${proc.name} (PID: ${proc.pid})`));
161
+ } catch (error) {
162
+ console.error(chalk.red(` ✗ Failed to kill ${proc.name} (PID: ${proc.pid}):`), error.message);
163
+ }
164
+ }
165
+ console.log('');
166
+ }
Binary file
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "npm-killer",
3
+ "version": "1.0.0",
4
+ "description": "Interactive CLI tool to kill Node.js processes on ports",
5
+ "main": "bin/index.js",
6
+ "bin": {
7
+ "npm-killer": "bin/index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/index.js",
11
+ "dev": "node bin/index.js",
12
+ "test": "node --test test/*.test.js",
13
+ "prepublishOnly": "npm test"
14
+ },
15
+ "keywords": [
16
+ "cli",
17
+ "port",
18
+ "kill",
19
+ "node",
20
+ "process",
21
+ "interactive"
22
+ ],
23
+ "author": "",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "commander": "^12.0.0",
27
+ "chalk": "^5.3.0",
28
+ "inquirer": "^9.2.0",
29
+ "ps-list": "^8.1.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ },
34
+ "type": "module"
35
+ }
@@ -0,0 +1,75 @@
1
+ import { describe, it, before, after } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { spawn } from 'child_process';
4
+ import { findNodeProcesses, killProcess, getProcessPorts } from '../lib/port-killer.js';
5
+
6
+ describe('port-killer', () => {
7
+ let testServer;
8
+ let testPort = 34567;
9
+
10
+ before(async () => {
11
+ // Start a test HTTP server
12
+ testServer = spawn('node', ['-e', `
13
+ const http = require('http');
14
+ const server = http.createServer((req, res) => res.end('test'));
15
+ server.listen(${testPort}, () => console.log('Server started on port ${testPort}'));
16
+ `], { stdio: ['ignore', 'pipe', 'pipe'] });
17
+
18
+ // Wait for server to start
19
+ await new Promise(resolve => setTimeout(resolve, 1500));
20
+ });
21
+
22
+ after(async () => {
23
+ if (testServer) {
24
+ testServer.kill();
25
+ await new Promise(resolve => setTimeout(resolve, 500));
26
+ }
27
+ });
28
+
29
+ it('should find Node.js processes with open ports', async () => {
30
+ const processes = await findNodeProcesses();
31
+ assert.ok(Array.isArray(processes));
32
+
33
+ // Our test server should be in the list
34
+ const testProc = processes.find(p => p.ports.includes(testPort));
35
+ assert.ok(testProc, `Should find test process on port ${testPort}`);
36
+ assert.ok(testProc.pid > 0);
37
+ // Name might be 'node' or include 'node'
38
+ assert.ok(testProc.name.toLowerCase() === 'node' || testProc.name.toLowerCase().includes('node'));
39
+ });
40
+
41
+ it('should get process ports correctly', async () => {
42
+ const processes = await findNodeProcesses();
43
+ const testProc = processes.find(p => p.ports.includes(testPort));
44
+
45
+ if (testProc) {
46
+ const ports = await getProcessPorts(testProc.pid);
47
+ assert.ok(ports.includes(testPort));
48
+ }
49
+ });
50
+
51
+ it('should kill a process by PID', async () => {
52
+ // Start another test process to kill
53
+ const killServer = spawn('node', ['-e', `
54
+ const http = require('http');
55
+ const server = http.createServer((req, res) => res.end('test'));
56
+ server.listen(34568, () => console.log('Server started on port 34568'));
57
+ `], { stdio: ['ignore', 'pipe', 'pipe'] });
58
+
59
+ await new Promise(resolve => setTimeout(resolve, 1500));
60
+
61
+ const processes = await findNodeProcesses();
62
+ const killProc = processes.find(p => p.ports.includes(34568));
63
+
64
+ assert.ok(killProc, 'Should find process to kill');
65
+
66
+ await killProcess(killProc.pid);
67
+
68
+ // Verify it's dead
69
+ await new Promise(resolve => setTimeout(resolve, 500));
70
+
71
+ const processesAfter = await findNodeProcesses();
72
+ const deadProc = processesAfter.find(p => p.pid === killProc.pid);
73
+ assert.strictEqual(deadProc, undefined, 'Process should be dead');
74
+ });
75
+ });