port-doctor-cli 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/LICENSE +21 -0
- package/README.md +266 -0
- package/bin/port-doctor.js +8 -0
- package/dist/cli/commands/free.d.ts +3 -0
- package/dist/cli/commands/free.d.ts.map +1 -0
- package/dist/cli/commands/free.js +120 -0
- package/dist/cli/commands/free.js.map +1 -0
- package/dist/cli/commands/overview.d.ts +3 -0
- package/dist/cli/commands/overview.d.ts.map +1 -0
- package/dist/cli/commands/overview.js +19 -0
- package/dist/cli/commands/overview.js.map +1 -0
- package/dist/cli/commands/run.d.ts +4 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +133 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/why.d.ts +3 -0
- package/dist/cli/commands/why.d.ts.map +1 -0
- package/dist/cli/commands/why.js +24 -0
- package/dist/cli/commands/why.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +200 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output/formatter.d.ts +6 -0
- package/dist/cli/output/formatter.d.ts.map +1 -0
- package/dist/cli/output/formatter.js +177 -0
- package/dist/cli/output/formatter.js.map +1 -0
- package/dist/cli/output/json.d.ts +2 -0
- package/dist/cli/output/json.d.ts.map +1 -0
- package/dist/cli/output/json.js +6 -0
- package/dist/cli/output/json.js.map +1 -0
- package/dist/cli/output/tree.d.ts +3 -0
- package/dist/cli/output/tree.d.ts.map +1 -0
- package/dist/cli/output/tree.js +19 -0
- package/dist/cli/output/tree.js.map +1 -0
- package/dist/cli/prompts/confirm.d.ts +3 -0
- package/dist/cli/prompts/confirm.d.ts.map +1 -0
- package/dist/cli/prompts/confirm.js +53 -0
- package/dist/cli/prompts/confirm.js.map +1 -0
- package/dist/core/diagnosis/index.d.ts +4 -0
- package/dist/core/diagnosis/index.d.ts.map +1 -0
- package/dist/core/diagnosis/index.js +109 -0
- package/dist/core/diagnosis/index.js.map +1 -0
- package/dist/core/framework/index.d.ts +5 -0
- package/dist/core/framework/index.d.ts.map +1 -0
- package/dist/core/framework/index.js +211 -0
- package/dist/core/framework/index.js.map +1 -0
- package/dist/core/git/index.d.ts +3 -0
- package/dist/core/git/index.d.ts.map +1 -0
- package/dist/core/git/index.js +56 -0
- package/dist/core/git/index.js.map +1 -0
- package/dist/core/lifecycle/index.d.ts +8 -0
- package/dist/core/lifecycle/index.d.ts.map +1 -0
- package/dist/core/lifecycle/index.js +175 -0
- package/dist/core/lifecycle/index.js.map +1 -0
- package/dist/core/process/index.d.ts +9 -0
- package/dist/core/process/index.d.ts.map +1 -0
- package/dist/core/process/index.js +94 -0
- package/dist/core/process/index.js.map +1 -0
- package/dist/core/project/index.d.ts +3 -0
- package/dist/core/project/index.d.ts.map +1 -0
- package/dist/core/project/index.js +84 -0
- package/dist/core/project/index.js.map +1 -0
- package/dist/core/scanner/index.d.ts +5 -0
- package/dist/core/scanner/index.d.ts.map +1 -0
- package/dist/core/scanner/index.js +71 -0
- package/dist/core/scanner/index.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/darwin.d.ts +27 -0
- package/dist/platform/darwin.d.ts.map +1 -0
- package/dist/platform/darwin.js +268 -0
- package/dist/platform/darwin.js.map +1 -0
- package/dist/platform/index.d.ts +4 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +26 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/linux.d.ts +22 -0
- package/dist/platform/linux.d.ts.map +1 -0
- package/dist/platform/linux.js +217 -0
- package/dist/platform/linux.js.map +1 -0
- package/dist/platform/types.d.ts +40 -0
- package/dist/platform/types.d.ts.map +1 -0
- package/dist/platform/types.js +2 -0
- package/dist/platform/types.js.map +1 -0
- package/dist/platform/windows.d.ts +17 -0
- package/dist/platform/windows.d.ts.map +1 -0
- package/dist/platform/windows.js +138 -0
- package/dist/platform/windows.js.map +1 -0
- package/dist/types/index.d.ts +101 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/exec.d.ts +16 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +51 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +60 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/redaction.d.ts +5 -0
- package/dist/utils/redaction.d.ts.map +1 -0
- package/dist/utils/redaction.js +31 -0
- package/dist/utils/redaction.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Port Doctor Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Port Doctor 🩺
|
|
2
|
+
|
|
3
|
+
> Developer-first diagnostic, troubleshooting, and forensic CLI for local development ports.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/port-doctor-cli)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
**Port Doctor** answers the question every developer asks:
|
|
9
|
+
**`"Why won't my development server start?"`**
|
|
10
|
+
|
|
11
|
+
It bridges low-level kernel sockets and opaque PIDs with rich developer workspace context: **Project Name, Git Repository, Branch, Dirty Status, Framework, Process Tree, and Uptime**.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## ⚡ Why Port Doctor?
|
|
16
|
+
|
|
17
|
+
| Feature | `kill-port` | `fkill` | Raw `lsof` / `netstat` | **Port Doctor** |
|
|
18
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
19
|
+
| **Identifies Git Repo & Branch** | ❌ | ❌ | ❌ | **✅ Yes** |
|
|
20
|
+
| **Identifies Web Framework / Database** | ❌ | ❌ | ❌ | **✅ Yes** |
|
|
21
|
+
| **Shows Full Process Tree & CWD** | ❌ | ❌ | ❌ | **✅ Yes** |
|
|
22
|
+
| **Safe Graceful Termination (SIGINT first)** | ❌ (blind kill -9) | ⚠️ Partial | ❌ | **✅ Yes** |
|
|
23
|
+
| **PID Race Condition Protection** | ❌ | ❌ | ❌ | **✅ Yes** |
|
|
24
|
+
| **Auto-Healing `run` Wrapper** | ❌ | ❌ | ❌ | **✅ Yes** |
|
|
25
|
+
| **Redacts Passwords / Tokens in CLI** | ❌ | ❌ | ❌ | **✅ Yes** |
|
|
26
|
+
| **Minimal Dependencies & Fast Startup** | ⚠️ | ❌ | ⚠️ | **✅ Yes** |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🚀 Quick Start
|
|
31
|
+
|
|
32
|
+
Run directly with `npx` (using the `port-doctor-cli` package) — no global installation required:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx port-doctor-cli
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or install globally to use the `port-doctor` command anywhere:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g port-doctor-cli
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Once installed globally, you can run `port-doctor` directly:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
port-doctor
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📖 Command Reference
|
|
53
|
+
|
|
54
|
+
### 1. Default Overview: `port-doctor`
|
|
55
|
+
Scans common development and database ports (`3000-3005, 5173-5175, 8000, 8080, 5432, 6379, 27017, ...`) and displays active services.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx port-doctor-cli
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```text
|
|
62
|
+
PORT DOCTOR v1.0.0 — Local Port Forensics & Diagnostics
|
|
63
|
+
|
|
64
|
+
PORT STATUS PROCESS PID PROJECT / SERVICE UPTIME
|
|
65
|
+
──────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
3000 BUSY node 8212 storefront-ui (Next.js) 2h 14m
|
|
67
|
+
5173 FREE - - - -
|
|
68
|
+
6379 BUSY redis-server 8121 Redis 5h 30m
|
|
69
|
+
27017 FREE - - - -
|
|
70
|
+
──────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
Scanned 18 ports • 2 occupied • 16 available
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Scan custom port ranges:
|
|
75
|
+
```bash
|
|
76
|
+
npx port-doctor-cli --ports 3000,5173,8000-8080
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### 2. Forensic Diagnosis: `port-doctor why <port>`
|
|
82
|
+
The signature command. Explains exactly what process, directory, Git branch, and service is holding a port.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npx port-doctor-cli why 3000
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
● PORT 3000 IS OCCUPIED
|
|
90
|
+
|
|
91
|
+
Process
|
|
92
|
+
Name: node
|
|
93
|
+
PID: 8212 (User: developer)
|
|
94
|
+
Command: npm run dev
|
|
95
|
+
Uptime: 2h 14m
|
|
96
|
+
Memory: 148.5 MB
|
|
97
|
+
|
|
98
|
+
Project
|
|
99
|
+
Name: storefront-ui
|
|
100
|
+
Path: /Users/developer/Projects/storefront-ui
|
|
101
|
+
Git: storefront-ui
|
|
102
|
+
Branch: feature/checkout-v2 [dirty: +3 files]
|
|
103
|
+
|
|
104
|
+
Service / Framework
|
|
105
|
+
Type: Next.js (web-framework)
|
|
106
|
+
|
|
107
|
+
Process Tree
|
|
108
|
+
VS Code (PID 4102)
|
|
109
|
+
└─ zsh (PID 4210)
|
|
110
|
+
└─ npm (PID 8200)
|
|
111
|
+
└─ node (PID 8212)
|
|
112
|
+
|
|
113
|
+
Listening on
|
|
114
|
+
127.0.0.1:3000 (TCP)
|
|
115
|
+
|
|
116
|
+
Diagnosis
|
|
117
|
+
Port 3000 is occupied by Next.js (storefront-ui on branch 'feature/checkout-v2').
|
|
118
|
+
|
|
119
|
+
Suggested Action
|
|
120
|
+
Run 'port-doctor free 3000' to safely release the port.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### 3. Safe Release: `port-doctor free <port...>`
|
|
126
|
+
Safely stops the process occupying a port. It first shows process and project details, attempts graceful `SIGINT` (Ctrl+C), then `SIGTERM`, and returns an error with instructions to use `--force` if the process resists graceful exit. It also verifies process identity immediately before sending kill signals to prevent PID race conditions.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Safe interactive release with confirmation (SIGINT -> SIGTERM)
|
|
130
|
+
npx port-doctor-cli free 3000
|
|
131
|
+
|
|
132
|
+
# Release multiple ports
|
|
133
|
+
npx port-doctor-cli free 3000 8080
|
|
134
|
+
|
|
135
|
+
# Explicit force termination (SIGKILL) without confirmation
|
|
136
|
+
npx port-doctor-cli free 3000 --force
|
|
137
|
+
|
|
138
|
+
# Dry run mode (simulate what would happen)
|
|
139
|
+
npx port-doctor-cli free 3000 --dry-run
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
*(Aliases: `kill`, `release`)*
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
### 4. Auto-Healing Runner: `port-doctor run "<command>"`
|
|
147
|
+
Runs your development startup script. If the port is already occupied by a leftover process, it detects the conflict before starting and prompts you to safely release it gracefully before booting the dev server.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx port-doctor-cli run "npm run dev"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
⚠ Port 3000 is already in use.
|
|
155
|
+
Process: node (PID 8212)
|
|
156
|
+
Project: old-storefront (branch: staging)
|
|
157
|
+
Command: npm run dev
|
|
158
|
+
|
|
159
|
+
How would you like to proceed?
|
|
160
|
+
1. Stop existing process gracefully and start
|
|
161
|
+
2. Cancel
|
|
162
|
+
|
|
163
|
+
Enter your choice (1-2): 1
|
|
164
|
+
|
|
165
|
+
Attempting graceful release of port 3000...
|
|
166
|
+
✓ Port 3000 released.
|
|
167
|
+
|
|
168
|
+
Starting: npm run dev
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
### 5. Global `--json` Mode
|
|
174
|
+
Outputs pure, machine-readable JSON across all commands for script automation and CI/CD pipelines.
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npx port-doctor-cli why 3000 --json
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"port": 3000,
|
|
183
|
+
"status": "busy",
|
|
184
|
+
"protocol": "tcp",
|
|
185
|
+
"address": "127.0.0.1",
|
|
186
|
+
"allAddresses": ["127.0.0.1", "::1"],
|
|
187
|
+
"pid": 8212,
|
|
188
|
+
"process": {
|
|
189
|
+
"pid": 8212,
|
|
190
|
+
"name": "node",
|
|
191
|
+
"command": "npm run dev",
|
|
192
|
+
"cwd": "/Users/developer/Projects/storefront-ui",
|
|
193
|
+
"uptimeSeconds": 8040,
|
|
194
|
+
"uptimeFormatted": "2h 14m"
|
|
195
|
+
},
|
|
196
|
+
"project": {
|
|
197
|
+
"name": "storefront-ui",
|
|
198
|
+
"rootDirectory": "/Users/developer/Projects/storefront-ui"
|
|
199
|
+
},
|
|
200
|
+
"git": {
|
|
201
|
+
"isGitRepo": true,
|
|
202
|
+
"repoName": "storefront-ui",
|
|
203
|
+
"branch": "feature/checkout-v2",
|
|
204
|
+
"isDirty": true,
|
|
205
|
+
"uncommittedFilesCount": 3
|
|
206
|
+
},
|
|
207
|
+
"framework": {
|
|
208
|
+
"name": "Next.js",
|
|
209
|
+
"category": "web-framework",
|
|
210
|
+
"confidence": "high"
|
|
211
|
+
},
|
|
212
|
+
"diagnosis": {
|
|
213
|
+
"type": "dev_server",
|
|
214
|
+
"severity": "warning",
|
|
215
|
+
"summary": "Port 3000 is occupied by Next.js (storefront-ui on branch 'feature/checkout-v2').",
|
|
216
|
+
"safeToFree": true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
For `port-doctor run "<command>" --json`, it executes a pre-flight port collision inspection and emits a structured `{ status: "ready" | "conflict", ... }` JSON payload without corrupting `stdout`.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 💻 Programmatic Node.js API
|
|
226
|
+
|
|
227
|
+
You can also use Port Doctor inside your own Node.js / TypeScript code:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { inspectPort, releasePort, scanDevPorts } from 'port-doctor-cli';
|
|
231
|
+
|
|
232
|
+
// Inspect a specific port
|
|
233
|
+
const info = await inspectPort(3000);
|
|
234
|
+
if (info.status === 'busy') {
|
|
235
|
+
console.log(`Port 3000 is used by ${info.project?.name} (${info.framework?.name})`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Safely release a port
|
|
239
|
+
const result = await releasePort(3000, { force: false });
|
|
240
|
+
console.log(`Port released: ${result.success}`);
|
|
241
|
+
|
|
242
|
+
// Scan active dev ports
|
|
243
|
+
const devPorts = await scanDevPorts();
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 🛡️ Security & Privacy
|
|
249
|
+
|
|
250
|
+
- **Credential Redaction**: Passwords, API tokens, bearer keys, and database connection strings in process command lines are automatically masked before terminal display or JSON output.
|
|
251
|
+
- **Command Execution**: `port-doctor run "<command>"` intentionally executes the user-supplied command through the platform shell. Internal process-management and diagnostic commands use structured argument execution instead of unnecessary shell interpolation.
|
|
252
|
+
- **Graceful Shutdown & Verification**: Attempts graceful shutdown (`SIGINT` then `SIGTERM`) and verifies process identity before sending signals to prevent PID race conditions. Escalates to force-kill (`SIGKILL`) only upon explicit user request (`--force`).
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 🌐 Platform Support
|
|
257
|
+
|
|
258
|
+
- **macOS** (Apple Silicon & Intel)
|
|
259
|
+
- **Linux** (Ubuntu, Debian, Fedora, Arch, Alpine)
|
|
260
|
+
- **Windows** (Windows 10, 11, Windows Server)
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## 📄 License
|
|
265
|
+
|
|
266
|
+
MIT © Port Doctor Contributors
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"free.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/free.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAc,MAAM,sBAAsB,CAAC;AAO3D,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA2H9E"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { inspectPort } from '../../core/process/index.js';
|
|
3
|
+
import { releasePort } from '../../core/lifecycle/index.js';
|
|
4
|
+
import { formatFreeResult } from '../output/formatter.js';
|
|
5
|
+
import { printJson } from '../output/json.js';
|
|
6
|
+
import { askConfirmation } from '../prompts/confirm.js';
|
|
7
|
+
export async function handleFree(ports, args) {
|
|
8
|
+
if (!ports || ports.length === 0) {
|
|
9
|
+
if (args.json) {
|
|
10
|
+
printJson({ status: 'error', error: 'No port specified to free.' });
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
console.error(pc.red('\n✗ Error: Please specify at least one port to free (e.g. `port-doctor free 3000`).\n'));
|
|
14
|
+
}
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const results = [];
|
|
18
|
+
for (const port of ports) {
|
|
19
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
20
|
+
console.error(pc.red(`✗ Invalid port: ${port}`));
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const info = await inspectPort(port, { deep: true });
|
|
24
|
+
if (info.status === 'free') {
|
|
25
|
+
const res = {
|
|
26
|
+
port,
|
|
27
|
+
pid: 0,
|
|
28
|
+
processName: 'none',
|
|
29
|
+
success: true,
|
|
30
|
+
method: 'none',
|
|
31
|
+
timeTakenMs: 0,
|
|
32
|
+
};
|
|
33
|
+
results.push(res);
|
|
34
|
+
if (!args.json) {
|
|
35
|
+
console.log(formatFreeResult(res));
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
// Dry run mode
|
|
40
|
+
if (args.dryRun) {
|
|
41
|
+
if (!args.json) {
|
|
42
|
+
console.log(pc.yellow(`[Dry Run] Would release port ${port} (PID ${info.pid} - ${info.process?.name || 'unknown'}, Project: ${info.project?.name || 'none'})`));
|
|
43
|
+
}
|
|
44
|
+
results.push({
|
|
45
|
+
port,
|
|
46
|
+
pid: info.pid || 0,
|
|
47
|
+
processName: info.process?.name || 'unknown',
|
|
48
|
+
projectName: info.project?.name,
|
|
49
|
+
success: true,
|
|
50
|
+
method: 'none',
|
|
51
|
+
timeTakenMs: 0,
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Safety protection: protect system processes and databases from accidental termination
|
|
56
|
+
if (info.diagnosis?.safeToFree === false && !args.force) {
|
|
57
|
+
const typeDesc = info.diagnosis.type === 'database' ? 'database' : 'system service';
|
|
58
|
+
const serviceName = info.framework?.name || info.process?.name || 'system process';
|
|
59
|
+
const reason = `Refusing to terminate ${serviceName} (PID ${info.pid}) on port ${port} because it is classified as a protected ${typeDesc}. Use 'port-doctor free ${port} --force' if you explicitly intend to terminate it.`;
|
|
60
|
+
if (!args.json) {
|
|
61
|
+
console.error(pc.red(`\n✗ Safety Protection: ${reason}\n`));
|
|
62
|
+
}
|
|
63
|
+
results.push({
|
|
64
|
+
port,
|
|
65
|
+
pid: info.pid || 0,
|
|
66
|
+
processName: info.process?.name || 'unknown',
|
|
67
|
+
projectName: info.project?.name,
|
|
68
|
+
success: false,
|
|
69
|
+
method: 'none',
|
|
70
|
+
timeTakenMs: 0,
|
|
71
|
+
error: reason,
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
// Confirmation prompt if not forced and in interactive terminal
|
|
76
|
+
if (!args.force && process.stdin.isTTY && !args.json) {
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(pc.bold(`Target: Port ${port}`));
|
|
79
|
+
console.log(` Process: ${pc.bold(info.process?.name || 'unknown')} (PID ${info.pid})`);
|
|
80
|
+
if (info.project?.name) {
|
|
81
|
+
console.log(` Project: ${pc.cyan(info.project.name)}${info.git?.branch ? ` (branch: ${info.git.branch})` : ''}`);
|
|
82
|
+
}
|
|
83
|
+
if (info.process?.fullCommand) {
|
|
84
|
+
console.log(` Command: ${info.process.fullCommand}`);
|
|
85
|
+
}
|
|
86
|
+
const confirmed = await askConfirmation(`Are you sure you want to release port ${port}?`, false);
|
|
87
|
+
if (!confirmed) {
|
|
88
|
+
console.log(pc.yellow(`Aborted release for port ${port}.\n`));
|
|
89
|
+
results.push({
|
|
90
|
+
port,
|
|
91
|
+
pid: info.pid || 0,
|
|
92
|
+
processName: info.process?.name || 'unknown',
|
|
93
|
+
projectName: info.project?.name,
|
|
94
|
+
success: false,
|
|
95
|
+
method: 'none',
|
|
96
|
+
timeTakenMs: 0,
|
|
97
|
+
error: 'User cancelled operation.',
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const onProgress = !args.json ? (msg) => console.log(pc.dim(` ${msg}`)) : undefined;
|
|
103
|
+
const res = await releasePort(port, {
|
|
104
|
+
force: args.force,
|
|
105
|
+
timeoutMs: args.timeout,
|
|
106
|
+
onProgress,
|
|
107
|
+
});
|
|
108
|
+
results.push(res);
|
|
109
|
+
if (!args.json) {
|
|
110
|
+
console.log(formatFreeResult(res));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (args.json) {
|
|
114
|
+
printJson({
|
|
115
|
+
status: results.every((r) => r.success) ? 'success' : 'partial_failure',
|
|
116
|
+
results,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=free.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"free.js","sourceRoot":"","sources":["../../../src/cli/commands/free.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/overview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAK/C,wBAAsB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgBjE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { scanDevPorts, parsePortList } from '../../core/scanner/index.js';
|
|
2
|
+
import { formatOverviewTable } from '../output/formatter.js';
|
|
3
|
+
import { printJson } from '../output/json.js';
|
|
4
|
+
export async function handleOverview(args) {
|
|
5
|
+
const targetPorts = args.ports || parsePortList();
|
|
6
|
+
const results = await scanDevPorts(targetPorts);
|
|
7
|
+
if (args.json) {
|
|
8
|
+
printJson({
|
|
9
|
+
status: 'success',
|
|
10
|
+
totalScanned: results.length,
|
|
11
|
+
busyCount: results.filter((r) => r.status === 'busy').length,
|
|
12
|
+
freeCount: results.filter((r) => r.status === 'free').length,
|
|
13
|
+
ports: results,
|
|
14
|
+
});
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
console.log(formatOverviewTable(results));
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=overview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.js","sourceRoot":"","sources":["../../../src/cli/commands/overview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAa;IAChD,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,aAAa,EAAE,CAAC;IAClD,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;IAEhD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,SAAS,CAAC;YACR,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,OAAO,CAAC,MAAM;YAC5B,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM;YAC5D,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,MAAM;YAC5D,KAAK,EAAE,OAAO;SACf,CAAC,CAAC;QACH,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CLIArgs } from '../../types/index.js';
|
|
2
|
+
export declare function detectExpectedPort(command: string, cwd?: string): Promise<number | undefined>;
|
|
3
|
+
export declare function handleRun(rawCommand: string, args: CLIArgs): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAM/C,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAgB,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAsC1G;AAED,wBAAsB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAwFhF"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { inspectPort } from '../../core/process/index.js';
|
|
6
|
+
import { releasePort } from '../../core/lifecycle/index.js';
|
|
7
|
+
import { printJson } from '../output/json.js';
|
|
8
|
+
import { askChoice } from '../prompts/confirm.js';
|
|
9
|
+
export async function detectExpectedPort(command, cwd = process.cwd()) {
|
|
10
|
+
// 1. Check command line flags like --port 3000 or -p 5173 or PORT=3000
|
|
11
|
+
const portFlagMatch = command.match(/(?:--port|-p)\s+(\d+)/i) || command.match(/PORT=(\d+)/i);
|
|
12
|
+
if (portFlagMatch) {
|
|
13
|
+
const p = parseInt(portFlagMatch[1], 10);
|
|
14
|
+
if (!isNaN(p) && p > 0 && p <= 65535)
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
// 2. Check .env, .env.local, .env.development in cwd
|
|
18
|
+
const envFiles = ['.env.local', '.env.development', '.env'];
|
|
19
|
+
for (const envFile of envFiles) {
|
|
20
|
+
try {
|
|
21
|
+
const content = await fs.readFile(path.join(cwd, envFile), 'utf8');
|
|
22
|
+
const match = content.match(/^PORT\s*=\s*(\d+)/m);
|
|
23
|
+
if (match) {
|
|
24
|
+
const p = parseInt(match[1], 10);
|
|
25
|
+
if (!isNaN(p) && p > 0 && p <= 65535)
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// ignore
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// 3. Inspect package.json
|
|
34
|
+
try {
|
|
35
|
+
const pkgContent = await fs.readFile(path.join(cwd, 'package.json'), 'utf8');
|
|
36
|
+
const pkg = JSON.parse(pkgContent);
|
|
37
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
38
|
+
if (allDeps['vite'])
|
|
39
|
+
return 5173;
|
|
40
|
+
if (allDeps['next'])
|
|
41
|
+
return 3000;
|
|
42
|
+
if (allDeps['astro'])
|
|
43
|
+
return 4321;
|
|
44
|
+
if (allDeps['@nestjs/core'] || allDeps['express'] || allDeps['fastify'])
|
|
45
|
+
return 3000;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// ignore
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
export async function handleRun(rawCommand, args) {
|
|
53
|
+
if (!rawCommand || !rawCommand.trim()) {
|
|
54
|
+
if (args.json) {
|
|
55
|
+
printJson({ status: 'error', error: 'No command specified for `run`.' });
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.error(pc.red('\n✗ Error: Please specify a command to run (e.g. `port-doctor run "npm run dev"`).\n'));
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const expectedPort = await detectExpectedPort(rawCommand);
|
|
63
|
+
if (expectedPort) {
|
|
64
|
+
const portInfo = await inspectPort(expectedPort, { deep: true });
|
|
65
|
+
if (portInfo.status === 'busy') {
|
|
66
|
+
if (args.json) {
|
|
67
|
+
printJson({
|
|
68
|
+
status: 'conflict',
|
|
69
|
+
message: `Port ${expectedPort} is already in use.`,
|
|
70
|
+
port: expectedPort,
|
|
71
|
+
occupyingProcess: portInfo.process,
|
|
72
|
+
project: portInfo.project,
|
|
73
|
+
});
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
console.log('');
|
|
77
|
+
console.log(pc.yellow(pc.bold(`⚠ Port ${expectedPort} is already in use.`)));
|
|
78
|
+
console.log(` Process: ${pc.bold(portInfo.process?.name || 'unknown')} (PID ${portInfo.pid})`);
|
|
79
|
+
if (portInfo.project?.name) {
|
|
80
|
+
console.log(` Project: ${pc.cyan(portInfo.project.name)}${portInfo.git?.branch ? ` (branch: ${portInfo.git.branch})` : ''}`);
|
|
81
|
+
}
|
|
82
|
+
if (portInfo.process?.fullCommand) {
|
|
83
|
+
console.log(` Command: ${portInfo.process.fullCommand}`);
|
|
84
|
+
}
|
|
85
|
+
const choices = [
|
|
86
|
+
`Stop existing process gracefully and start`,
|
|
87
|
+
`Cancel`,
|
|
88
|
+
];
|
|
89
|
+
const choiceIdx = await askChoice('How would you like to proceed?', choices);
|
|
90
|
+
if (choiceIdx === 0) {
|
|
91
|
+
// Attempt graceful release ONLY
|
|
92
|
+
console.log(pc.dim(`\nAttempting graceful release of port ${expectedPort}...`));
|
|
93
|
+
const freeRes = await releasePort(expectedPort, { force: false });
|
|
94
|
+
if (!freeRes.success) {
|
|
95
|
+
console.error(pc.red(`\n✗ Graceful stop failed (PID ${portInfo.pid} did not exit within timeout).`));
|
|
96
|
+
console.error(` To force-kill this process, run: ${pc.cyan(`port-doctor free ${expectedPort} --force`)}\n`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
console.log(pc.green(`✓ Port ${expectedPort} released.`));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log(pc.dim('Operation cancelled.'));
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Handle JSON preflight mode without corrupting stdout
|
|
108
|
+
if (args.json) {
|
|
109
|
+
printJson({
|
|
110
|
+
status: 'ready',
|
|
111
|
+
message: expectedPort ? `Port ${expectedPort} is available.` : 'Command pre-flight passed.',
|
|
112
|
+
port: expectedPort,
|
|
113
|
+
command: rawCommand,
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Execute user command with inherited stdio
|
|
118
|
+
console.log(pc.cyan(`\nStarting: ${pc.bold(rawCommand)}\n`));
|
|
119
|
+
const child = spawn(rawCommand, {
|
|
120
|
+
shell: true,
|
|
121
|
+
stdio: 'inherit',
|
|
122
|
+
env: process.env,
|
|
123
|
+
});
|
|
124
|
+
child.on('exit', (code, signal) => {
|
|
125
|
+
if (signal) {
|
|
126
|
+
process.kill(process.pid, signal);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
process.exit(code || 0);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/cli/commands/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"why.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/why.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAK/C,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { inspectPort } from '../../core/process/index.js';
|
|
2
|
+
import { formatWhyReport } from '../output/formatter.js';
|
|
3
|
+
import { printJson } from '../output/json.js';
|
|
4
|
+
export async function handleWhy(port, args) {
|
|
5
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
6
|
+
if (args.json) {
|
|
7
|
+
printJson({
|
|
8
|
+
status: 'error',
|
|
9
|
+
error: `Invalid port number: ${port}. Port must be an integer between 1 and 65535.`,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
console.error(`\n✗ Error: Invalid port number "${port}". Must be between 1 and 65535.\n`);
|
|
14
|
+
}
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const info = await inspectPort(port, { deep: true });
|
|
18
|
+
if (args.json) {
|
|
19
|
+
printJson(info);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(formatWhyReport(info));
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=why.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"why.js","sourceRoot":"","sources":["../../../src/cli/commands/why.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,IAAa;IACzD,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QAC7C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,SAAS,CAAC;gBACR,MAAM,EAAE,OAAO;gBACf,KAAK,EAAE,wBAAwB,IAAI,gDAAgD;aACpF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,mCAAmC,IAAI,mCAAmC,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAErD,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;AACrC,CAAC"}
|