port-nuker 1.0.3 → 1.0.5

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.
Files changed (3) hide show
  1. package/README.md +48 -0
  2. package/index.js +272 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -32,6 +32,9 @@ nuke <PORT>
32
32
  # Kill and wait until port is actually free
33
33
  nuke <PORT> --wait
34
34
 
35
+ # Force kill even on protected ports
36
+ nuke <PORT> --force
37
+
35
38
  # List all active ports and select one to kill interactively
36
39
  nuke list
37
40
  ```
@@ -87,6 +90,8 @@ nuke 5000 --wait && echo "Port is free!"
87
90
  ## Features
88
91
 
89
92
  - **Cross-Platform**: Works on Windows (using `netstat` + `taskkill`) and Unix-like systems (Linux/macOS using `lsof` + `kill`)
93
+ - **Docker Awareness**: Detects when a port is held by Docker and offers to stop the specific container instead of killing the entire Docker daemon
94
+ - **Safe Mode Protection**: Protected ports (22, 80, 443, 3306, 5432, 6379, 27017, 5000, 8080) require `--force` flag to prevent accidental termination of critical services
90
95
  - **Interactive Mode**: Browse all active ports with `nuke list` and select which one to kill
91
96
  - **Wait Mode**: Block until port is actually released with `--wait` flag for safe command chaining
92
97
  - **Detailed Process Info**: See PID, command name, user, memory usage, and protocol for each port
@@ -113,6 +118,49 @@ nuke 8080
113
118
  node server.js
114
119
  ```
115
120
 
121
+ ### Protected Ports (Safe Mode)
122
+
123
+ ```bash
124
+ # Attempting to kill a protected port (e.g., SSH on 22)
125
+ nuke 22
126
+ # Output:
127
+ # ⚠️ Port 22 is protected (SSH).
128
+ # This port is typically used for critical services.
129
+ # Use --force to override: nuke 22 --force
130
+
131
+ # Force kill a protected port
132
+ nuke 443 --force
133
+ # Successfully nukes the process on port 443 (HTTPS)
134
+
135
+ # Protected ports: 22 (SSH), 80 (HTTP), 443 (HTTPS), 3306 (MySQL),
136
+ # 5432 (PostgreSQL), 6379 (Redis), 27017 (MongoDB),
137
+ # 5000 (Flask/Docker), 8080 (Alt HTTP)
138
+ ```
139
+
140
+ ### Docker Containers
141
+
142
+ ```bash
143
+ # When a port is held by Docker, port-nuker detects it automatically
144
+ nuke 3000
145
+
146
+ # Output:
147
+ # 🐳 Detected Docker process (PID: 12345)
148
+ # Searching for container using this port...
149
+ #
150
+ # 📦 Found container using port 3000:
151
+ # Name: my-app
152
+ # ID: abc123def456
153
+ # Ports: 0.0.0.0:3000->3000/tcp
154
+ #
155
+ # What would you like to do?
156
+ # 1. Stop container 'my-app' (recommended)
157
+ # 2. ⚠️ Kill Docker daemon (stops ALL containers)
158
+ # 3. Cancel
159
+
160
+ # Selecting option 1 stops only the specific container
161
+ # ✅ Successfully stopped container abc123def456
162
+ ```
163
+
116
164
  ## Requirements
117
165
 
118
166
  - Node.js >= 12.0.0
package/index.js CHANGED
@@ -5,16 +5,34 @@ const os = require('os');
5
5
  const inquirer = require('inquirer');
6
6
  const Table = require('cli-table3');
7
7
 
8
+ // Protected ports that require --force flag
9
+ const SAFE_PORTS = [22, 80, 443, 3306, 5432, 6379, 27017, 5000, 8080];
10
+
11
+ const SAFE_PORT_DESCRIPTIONS = {
12
+ 22: 'SSH',
13
+ 80: 'HTTP',
14
+ 443: 'HTTPS',
15
+ 3306: 'MySQL',
16
+ 5432: 'PostgreSQL',
17
+ 6379: 'Redis',
18
+ 27017: 'MongoDB',
19
+ 5000: 'Flask/Docker Registry',
20
+ 8080: 'Alternative HTTP'
21
+ };
22
+
8
23
  const args = process.argv.slice(2);
