devenv-inspector 0.2.0 → 0.3.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 +120 -32
- package/cli/README.md +101 -2
- package/cli/bin/devenv.js +8 -1
- package/cli/package.json +1 -1
- package/cli/src/commands/info.js +15 -22
- package/cli/src/commands/list.js +4 -12
- package/cli/src/commands/packages.js +7 -13
- package/cli/src/commands/ports.js +47 -0
- package/cli/src/commands/uninstall.js +6 -15
- package/cli/src/detectors.js +13 -40
- package/cli/src/parsers.js +10 -97
- package/cli/src/ports.js +70 -0
- package/cli/src/registry.js +36 -0
- package/cli/src/runtimes/builtins.js +187 -0
- package/out/main/index.js +226 -113
- package/out/preload/index.js +3 -1
- package/out/renderer/assets/{index-Dp5c_1B_.css → index-BOnw6_r2.css} +7 -3
- package/out/renderer/assets/{index-BzF4Ak0A.js → index-tcFa-qhy.js} +11 -12
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/detectors.js +13 -40
- package/src/main/index.js +1 -0
- package/src/main/ipcHandlers.js +20 -11
- package/src/main/parsers.js +10 -98
- package/src/main/ports.js +85 -0
- package/src/main/registry.js +36 -0
- package/src/main/runtimes/builtins.js +189 -0
- package/src/preload/index.js +3 -1
- package/src/renderer/src/App.css +96 -0
- package/src/renderer/src/App.jsx +72 -25
- package/src/renderer/src/components/KillDialog.jsx +25 -0
- package/src/renderer/src/components/PackageTable.jsx +7 -2
- package/src/renderer/src/components/PortsTable.jsx +110 -0
package/README.md
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
<h1>DevEnv Inspector</h1>
|
|
5
5
|
|
|
6
|
-
<p>A unified desktop GUI for inspecting
|
|
6
|
+
<p>A unified desktop GUI for inspecting runtimes, managing global packages, and monitoring active local ports — no terminal required.</p>
|
|
7
7
|
|
|
8
8
|
<p>
|
|
9
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
9
|
+
<img src="https://img.shields.io/badge/version-0.3.0-5a7af5?style=for-the-badge" alt="Version" />
|
|
10
10
|
<img src="https://img.shields.io/badge/platform-macOS-lightgrey?style=for-the-badge&logo=apple" alt="Platform" />
|
|
11
11
|
<img src="https://img.shields.io/badge/license-MIT-44c98b?style=for-the-badge" alt="License" />
|
|
12
12
|
<img src="https://img.shields.io/badge/open%20source-%E2%9D%A4-e05454?style=for-the-badge" alt="Open Source" />
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
## What is this?
|
|
34
34
|
|
|
35
|
-
Developers who work across Python, Node.js, and Conda constantly switch between terminal commands just to see what's installed. DevEnv Inspector puts it all in one window
|
|
35
|
+
Developers who work across Python, Node.js, and Conda constantly switch between terminal commands just to see what's installed — and what's running. DevEnv Inspector puts it all in one window: version badges, a searchable package list, one-click uninstallation, and a live view of every port in use on your machine.
|
|
36
36
|
|
|
37
37
|
<div align="center">
|
|
38
38
|
<img src="media/screenshot.png" alt="DevEnv Inspector Screenshot" width="780" />
|
|
@@ -42,11 +42,14 @@ Developers who work across Python, Node.js, and Conda constantly switch between
|
|
|
42
42
|
|
|
43
43
|
## Features
|
|
44
44
|
|
|
45
|
-
- **Runtime detection** — instantly shows installed versions of Python, Conda,
|
|
46
|
-
- **Unified package table** — all pip, conda, and
|
|
45
|
+
- **Runtime detection** — instantly shows installed versions of Python, Conda, Node.js, npm, Yarn, and pnpm
|
|
46
|
+
- **Unified package table** — all pip, conda, npm, yarn, and pnpm global packages in one searchable list
|
|
47
47
|
- **Safe uninstallation** — confirmation dialog before any package is removed
|
|
48
|
-
- **Filter by manager** —
|
|
49
|
-
- **
|
|
48
|
+
- **Filter by manager** — dynamically shows only the managers that are installed on your machine
|
|
49
|
+
- **Active Ports tab** — see every TCP/UDP port in use: port number, protocol, PID, and process name
|
|
50
|
+
- **Kill process** — terminate any port's process with a single click and a confirmation dialog
|
|
51
|
+
- **Graceful fallbacks** — missing runtimes show "Not Installed" and hide irrelevant filter tabs
|
|
52
|
+
- **Plugin system** — add support for new package managers in a single file, zero changes to core code
|
|
50
53
|
- **No internet required** — everything runs locally against your machine
|
|
51
54
|
|
|
52
55
|
---
|
|
@@ -63,8 +66,10 @@ npm install -g devenv-inspector-cli
|
|
|
63
66
|
devenv list # show all runtimes
|
|
64
67
|
devenv packages # list all global packages
|
|
65
68
|
devenv packages --runtime pip # filter by manager
|
|
69
|
+
devenv packages --runtime pnpm # pnpm only
|
|
66
70
|
devenv uninstall numpy --runtime conda
|
|
67
|
-
devenv info
|
|
71
|
+
devenv info pnpm
|
|
72
|
+
devenv ports # show all active local ports
|
|
68
73
|
```
|
|
69
74
|
|
|
70
75
|
> Source lives in [`cli/`](./cli) — same repo, zero Electron. Docker support included.
|
|
@@ -126,52 +131,135 @@ npm run package
|
|
|
126
131
|
```
|
|
127
132
|
|
|
128
133
|
This builds the source and produces a `.dmg` installer in `dist/`:
|
|
129
|
-
- `DevEnv Inspector-0.
|
|
130
|
-
- `DevEnv Inspector-0.
|
|
134
|
+
- `DevEnv Inspector-0.3.0-arm64.dmg` — Apple Silicon
|
|
135
|
+
- `DevEnv Inspector-0.3.0.dmg` — Intel
|
|
131
136
|
|
|
132
137
|
---
|
|
133
138
|
|
|
134
139
|
## How It Works
|
|
135
140
|
|
|
136
141
|
```
|
|
137
|
-
|
|
138
|
-
│
|
|
139
|
-
│ React UI —
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
│
|
|
145
|
-
│
|
|
146
|
-
│
|
|
147
|
-
│
|
|
148
|
-
|
|
142
|
+
┌─────────────────────────────────────────────────────────┐
|
|
143
|
+
│ Renderer Process │
|
|
144
|
+
│ React UI — runtime cards, Packages tab, Ports tab │
|
|
145
|
+
│ Tabs derived dynamically from registry metadata │
|
|
146
|
+
└──────────────────────┬──────────────────────────────────┘
|
|
147
|
+
│ IPC (contextBridge)
|
|
148
|
+
┌──────────────────────▼──────────────────────────────────┐
|
|
149
|
+
│ Main Process │
|
|
150
|
+
│ runtimes/builtins.js → registerRuntime() × 6 │
|
|
151
|
+
│ registry.js → getRegisteredRuntimes() │
|
|
152
|
+
│ detectors.js → registry loop → versions │
|
|
153
|
+
│ parsers.js → registry loop → packages │
|
|
154
|
+
│ ports.js → lsof → TCP/UDP port list │
|
|
155
|
+
│ ipcHandlers.js → uninstall + kill-port routing │
|
|
156
|
+
│ shell.js → login shell executor │
|
|
157
|
+
└─────────────────────────────────────────────────────────┘
|
|
149
158
|
```
|
|
150
159
|
|
|
151
160
|
Every command runs through the user's login shell (`zsh -i -l -c`) so that conda, pyenv, nvm, and other shell-managed tools are always found — both in dev mode and in the packaged `.app`.
|
|
152
161
|
|
|
153
162
|
---
|
|
154
163
|
|
|
155
|
-
## Supported Runtimes (v0.
|
|
164
|
+
## Supported Runtimes (v0.3.0)
|
|
156
165
|
|
|
157
|
-
| Runtime | Packages | Uninstall |
|
|
166
|
+
| Runtime | Detect | Packages | Uninstall |
|
|
167
|
+
|---|---|---|---|
|
|
168
|
+
| Python | `python3 --version` | `python3 -m pip list --format=json` | `python3 -m pip uninstall -y` |
|
|
169
|
+
| Anaconda | `conda --version` | `conda list --json` | `conda remove -y` |
|
|
170
|
+
| Node.js | `node --version` | — | — |
|
|
171
|
+
| npm | `npm --version` | `npm list -g --depth=0 --json` | `npm uninstall -g` |
|
|
172
|
+
| Yarn | `yarn --version` | `yarn global list --json` | `yarn global remove` |
|
|
173
|
+
| pnpm | `pnpm --version` | `pnpm list -g --json` | `pnpm remove -g` |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Active Local Ports
|
|
178
|
+
|
|
179
|
+
The **Active Ports** tab gives you an instant overview of every listening port on your machine — the same information as `lsof`, presented without the terminal.
|
|
180
|
+
|
|
181
|
+
| Column | Description |
|
|
182
|
+
|---|---|
|
|
183
|
+
| **Port** | The open port number |
|
|
184
|
+
| **Protocol** | TCP or UDP |
|
|
185
|
+
| **PID** | Process ID using the port |
|
|
186
|
+
| **Process** | Name of the program / service |
|
|
187
|
+
|
|
188
|
+
### In the GUI
|
|
189
|
+
|
|
190
|
+
- Switch to the **Active Ports** tab next to Packages — a live count badge shows how many ports are open
|
|
191
|
+
- Search by port number, process name, or PID
|
|
192
|
+
- Filter by **TCP** / **UDP**
|
|
193
|
+
- Click **Kill** on any row → confirmation dialog → sends `SIGTERM` to that process
|
|
194
|
+
|
|
195
|
+
### How ports are detected
|
|
196
|
+
|
|
197
|
+
`lsof -F pcn` is used with structured field output — no text parsing of headers, injection-safe. TCP ports are filtered to `LISTEN` state only. IPv4/IPv6 duplicates are deduplicated automatically.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Plugin System
|
|
202
|
+
|
|
203
|
+
v0.3.0 ships a runtime registry that makes adding new package managers trivial. Each built-in (pip, conda, npm, yarn, pnpm) is now a self-describing plugin object registered via `registerRuntime()`. The core detectors, parsers, IPC handlers, and renderer UI are all driven by the registry — they contain zero hardcoded manager names.
|
|
204
|
+
|
|
205
|
+
### Adding a community runtime (e.g. Bun)
|
|
206
|
+
|
|
207
|
+
Create one file — `src/main/runtimes/bun.js`:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
import { registerRuntime } from '../registry.js'
|
|
211
|
+
import { runInShell } from '../shell.js'
|
|
212
|
+
|
|
213
|
+
registerRuntime({
|
|
214
|
+
name: 'bun',
|
|
215
|
+
label: 'Bun',
|
|
216
|
+
color: '#fbf0df',
|
|
217
|
+
detect: () => runInShell('bun', ['--version'], { timeout: 10000 }).catch(() => null),
|
|
218
|
+
parseVersion: (o) => o.trim().replace(/^bun\s+v?/i, ''),
|
|
219
|
+
list: async () => { /* parse: bun pm ls --global */ },
|
|
220
|
+
uninstall: (pkg) => ['bun', ['remove', '-g', pkg]]
|
|
221
|
+
})
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Then add **one import line** in `src/main/index.js`:
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
import './runtimes/bun.js'
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
That's it. The runtime card, filter tab, package listing, and uninstall button all appear automatically — **zero changes to core code**.
|
|
231
|
+
|
|
232
|
+
### Plugin shape reference
|
|
233
|
+
|
|
234
|
+
| Field | Type | Description |
|
|
158
235
|
|---|---|---|
|
|
159
|
-
|
|
|
160
|
-
|
|
|
161
|
-
|
|
|
162
|
-
|
|
|
163
|
-
|
|
|
236
|
+
| `name` | `string` | Unique key (also used as `manager` in package rows) |
|
|
237
|
+
| `label` | `string` | Display name in UI and CLI |
|
|
238
|
+
| `color` | `string` | Hex color for the manager badge |
|
|
239
|
+
| `detect` | `async () => string \| null` | Returns raw stdout, or `null` if not installed |
|
|
240
|
+
| `parseVersion` | `(output) => string` | Cleans `detect()` output to a version string |
|
|
241
|
+
| `list` | `async () => [{name, version}] \| null` | `null` = no packages for this runtime |
|
|
242
|
+
| `uninstall` | `(pkg) => [cmd, args] \| null` | `null` = uninstall not supported |
|
|
164
243
|
|
|
165
244
|
---
|
|
166
245
|
|
|
167
246
|
## Roadmap
|
|
168
247
|
|
|
169
|
-
|
|
248
|
+
### Shipped
|
|
249
|
+
- [x] Python, Conda, Node.js, npm, Yarn, pnpm support
|
|
250
|
+
- [x] Searchable + filterable global package table
|
|
251
|
+
- [x] One-click uninstall with confirmation
|
|
252
|
+
- [x] CLI companion (`devenv-inspector-cli` on npm)
|
|
253
|
+
- [x] Docker support for the CLI
|
|
254
|
+
- [x] **Plugin system** — add any runtime in a single file
|
|
255
|
+
- [x] **Active Local Ports** — live TCP/UDP port viewer with process names and kill support
|
|
256
|
+
|
|
257
|
+
### Upcoming
|
|
258
|
+
- [ ] nvm / pyenv version manager support
|
|
170
259
|
- [ ] Virtual environment detection
|
|
171
|
-
- [ ] Package update detection
|
|
260
|
+
- [ ] Package update detection (`outdated` view)
|
|
172
261
|
- [ ] Dependency graph visualization
|
|
173
262
|
- [ ] Dark / light mode toggle
|
|
174
|
-
- [ ] Plugin system for new package managers
|
|
175
263
|
- [ ] Windows & Linux support
|
|
176
264
|
|
|
177
265
|
---
|
package/cli/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<h1>devenv-inspector-cli</h1>
|
|
3
|
-
<p>Inspect runtimes
|
|
3
|
+
<p>Inspect runtimes, manage global packages, and monitor active local ports from your terminal — no GUI required.</p>
|
|
4
4
|
|
|
5
5
|
<p>
|
|
6
|
-
<img src="https://img.shields.io/badge/version-0.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-0.3.0-5a7af5?style=for-the-badge" alt="Version" />
|
|
7
7
|
<img src="https://img.shields.io/badge/platform-macOS-lightgrey?style=for-the-badge&logo=apple" alt="Platform" />
|
|
8
8
|
<img src="https://img.shields.io/badge/license-MIT-44c98b?style=for-the-badge" alt="License" />
|
|
9
9
|
<img src="https://img.shields.io/npm/v/devenv-inspector-cli?style=for-the-badge&logo=npm&color=cc3534" alt="npm" />
|
|
@@ -31,6 +31,7 @@ No Node.js or Python required — just Docker:
|
|
|
31
31
|
docker run --rm ghcr.io/ali-aldahmani/devenv-inspector-cli list
|
|
32
32
|
docker run --rm ghcr.io/ali-aldahmani/devenv-inspector-cli packages
|
|
33
33
|
docker run --rm ghcr.io/ali-aldahmani/devenv-inspector-cli info python
|
|
34
|
+
docker run --rm ghcr.io/ali-aldahmani/devenv-inspector-cli ports
|
|
34
35
|
|
|
35
36
|
# Or build locally from source
|
|
36
37
|
git clone https://github.com/Ali-Aldahmani/devenv-inspector.git
|
|
@@ -57,6 +58,8 @@ $ devenv list
|
|
|
57
58
|
Conda 26.1.0
|
|
58
59
|
Node.js 24.13.0
|
|
59
60
|
npm 11.6.2
|
|
61
|
+
Yarn 1.22.22
|
|
62
|
+
pnpm 9.15.4
|
|
60
63
|
```
|
|
61
64
|
|
|
62
65
|
---
|
|
@@ -69,6 +72,8 @@ devenv packages # all packages
|
|
|
69
72
|
devenv packages --runtime pip # pip only
|
|
70
73
|
devenv packages --runtime conda # conda only
|
|
71
74
|
devenv packages --runtime npm # npm only
|
|
75
|
+
devenv packages --runtime yarn # yarn only
|
|
76
|
+
devenv packages --runtime pnpm # pnpm only
|
|
72
77
|
```
|
|
73
78
|
|
|
74
79
|
```
|
|
@@ -93,6 +98,8 @@ Uninstall a package with a confirmation prompt.
|
|
|
93
98
|
devenv uninstall requests --runtime pip
|
|
94
99
|
devenv uninstall numpy --runtime conda
|
|
95
100
|
devenv uninstall electron --runtime npm
|
|
101
|
+
devenv uninstall create-react-app --runtime yarn
|
|
102
|
+
devenv uninstall typescript --runtime pnpm
|
|
96
103
|
```
|
|
97
104
|
|
|
98
105
|
```
|
|
@@ -112,6 +119,8 @@ devenv info python
|
|
|
112
119
|
devenv info conda
|
|
113
120
|
devenv info node
|
|
114
121
|
devenv info npm
|
|
122
|
+
devenv info yarn
|
|
123
|
+
devenv info pnpm
|
|
115
124
|
```
|
|
116
125
|
|
|
117
126
|
```
|
|
@@ -126,12 +135,102 @@ $ devenv info python
|
|
|
126
135
|
|
|
127
136
|
---
|
|
128
137
|
|
|
138
|
+
### `devenv ports`
|
|
139
|
+
Show all active local listening ports (TCP + UDP) with their process details.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
$ devenv ports
|
|
143
|
+
|
|
144
|
+
DevEnv Inspector — Active Local Ports
|
|
145
|
+
|
|
146
|
+
┌────────┬──────────┬───────┬────────────┐
|
|
147
|
+
│ Port │ Protocol │ PID │ Process │
|
|
148
|
+
├────────┼──────────┼───────┼────────────┤
|
|
149
|
+
│ 3000 │ TCP │ 1234 │ node │
|
|
150
|
+
│ 5432 │ TCP │ 2345 │ postgres │
|
|
151
|
+
│ 8000 │ TCP │ 3456 │ python3 │
|
|
152
|
+
│ 8080 │ TCP │ 4567 │ node │
|
|
153
|
+
└────────┴──────────┴───────┴────────────┘
|
|
154
|
+
|
|
155
|
+
4 active ports
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
TCP ports are colored **blue**, UDP ports **orange**. Only `LISTEN` state TCP sockets and bound UDP sockets are shown — established connections are excluded.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
129
162
|
## How It Works
|
|
130
163
|
|
|
131
164
|
All commands run through your login shell (`zsh -i -l -c`) — the same approach as the GUI app — so conda, pyenv, nvm, and other shell-managed tools are always found.
|
|
132
165
|
|
|
133
166
|
Package names are validated against `/^[a-zA-Z0-9._\-@/]+$/` before any shell call to prevent injection.
|
|
134
167
|
|
|
168
|
+
Port detection uses `lsof -F pcn` with structured field output — no fragile header parsing. IPv4/IPv6 duplicates are deduplicated automatically.
|
|
169
|
+
|
|
170
|
+
Runtimes are powered by a **plugin registry** — each manager is a self-describing object registered via `registerRuntime()`. The CLI commands contain zero hardcoded manager names; everything is derived from the registry at runtime.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Plugin System
|
|
175
|
+
|
|
176
|
+
Adding support for a new package manager requires **one file** and **one import line**.
|
|
177
|
+
|
|
178
|
+
Create `cli/src/runtimes/bun.js`:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
import { registerRuntime } from '../registry.js'
|
|
182
|
+
import { runInShell } from '../shell.js'
|
|
183
|
+
|
|
184
|
+
registerRuntime({
|
|
185
|
+
name: 'bun',
|
|
186
|
+
label: 'Bun',
|
|
187
|
+
color: '#fbf0df',
|
|
188
|
+
detect: () => runInShell('bun', ['--version'], { timeout: 10000 }).catch(() => null),
|
|
189
|
+
parseVersion: (o) => o.trim().replace(/^bun\s+v?/i, ''),
|
|
190
|
+
list: async () => { /* parse: bun pm ls --global */ },
|
|
191
|
+
uninstall: (pkg) => ['bun', ['remove', '-g', pkg]]
|
|
192
|
+
})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Then add one line in `cli/bin/devenv.js`:
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
import '../src/runtimes/bun.js'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Bun immediately appears in `devenv list`, `devenv packages --runtime bun`, `devenv uninstall`, and `devenv info bun` — **zero changes to CLI command files**.
|
|
202
|
+
|
|
203
|
+
### Plugin shape reference
|
|
204
|
+
|
|
205
|
+
| Field | Type | Description |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| `name` | `string` | Unique key used as `--runtime` value |
|
|
208
|
+
| `label` | `string` | Display name in output |
|
|
209
|
+
| `color` | `string` | Hex color for colored output |
|
|
210
|
+
| `detect` | `async () => string \| null` | Returns raw stdout, or `null` if not installed |
|
|
211
|
+
| `parseVersion` | `(output) => string` | Cleans `detect()` output to a version string |
|
|
212
|
+
| `list` | `async () => [{name, version}] \| null` | `null` = no packages for this runtime |
|
|
213
|
+
| `uninstall` | `(pkg) => [cmd, args] \| null` | `null` = uninstall not supported |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Roadmap
|
|
218
|
+
|
|
219
|
+
### Shipped
|
|
220
|
+
- [x] Python, Conda, Node.js, npm, Yarn, pnpm support
|
|
221
|
+
- [x] Colored, tabular package output with `cli-table3`
|
|
222
|
+
- [x] Confirmation prompt before uninstall
|
|
223
|
+
- [x] `devenv info` command for per-runtime details
|
|
224
|
+
- [x] Docker support
|
|
225
|
+
- [x] **Plugin system** — add any runtime in a single file
|
|
226
|
+
- [x] **`devenv ports`** — active TCP/UDP port viewer with process names
|
|
227
|
+
|
|
228
|
+
### Upcoming
|
|
229
|
+
- [ ] nvm / pyenv version manager support
|
|
230
|
+
- [ ] `devenv packages --outdated` — highlight packages with available updates
|
|
231
|
+
- [ ] JSON output flag (`--json`) for scripting
|
|
232
|
+
- [ ] Windows & Linux support
|
|
233
|
+
|
|
135
234
|
---
|
|
136
235
|
|
|
137
236
|
## License
|
package/cli/bin/devenv.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import '../src/runtimes/builtins.js'
|
|
2
3
|
import { Command } from 'commander'
|
|
3
4
|
import { listCommand } from '../src/commands/list.js'
|
|
4
5
|
import { packagesCommand } from '../src/commands/packages.js'
|
|
5
6
|
import { uninstallCommand } from '../src/commands/uninstall.js'
|
|
6
7
|
import { infoCommand } from '../src/commands/info.js'
|
|
8
|
+
import { portsCommand } from '../src/commands/ports.js'
|
|
7
9
|
|
|
8
10
|
const program = new Command()
|
|
9
11
|
|
|
10
12
|
program
|
|
11
13
|
.name('devenv')
|
|
12
14
|
.description('Inspect and manage your development runtimes and global packages')
|
|
13
|
-
.version('0.
|
|
15
|
+
.version('0.3.0')
|
|
14
16
|
|
|
15
17
|
program
|
|
16
18
|
.command('list')
|
|
@@ -34,4 +36,9 @@ program
|
|
|
34
36
|
.description('Show detailed info for a runtime (python, conda, node, npm)')
|
|
35
37
|
.action(infoCommand)
|
|
36
38
|
|
|
39
|
+
program
|
|
40
|
+
.command('ports')
|
|
41
|
+
.description('Show all active local listening ports and their processes')
|
|
42
|
+
.action(portsCommand)
|
|
43
|
+
|
|
37
44
|
await program.parseAsync(process.argv)
|
package/cli/package.json
CHANGED
package/cli/src/commands/info.js
CHANGED
|
@@ -1,51 +1,44 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import { detectRuntimes } from '../detectors.js'
|
|
3
3
|
import { getAllPackages } from '../parsers.js'
|
|
4
|
-
|
|
5
|
-
const RUNTIME_META = {
|
|
6
|
-
python: { label: 'Python', manager: 'pip', packageLabel: 'pip packages' },
|
|
7
|
-
conda: { label: 'Conda', manager: 'conda', packageLabel: 'conda packages' },
|
|
8
|
-
node: { label: 'Node.js', manager: null, packageLabel: null },
|
|
9
|
-
npm: { label: 'npm', manager: 'npm', packageLabel: 'global npm packages' },
|
|
10
|
-
yarn: { label: 'Yarn', manager: 'yarn', packageLabel: 'global yarn packages' },
|
|
11
|
-
pnpm: { label: 'pnpm', manager: 'pnpm', packageLabel: 'global pnpm packages' }
|
|
12
|
-
}
|
|
4
|
+
import { getRegisteredRuntimes, getRuntime } from '../registry.js'
|
|
13
5
|
|
|
14
6
|
export async function infoCommand(runtime) {
|
|
15
7
|
const key = runtime.toLowerCase()
|
|
16
|
-
const
|
|
8
|
+
const rt = getRuntime(key)
|
|
17
9
|
|
|
18
|
-
if (!
|
|
19
|
-
|
|
10
|
+
if (!rt) {
|
|
11
|
+
const valid = getRegisteredRuntimes().map((r) => r.name).join(', ')
|
|
12
|
+
console.error(chalk.red(`\n Unknown runtime "${runtime}". Valid values: ${valid}\n`))
|
|
20
13
|
process.exit(1)
|
|
21
14
|
}
|
|
22
15
|
|
|
23
|
-
if (process.stdout.isTTY) process.stdout.write(` Loading info for ${
|
|
16
|
+
if (process.stdout.isTTY) process.stdout.write(` Loading info for ${rt.label}...`)
|
|
24
17
|
|
|
25
18
|
const runtimes = await detectRuntimes()
|
|
26
19
|
|
|
27
20
|
if (process.stdout.isTTY) process.stdout.write('\r' + ' '.repeat(40) + '\r')
|
|
28
21
|
|
|
29
|
-
const
|
|
22
|
+
const info = runtimes[key]
|
|
30
23
|
|
|
31
|
-
console.log(chalk.bold(`\n ${
|
|
32
|
-
console.log(` Status ${
|
|
24
|
+
console.log(chalk.bold(`\n ${rt.label}\n`))
|
|
25
|
+
console.log(` Status ${info?.installed ? chalk.green('Installed') : chalk.dim('Not Installed')}`)
|
|
33
26
|
|
|
34
|
-
if (!
|
|
27
|
+
if (!info?.installed) {
|
|
35
28
|
console.log()
|
|
36
29
|
return
|
|
37
30
|
}
|
|
38
31
|
|
|
39
|
-
console.log(` Version ${chalk.cyan(
|
|
32
|
+
console.log(` Version ${chalk.cyan(info.version)}`)
|
|
40
33
|
|
|
41
|
-
// Show package count if this runtime
|
|
42
|
-
if (
|
|
34
|
+
// Show package count if this runtime supports package listing
|
|
35
|
+
if (rt.list) {
|
|
43
36
|
if (process.stdout.isTTY) process.stdout.write(' Counting packages...')
|
|
44
37
|
const allPkgs = await getAllPackages(runtimes)
|
|
45
38
|
if (process.stdout.isTTY) process.stdout.write('\r' + ' '.repeat(30) + '\r')
|
|
46
39
|
|
|
47
|
-
const count = allPkgs.filter((p) => p.manager ===
|
|
48
|
-
console.log(` Packages ${chalk.yellow(count)} ${
|
|
40
|
+
const count = allPkgs.filter((p) => p.manager === key).length
|
|
41
|
+
console.log(` Packages ${chalk.yellow(count)} global ${rt.label} packages`)
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
console.log()
|
package/cli/src/commands/list.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
2
|
import { detectRuntimes } from '../detectors.js'
|
|
3
3
|
|
|
4
|
-
const RUNTIMES = [
|
|
5
|
-
{ key: 'python', label: 'Python' },
|
|
6
|
-
{ key: 'conda', label: 'Conda' },
|
|
7
|
-
{ key: 'node', label: 'Node.js' },
|
|
8
|
-
{ key: 'npm', label: 'npm' }
|
|
9
|
-
]
|
|
10
|
-
|
|
11
4
|
export async function listCommand() {
|
|
12
5
|
console.log(chalk.bold('\n DevEnv Inspector — Runtimes\n'))
|
|
13
6
|
|
|
@@ -18,11 +11,10 @@ export async function listCommand() {
|
|
|
18
11
|
|
|
19
12
|
if (spinner) process.stdout.write('\r' + ' '.repeat(30) + '\r')
|
|
20
13
|
|
|
21
|
-
for (const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
console.log(` ${name} ${chalk.green(rt.version)}`)
|
|
14
|
+
for (const [, info] of Object.entries(runtimes)) {
|
|
15
|
+
const name = chalk.white.bold(info.label.padEnd(10))
|
|
16
|
+
if (info.installed) {
|
|
17
|
+
console.log(` ${name} ${chalk.green(info.version)}`)
|
|
26
18
|
} else {
|
|
27
19
|
console.log(` ${name} ${chalk.dim('Not Installed')}`)
|
|
28
20
|
}
|
|
@@ -2,22 +2,15 @@ import chalk from 'chalk'
|
|
|
2
2
|
import Table from 'cli-table3'
|
|
3
3
|
import { detectRuntimes } from '../detectors.js'
|
|
4
4
|
import { getAllPackages } from '../parsers.js'
|
|
5
|
-
|
|
6
|
-
const VALID_RUNTIMES = ['pip', 'conda', 'npm', 'yarn', 'pnpm']
|
|
7
|
-
|
|
8
|
-
const MANAGER_COLORS = {
|
|
9
|
-
pip: (s) => chalk.hex('#5a7af5')(s),
|
|
10
|
-
conda: (s) => chalk.hex('#44c98b')(s),
|
|
11
|
-
npm: (s) => chalk.hex('#e05454')(s),
|
|
12
|
-
yarn: (s) => chalk.hex('#2c8ebb')(s),
|
|
13
|
-
pnpm: (s) => chalk.hex('#f69220')(s)
|
|
14
|
-
}
|
|
5
|
+
import { getRegisteredRuntimes, getRuntime } from '../registry.js'
|
|
15
6
|
|
|
16
7
|
export async function packagesCommand(options) {
|
|
17
8
|
const filter = options.runtime?.toLowerCase()
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
const validRuntimes = getRegisteredRuntimes().filter((rt) => rt.list).map((rt) => rt.name)
|
|
11
|
+
|
|
12
|
+
if (filter && !validRuntimes.includes(filter)) {
|
|
13
|
+
console.error(chalk.red(`\n Unknown runtime "${filter}". Valid values: ${validRuntimes.join(', ')}\n`))
|
|
21
14
|
process.exit(1)
|
|
22
15
|
}
|
|
23
16
|
|
|
@@ -54,7 +47,8 @@ export async function packagesCommand(options) {
|
|
|
54
47
|
})
|
|
55
48
|
|
|
56
49
|
for (const pkg of packages) {
|
|
57
|
-
const
|
|
50
|
+
const color = getRuntime(pkg.manager)?.color ?? '#ffffff'
|
|
51
|
+
const colorFn = (s) => chalk.hex(color)(s)
|
|
58
52
|
table.push([
|
|
59
53
|
pkg.name,
|
|
60
54
|
chalk.dim(pkg.version),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import Table from 'cli-table3'
|
|
3
|
+
import { getActivePorts } from '../ports.js'
|
|
4
|
+
|
|
5
|
+
export async function portsCommand() {
|
|
6
|
+
console.log(chalk.bold('\n DevEnv Inspector — Active Local Ports\n'))
|
|
7
|
+
|
|
8
|
+
const spinner = process.stdout.isTTY
|
|
9
|
+
if (spinner) process.stdout.write(' Scanning ports...')
|
|
10
|
+
|
|
11
|
+
const ports = await getActivePorts()
|
|
12
|
+
|
|
13
|
+
if (spinner) process.stdout.write('\r' + ' '.repeat(30) + '\r')
|
|
14
|
+
|
|
15
|
+
if (ports.length === 0) {
|
|
16
|
+
console.log(chalk.dim(' No active listening ports found.\n'))
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const table = new Table({
|
|
21
|
+
head: [
|
|
22
|
+
chalk.white.bold('Port'),
|
|
23
|
+
chalk.white.bold('Protocol'),
|
|
24
|
+
chalk.white.bold('PID'),
|
|
25
|
+
chalk.white.bold('Process')
|
|
26
|
+
],
|
|
27
|
+
style: { head: [], border: [] },
|
|
28
|
+
colWidths: [10, 12, 10, 30]
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
for (const p of ports) {
|
|
32
|
+
const proto =
|
|
33
|
+
p.protocol === 'TCP'
|
|
34
|
+
? chalk.hex('#4b9eff')(p.protocol)
|
|
35
|
+
: chalk.hex('#f69220')(p.protocol)
|
|
36
|
+
|
|
37
|
+
table.push([
|
|
38
|
+
chalk.white.bold(String(p.port)),
|
|
39
|
+
proto,
|
|
40
|
+
chalk.dim(String(p.pid)),
|
|
41
|
+
chalk.white(p.process)
|
|
42
|
+
])
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log(table.toString())
|
|
46
|
+
console.log(chalk.dim(`\n ${ports.length} active port${ports.length !== 1 ? 's' : ''}\n`))
|
|
47
|
+
}
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import chalk from 'chalk'
|
|
2
|
-
import { createRequire } from 'module'
|
|
3
2
|
import { runInShell } from '../shell.js'
|
|
3
|
+
import { getRegisteredRuntimes, getRuntime } from '../registry.js'
|
|
4
4
|
|
|
5
5
|
const PACKAGE_NAME_RE = /^[a-zA-Z0-9._\-@/]+$/
|
|
6
6
|
|
|
7
|
-
const VALID_MANAGERS = ['pip', 'conda', 'npm', 'yarn', 'pnpm']
|
|
8
|
-
|
|
9
|
-
const UNINSTALL_COMMANDS = {
|
|
10
|
-
pip: ['python3', ['-m', 'pip', 'uninstall', '-y']],
|
|
11
|
-
conda: ['conda', ['remove', '-y']],
|
|
12
|
-
npm: ['npm', ['uninstall', '-g']],
|
|
13
|
-
yarn: ['yarn', ['global', 'remove']],
|
|
14
|
-
pnpm: ['pnpm', ['remove', '-g']]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
7
|
export async function uninstallCommand(pkg, options) {
|
|
18
8
|
const manager = options.runtime?.toLowerCase()
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
const validManagers = getRegisteredRuntimes().filter((rt) => rt.uninstall).map((rt) => rt.name)
|
|
11
|
+
|
|
12
|
+
if (!validManagers.includes(manager)) {
|
|
13
|
+
console.error(chalk.red(`\n Unknown runtime "${manager}". Valid values: ${validManagers.join(', ')}\n`))
|
|
22
14
|
process.exit(1)
|
|
23
15
|
}
|
|
24
16
|
|
|
@@ -46,8 +38,7 @@ export async function uninstallCommand(pkg, options) {
|
|
|
46
38
|
|
|
47
39
|
console.log(chalk.dim(`\n Running ${manager} uninstall ${pkg}...`))
|
|
48
40
|
|
|
49
|
-
const [cmd,
|
|
50
|
-
const args = [...baseArgs, pkg]
|
|
41
|
+
const [cmd, args] = getRuntime(manager).uninstall(pkg)
|
|
51
42
|
|
|
52
43
|
try {
|
|
53
44
|
await runInShell(cmd, args, { timeout: 60000 })
|