9
24
 
10
25
  // Parse arguments
11
26
  let port = null;
12
27
  let shouldWait = false;
28
+ let shouldForce = false;
13
29
  let command = null;
14
30
 
15
31
  for (let i = 0; i < args.length; i++) {
16
32
  if (args[i] === '--wait') {
17
33
  shouldWait = true;
34
+ } else if (args[i] === '--force') {
35
+ shouldForce = true;
18
36
  } else if (args[i] === 'list') {
19
37
  command = 'list';
20
38
  } else if (!isNaN(args[i])) {
@@ -26,16 +44,179 @@ for (let i = 0; i < args.length; i++) {
26
44
  if (command === 'list') {
27
45
  listPorts();
28
46
  } else if (port) {
29
- // nuke <PORT> [--wait]
30
- nukePort(port, shouldWait);
47
+ // nuke <PORT> [--wait] [--force]
48
+ nukePort(port, shouldWait, shouldForce);
31
49
  } else {
32
50
  console.error('Usage:');
33
- console.error(' nuke <PORT> Kill process on specific port');
34
- console.error(' nuke <PORT> --wait Kill process and wait until port is free');
35
- console.error(' nuke list List all active ports and select one to kill');
51
+ console.error(' nuke <PORT> Kill process on specific port');
52
+ console.error(' nuke <PORT> --wait Kill process and wait until port is free');
53
+ console.error(' nuke <PORT> --force Force kill even on protected ports');
54
+ console.error(' nuke list List all active ports and select one to kill');
55
+ console.error('');
56
+ console.error('Protected ports (require --force): 22, 80, 443, 3306, 5432, 6379, 27017, 5000, 8080');
36
57
  process.exit(1);
37
58
  }
38
59
 
60
+ // Check if a port is protected and requires --force flag
61
+ function checkSafePort(port, force) {
62
+ const portNum = parseInt(port);
63
+
64
+ if (SAFE_PORTS.includes(portNum) && !force) {
65
+ const description = SAFE_PORT_DESCRIPTIONS[portNum] || 'System Service';
66
+ console.error(`\n⚠️ Port ${port} is protected (${description}).`);
67
+ console.error(` This port is typically used for critical services.`);
68
+ console.error(` Use --force to override: nuke ${port} --force\n`);
69
+ return false;
70
+ }
71
+
72
+ return true;
73
+ }
74
+
75
+ // Check if a process is Docker-related
76
+ function isDockerProcess(commandName) {
77
+ if (!commandName) return false;
78
+
79
+ const dockerProcessNames = [
80
+ 'docker',
81
+ 'dockerd',
82
+ 'com.docker.backend',
83
+ 'docker desktop',
84
+ 'containerd',
85
+ 'docker.exe'
86
+ ];
87
+
88
+ const lowerCommand = commandName.toLowerCase();
89
+ return dockerProcessNames.some(name => lowerCommand.includes(name));
90
+ }
91
+
92
+ // Find Docker container using a specific port
93
+ async function findDockerContainerByPort(port) {
94
+ return new Promise((resolve) => {
95
+ exec('docker ps --format "{{.ID}}|{{.Names}}|{{.Ports}}"', (error, stdout) => {
96
+ if (error) {
97
+ resolve(null);
98
+ return;
99
+ }
100
+
101
+ const lines = stdout.trim().split('\n');
102
+
103
+ for (const line of lines) {
104
+ if (!line) continue;
105
+
106
+ const [id, name, ports] = line.split('|');
107
+
108
+ // Check if this container has the target port
109
+ // Formats: 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, etc.
110
+ const portRegex = new RegExp(`[:\\s]${port}->|[:\\s]${port}/`);
111
+
112
+ if (ports && portRegex.test(ports)) {
113
+ resolve({ id, name, ports });
114
+ return;
115
+ }
116
+ }
117
+
118
+ resolve(null);
119
+ });
120
+ });
121
+ }
122
+
123
+ // Handle Docker process - offer to stop container instead of killing daemon
124
+ async function handleDockerProcess(port, pid, processCommand) {
125
+ console.log(`\n🐳 Detected Docker process (PID: ${pid})`);
126
+ console.log(' Searching for container using this port...\n');
127
+
128
+ const container = await findDockerContainerByPort(port);
129
+
130
+ if (!container) {
131
+ console.log('⚠️ Could not find a specific container using this port.');
132
+ console.log(' The port might be used by Docker\'s internal networking.\n');
133
+
134
+ const answer = await inquirer.prompt([
135
+ {
136
+ type: 'list',
137
+ name: 'action',
138
+ message: 'What would you like to do?',
139
+ choices: [
140
+ { name: 'Cancel (recommended)', value: 'cancel' },
141
+ { name: '⚠️ Kill Docker daemon (stops ALL containers)', value: 'kill' }
142
+ ]
143
+ }
144
+ ]);
145
+
146
+ if (answer.action === 'kill') {
147
+ const confirm = await inquirer.prompt([
148
+ {
149
+ type: 'confirm',
150
+ name: 'confirmed',
151
+ message: '⚠️ This will stop ALL Docker containers. Are you sure?',
152
+ default: false
153
+ }
154
+ ]);
155
+
156
+ if (confirm.confirmed) {
157
+ killPid(pid);
158
+ } else {
159
+ console.log('Cancelled.');
160
+ }
161
+ } else {
162
+ console.log('Cancelled.');
163
+ }
164
+ return;
165
+ }
166
+
167
+ // Found a container
168
+ console.log(`📦 Found container using port ${port}:`);
169
+ console.log(` Name: ${container.name}`);
170
+ console.log(` ID: ${container.id}`);
171
+ console.log(` Ports: ${container.ports}\n`);
172
+
173
+ const answer = await inquirer.prompt([
174
+ {
175
+ type: 'list',
176
+ name: 'action',
177
+ message: 'What would you like to do?',
178
+ choices: [
179
+ { name: `Stop container '${container.name}' (recommended)`, value: 'stop' },
180
+ { name: '⚠️ Kill Docker daemon (stops ALL containers)', value: 'kill' },
181
+ { name: 'Cancel', value: 'cancel' }
182
+ ]
183
+ }
184
+ ]);
185
+
186
+ if (answer.action === 'stop') {
187
+ console.log(`\nStopping container '${container.name}'...`);
188
+ stopDockerContainer(container.id);
189
+ } else if (answer.action === 'kill') {
190
+ const confirm = await inquirer.prompt([
191
+ {
192
+ type: 'confirm',
193
+ name: 'confirmed',
194
+ message: '⚠️ This will stop ALL Docker containers. Are you sure?',
195
+ default: false
196
+ }
197
+ ]);
198
+
199
+ if (confirm.confirmed) {
200
+ killPid(pid);
201
+ } else {
202
+ console.log('Cancelled.');
203
+ }
204
+ } else {
205
+ console.log('Cancelled.');
206
+ }
207
+ }
208
+
209
+ // Stop a Docker container
210
+ function stopDockerContainer(containerId) {
211
+ exec(`docker stop ${containerId}`, (error, stdout, stderr) => {
212
+ if (error) {
213
+ console.error(`Failed to stop container: ${error.message}`);
214
+ return;
215
+ }
216
+ console.log(`✅ Successfully stopped container ${containerId}`);
217
+ });
218
+ }
219
+
39
220
  // List all active ports with process details
40
221
  function listPorts() {
41
222
  const platform = os.platform();
@@ -235,11 +416,22 @@ async function promptKillProcess(processes) {
235
416
  if (answer.pid) {
236
417
  const selectedProcess = processes.find(p => p.pid === answer.pid);
237
418
 
419
+ // Check if port is protected
420
+ const portNum = parseInt(selectedProcess.port);
421
+ const isProtected = SAFE_PORTS.includes(portNum);
422
+
423
+ let confirmMessage = `Kill process ${selectedProcess.command} (PID: ${answer.pid}) on port ${selectedProcess.port}?`;
424
+
425
+ if (isProtected) {
426
+ const description = SAFE_PORT_DESCRIPTIONS[portNum] || 'System Service';
427
+ confirmMessage = `⚠️ Port ${selectedProcess.port} is protected (${description}).\n Kill process ${selectedProcess.command} (PID: ${answer.pid})?`;
428
+ }
429
+
238
430
  const confirm = await inquirer.prompt([
239
431
  {
240
432
  type: 'confirm',
241
433
  name: 'confirmed',
242
- message: `Kill process ${selectedProcess.command} (PID: ${answer.pid}) on port ${selectedProcess.port}?`,
434
+ message: confirmMessage,
243
435
  default: false
244
436
  }
245
437
  ]);
@@ -335,7 +527,12 @@ function pollPortUntilFree(port, maxWaitSeconds = 30) {
335
527
  }
336
528
 
337
529
  // Original nuke port function
338
- function nukePort(port, shouldWait = false) {
530
+ function nukePort(port, shouldWait = false, shouldForce = false) {
531
+ // Check if port is protected
532
+ if (!checkSafePort(port, shouldForce)) {
533
+ process.exit(1);
534
+ }
535
+
339
536
  const platform = os.platform();
340
537
  let findCommand;
341
538
 
@@ -402,17 +599,76 @@ function nukePort(port, shouldWait = false) {
402
599
  return;
403
600
  }
404
601
 
405
- pids.forEach(pid => {
406
- console.log(`Found process with PID: ${pid}. Nuking it...`);
407
- killPid(pid, () => {
408
- if (shouldWait) {
409
- // Wait a moment for the process to fully terminate
410
- setTimeout(() => {
411
- pollPortUntilFree(port);
412
- }, 1000);
602
+ // Get process details to check for Docker
603
+ const pidArray = Array.from(pids);
604
+
605
+ if (platform === 'win32') {
606
+ // Get process details on Windows
607
+ const tasklistCmd = `tasklist /FI "PID eq ${pidArray.join('" /FI "PID eq ')}" /FO CSV /NH`;
608
+
609
+ exec(tasklistCmd, async (error, tasklistOutput) => {
610
+ let processCommand = null;
611
+
612
+ if (!error && tasklistOutput) {
613
+ const lines = tasklistOutput.trim().split('\n');
614
+ if (lines.length > 0) {
615
+ const matches = lines[0].match(/"([^"]+)"/g);
616
+ if (matches && matches.length > 0) {
617
+ processCommand = matches[0].replace(/"/g, '');
618
+ }
619
+ }
620
+ }
621
+
622
+ // Check if it's a Docker process
623
+ if (processCommand && isDockerProcess(processCommand)) {
624
+ await handleDockerProcess(port, pidArray[0], processCommand);
625
+ } else {
626
+ // Normal kill
627
+ pidArray.forEach(pid => {
628
+ console.log(`Found process with PID: ${pid}. Nuking it...`);
629
+ killPid(pid, () => {
630
+ if (shouldWait) {
631
+ setTimeout(() => {
632
+ pollPortUntilFree(port);
633
+ }, 1000);
634
+ }
635
+ });
636
+ });
413
637
  }
414
638
  });
415
- });
639
+ } else {
640
+ // Unix-like: use lsof to get process command
641
+ exec(`lsof -i :${port} -n -P`, async (error, lsofOutput) => {
642
+ let processCommand = null;
643
+
644
+ if (!error && lsofOutput) {
645
+ const lines = lsofOutput.trim().split('\n');
646
+ if (lines.length > 1) {
647
+ const parts = lines[1].trim().split(/\s+/);
648
+ if (parts.length > 0) {
649
+ processCommand = parts[0];
650
+ }
651
+ }
652
+ }
653
+
654
+ // Check if it's a Docker process
655
+ if (processCommand && isDockerProcess(processCommand)) {
656
+ await handleDockerProcess(port, pidArray[0], processCommand);
657
+ } else {
658
+ // Normal kill
659
+ pidArray.forEach(pid => {
660
+ console.log(`Found process with PID: ${pid}. Nuking it...`);
661
+ killPid(pid, () => {
662
+ if (shouldWait) {
663
+ setTimeout(() => {
664
+ pollPortUntilFree(port);
665
+ }, 1000);
666
+ }
667
+ });
668
+ });
669
+ }
670
+ });
671
+ }
416
672
  });
417
673
  }
418
674
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "port-nuker",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "A simple CLI utility to kill processes holding a specific port. Solves EADDRINUSE errors instantly.",
5
5
  "main": "index.js",
6
6
  "bin": {