@saptools/cf-debugger 0.1.14 โ 0.1.16
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 +73 -27
- package/dist/cli.js +1712 -510
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +34 -2
- package/dist/index.js +1557 -415
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ Signal the remote process, enable SSH if needed, forward `9229` to a free local
|
|
|
22
22
|
|
|
23
23
|
- ๐ **One-shot tunnel** โ auth, target, SSH-enable, USR1 signal, port forward, readiness probe โ all hidden behind `cf-debugger start`
|
|
24
24
|
- ๐งต **Multi-debugger concurrency** โ run N debuggers for N apps at once; each session gets its own local port, isolated `CF_HOME`, and an entry in the shared state file
|
|
25
|
-
-
|
|
26
|
-
-
|
|
25
|
+
- ๐ฏ **Exact process targeting** โ select a CF process, instance, and optional Node PID; when several Node processes exist the one owning the app's `$PORT` listening socket is auto-selected, otherwise selection fails closed
|
|
26
|
+
- ๐ก๏ธ **Duplicate-session protection** โ the same `region/org/space/app/process/instance` cannot be debugged twice simultaneously (returns `SESSION_ALREADY_RUNNING`)
|
|
27
|
+
- ๐งน **Crash-proof state** โ provably dead entries are pruned, while ownership mismatches are retained for safe recovery instead of being deleted blindly
|
|
27
28
|
- ๐ **Deterministic ports** โ auto-assigned from a safe range (`20000โ20999`), or pick your own with `--port`
|
|
28
29
|
- ๐งฉ **CLI & typed API** โ every command has a zero-config Node.js equivalent with full TypeScript definitions
|
|
29
30
|
- ๐ชถ **Small + boring** โ one runtime dep (`commander`), no daemons, no magic
|
|
@@ -43,7 +44,9 @@ npm install @saptools/cf-debugger
|
|
|
43
44
|
```
|
|
44
45
|
|
|
45
46
|
> [!NOTE]
|
|
46
|
-
> Requires **Node.js โฅ 20** and the official **`cf` CLI** on `PATH
|
|
47
|
+
> Requires **Node.js โฅ 20** and the official **`cf` CLI** on `PATH`. Default `web`
|
|
48
|
+
> process targets work with CF CLI v6+; non-web process targeting uses `--process` and requires
|
|
49
|
+
> CF CLI v7+ (v8 recommended).
|
|
47
50
|
|
|
48
51
|
---
|
|
49
52
|
|
|
@@ -66,13 +69,15 @@ cf-debugger start \
|
|
|
66
69
|
# Local port: 20142
|
|
67
70
|
# Remote port: 9229
|
|
68
71
|
# Session id: 01HXYZ...
|
|
69
|
-
# PID:
|
|
72
|
+
# Tunnel PID: 83421
|
|
70
73
|
# Press Ctrl+C to stop.
|
|
71
74
|
|
|
72
75
|
# 3. Attach your IDE (VSCode, Chrome DevTools, ...) to localhost:20142
|
|
73
76
|
```
|
|
74
77
|
|
|
75
|
-
Ctrl+C cleans everything up
|
|
78
|
+
Ctrl+C cleans everything up after tunnel termination is confirmed. If the PID or process group does
|
|
79
|
+
not terminate, `cf-debugger` reports an error and retains its state and isolated CF home for safe
|
|
80
|
+
recovery instead of hiding an unmanaged tunnel.
|
|
76
81
|
|
|
77
82
|
---
|
|
78
83
|
|
|
@@ -85,6 +90,7 @@ Open a tunnel for one app and keep running until interrupted.
|
|
|
85
90
|
```bash
|
|
86
91
|
cf-debugger start --region eu10 --org my-org --space dev --app my-app
|
|
87
92
|
cf-debugger start --region eu10 --org my-org --space dev --app my-app --port 9230
|
|
93
|
+
cf-debugger start --region eu10 --org my-org --space dev --app my-app --process worker --instance 2 --node-pid 4312
|
|
88
94
|
cf-debugger start --region eu10 --org my-org --space dev --app my-app --timeout 180 --verbose
|
|
89
95
|
```
|
|
90
96
|
|
|
@@ -94,20 +100,33 @@ cf-debugger start --region eu10 --org my-org --space dev --app my-app --timeout
|
|
|
94
100
|
| `--org <name>` | **Required.** CF org name |
|
|
95
101
|
| `--space <name>` | **Required.** CF space name |
|
|
96
102
|
| `--app <name>` | **Required.** CF app name |
|
|
103
|
+
| `--process <name>` | CF process name (default: `web`) |
|
|
104
|
+
| `-i, --instance <index>` | Zero-based CF process instance (default: `0`) |
|
|
105
|
+
| `--node-pid <pid>` | Exact remote Node.js PID; otherwise the app-`$PORT` listener is auto-selected, else one unambiguous PID is discovered |
|
|
97
106
|
| `--port <number>` | Preferred local port (auto-assigned in `20000โ20999` if omitted) |
|
|
98
107
|
| `--timeout <seconds>` | Tunnel-ready timeout (default: `180`) |
|
|
99
108
|
| `--verbose` | Print every status transition |
|
|
100
109
|
|
|
101
110
|
Cloud Foundry startup commands (`api`, `auth`, `target`, SSH checks, app
|
|
102
|
-
restart, and the one-shot SIGUSR1 SSH command) each allow up to
|
|
111
|
+
restart, and the one-shot SIGUSR1 SSH command) each allow up to 300 seconds.
|
|
103
112
|
`--timeout` controls the subsequent local tunnel-readiness probe separately.
|
|
104
113
|
|
|
114
|
+
When `--node-pid` is explicit and SSH is disabled, `cf-debugger` fails with
|
|
115
|
+
`NODE_PID_RESTART_UNSAFE` before enabling SSH or restarting the app. A restart replaces the
|
|
116
|
+
container process identity, so enable SSH and restart manually, then resolve and pass the new PID.
|
|
117
|
+
|
|
105
118
|
### โน๏ธ `cf-debugger stop`
|
|
106
119
|
|
|
107
|
-
Stop a specific session or everything at once.
|
|
120
|
+
Stop a specific session or everything at once. A stop received during startup records an atomic
|
|
121
|
+
stop intent and asks the startup owner to cancel and clean up; the command reports `Stop requested`
|
|
122
|
+
instead of claiming the tunnel has already stopped. If a matching entry is provably stale,
|
|
123
|
+
`stop --session-id` removes it idempotently. For bare app names, matching still uses the current CF
|
|
124
|
+
target; use `--session-id` or the full `region/org/space/app` selector when the listed session belongs
|
|
125
|
+
to a different target.
|
|
108
126
|
|
|
109
127
|
```bash
|
|
110
128
|
cf-debugger stop --region eu10 --org my-org --space dev --app my-app
|
|
129
|
+
cf-debugger stop --region eu10 --org my-org --space dev --app my-app --process worker --instance 2
|
|
111
130
|
cf-debugger stop --session-id 01HXYZABCD...
|
|
112
131
|
cf-debugger stop --all
|
|
113
132
|
```
|
|
@@ -115,12 +134,17 @@ cf-debugger stop --all
|
|
|
115
134
|
| Flag | Description |
|
|
116
135
|
| --- | --- |
|
|
117
136
|
| `--region` / `--org` / `--space` / `--app` | Match session by key (all four required together) |
|
|
137
|
+
| `--process <name>` / `--instance <index>` | Match the process-instance target (defaults: `web` / `0`) |
|
|
118
138
|
| `--session-id <id>` | Match session by its ID |
|
|
119
139
|
| `--all` | Stop every active session on this machine |
|
|
120
140
|
|
|
121
141
|
### ๐ `cf-debugger list`
|
|
122
142
|
|
|
123
|
-
Print
|
|
143
|
+
Print active and conservatively retained sessions as JSON. During startup, a live controller retains
|
|
144
|
+
the record; after `ready`, tunnel process-group and port ownership determine health. An entry is
|
|
145
|
+
pruned only when the owners relevant to its phase are gone and the recorded port is closed. If a
|
|
146
|
+
relevant PID/group is alive or the port has an unexpected owner, the entry is retained so a later
|
|
147
|
+
command cannot target or delete an unrelated process.
|
|
124
148
|
|
|
125
149
|
```bash
|
|
126
150
|
cf-debugger list | jq '.[] | {app, localPort, status}'
|
|
@@ -128,10 +152,12 @@ cf-debugger list | jq '.[] | {app, localPort, status}'
|
|
|
128
152
|
|
|
129
153
|
### ๐ `cf-debugger status`
|
|
130
154
|
|
|
131
|
-
Print one
|
|
155
|
+
Print one retained session by key (or `null` if no session matches after the same safe pruning used
|
|
156
|
+
by `list`).
|
|
132
157
|
|
|
133
158
|
```bash
|
|
134
159
|
cf-debugger status --region eu10 --org my-org --space dev --app my-app
|
|
160
|
+
cf-debugger status --region eu10 --org my-org --space dev --app my-app --process worker --instance 2
|
|
135
161
|
```
|
|
136
162
|
|
|
137
163
|
---
|
|
@@ -141,23 +167,26 @@ cf-debugger status --region eu10 --org my-org --space dev --app my-app
|
|
|
141
167
|
```
|
|
142
168
|
โโโโโโโโโโโโโโโโโโโโโโ 1. cf api + cf auth (retry x3)
|
|
143
169
|
โ cf-debugger start โ 2. cf target -o <org> -s <space>
|
|
144
|
-
โ region/org/ โ 3. cf ssh
|
|
145
|
-
โ space/app โ โโบ 4.
|
|
146
|
-
โโโโโโโโโโโโโโโโโโโโโโ 5.
|
|
147
|
-
โ 6.
|
|
148
|
-
|
|
149
|
-
|
|
170
|
+
โ region/org/ โ 3. Probe one exact/unambiguous Node PID through cf ssh
|
|
171
|
+
โ space/app โ โโบ 4. Signal/verify inspector ownership on remote port 9229
|
|
172
|
+
โโโโโโโโโโโโโโโโโโโโโโ 5. If SSH is disabled: enable + restart only for automatic PID selection
|
|
173
|
+
โ 6. Retry the probe, then record its verified remote Node PID
|
|
174
|
+
โ 7. Open cf ssh [--process <non-web>] -i <instance> with -L
|
|
175
|
+
โผ 8. Verify the local listener PID and TCP readiness
|
|
176
|
+
DebuggerHandle 9. Save ready state to ~/.saptools/cf-debugger-state-v2.json
|
|
150
177
|
```
|
|
151
178
|
|
|
152
179
|
Each step emits a status update (`logging-in`, `targeting`, `ssh-enabling`, `signaling`, `tunneling`, `ready`, โฆ). `--verbose` prints them live; the programmatic API exposes the same stream via `onStatus`.
|
|
153
180
|
|
|
154
181
|
### Concurrency model
|
|
155
182
|
|
|
156
|
-
- **Atomic state** โ `~/.saptools/cf-debugger-state.json` is written via temp-file + `rename`, guarded by a short-lived
|
|
183
|
+
- **Atomic state** โ `~/.saptools/cf-debugger-state-v2.json` is written via temp-file + `rename`, guarded by a short-lived v2 lock file (`open(..., "wx")`).
|
|
157
184
|
- **Port allocation** โ on register, ports already used by other sessions are excluded; the first free port in `20000โ20999` wins.
|
|
158
|
-
- **Isolated CF homes** โ each session runs with its own `CF_HOME` (`~/.saptools/cf-debugger-homes/<sessionId>/`), so `cf target` in one terminal can't clobber another.
|
|
159
|
-
- **
|
|
160
|
-
- **
|
|
185
|
+
- **Isolated CF homes** โ each session runs with its own `CF_HOME` (`~/.saptools/cf-debugger-homes-v2/<sessionId>/`), so `cf target` in one terminal can't clobber another.
|
|
186
|
+
- **Ownership-aware lifecycle** โ startup records separate controller and tunnel PIDs, polls an atomic stop intent, and lets the startup owner clean up. A tunnel PID is signalled only after exact listener ownership is verified.
|
|
187
|
+
- **Conservative stale pruning** โ state is removed automatically only when recorded owners are dead and the port is closed. Unknown or mismatched ownership is retained and reported instead of risking an unrelated process.
|
|
188
|
+
- **Exact Node selection** โ the fixed remote probe reads numeric `/proc` entries, never reads command lines, verifies the chosen PID owns inspector port `9229`, and fails closed when automatic selection finds zero or multiple Node processes.
|
|
189
|
+
- **Duplicate guard** โ trying to start a second healthy tunnel for the same `region/org/space/app/process/instance` fails fast with `SESSION_ALREADY_RUNNING` instead of racing for the port; stale same-key entries are pruned so a fresh tunnel can recover.
|
|
161
190
|
|
|
162
191
|
---
|
|
163
192
|
|
|
@@ -166,17 +195,17 @@ Each step emits a status update (`logging-in`, `targeting`, `ssh-enabling`, `sig
|
|
|
166
195
|
All state lives under your home directory:
|
|
167
196
|
|
|
168
197
|
```text
|
|
169
|
-
~/.saptools/cf-debugger-state.json # active sessions (atomic JSON)
|
|
170
|
-
~/.saptools/cf-debugger-state.lock # short-lived lock file
|
|
171
|
-
~/.saptools/cf-debugger-homes/<id>/ # per-session isolated CF_HOME
|
|
198
|
+
~/.saptools/cf-debugger-state-v2.json # active sessions (atomic JSON)
|
|
199
|
+
~/.saptools/cf-debugger-state-v2.lock # short-lived lock file
|
|
200
|
+
~/.saptools/cf-debugger-homes-v2/<id>/ # per-session isolated CF_HOME
|
|
172
201
|
```
|
|
173
202
|
|
|
174
203
|
<details>
|
|
175
|
-
<summary><b>๐ฌ Shape of <code>cf-debugger-state.json</code></b></summary>
|
|
204
|
+
<summary><b>๐ฌ Shape of <code>cf-debugger-state-v2.json</code></b></summary>
|
|
176
205
|
|
|
177
206
|
```jsonc
|
|
178
207
|
{
|
|
179
|
-
"version":
|
|
208
|
+
"version": "2",
|
|
180
209
|
"sessions": [
|
|
181
210
|
{
|
|
182
211
|
"sessionId": "01HXYZABCD...",
|
|
@@ -184,9 +213,17 @@ All state lives under your home directory:
|
|
|
184
213
|
"org": "my-org",
|
|
185
214
|
"space": "dev",
|
|
186
215
|
"app": "my-app",
|
|
216
|
+
"process": "web",
|
|
217
|
+
"instance": 0,
|
|
218
|
+
"hostname": "developer-host",
|
|
219
|
+
"apiEndpoint": "https://api.cf.eu10.hana.ondemand.com",
|
|
187
220
|
"localPort": 20142,
|
|
188
221
|
"remotePort": 9229,
|
|
189
222
|
"pid": 83421,
|
|
223
|
+
"controllerPid": 83390,
|
|
224
|
+
"tunnelPid": 83421,
|
|
225
|
+
"remoteNodePid": 4312,
|
|
226
|
+
"cfHomeDir": "/home/developer/.saptools/cf-debugger-homes-v2/01HXYZABCD...",
|
|
190
227
|
"status": "ready",
|
|
191
228
|
"startedAt": "2026-04-18T00:00:00.000Z"
|
|
192
229
|
}
|
|
@@ -199,6 +236,12 @@ All state lives under your home directory:
|
|
|
199
236
|
> [!IMPORTANT]
|
|
200
237
|
> Prefer the CLI commands (`list` / `status`) or the exported APIs over parsing these files โ the on-disk format is an implementation detail.
|
|
201
238
|
|
|
239
|
+
> [!WARNING]
|
|
240
|
+
> State v2 intentionally does not adopt or modify legacy `cf-debugger-state.json` and
|
|
241
|
+
> `cf-debugger-homes/` artifacts. Stop all sessions owned by the older CLI before upgrading or
|
|
242
|
+
> downgrading. Separate namespaces prevent cross-version state overwrite and cross-stop, but an old
|
|
243
|
+
> CLI can still compete for the same local ports because it does not understand v2 reservations.
|
|
244
|
+
|
|
202
245
|
---
|
|
203
246
|
|
|
204
247
|
## โ FAQ
|
|
@@ -206,14 +249,17 @@ All state lives under your home directory:
|
|
|
206
249
|
<details>
|
|
207
250
|
<summary><b>Can I run multiple debuggers at once?</b></summary>
|
|
208
251
|
|
|
209
|
-
Yes โ that's a core feature. Open two terminals, pick
|
|
252
|
+
Yes โ that's a core feature. Open two terminals, pick different apps or process instances, and the tunnels come up on separate local ports. `cf-debugger list` shows everything at once. One exact app/process/instance target can have only one healthy tunnel.
|
|
210
253
|
|
|
211
254
|
</details>
|
|
212
255
|
|
|
213
256
|
<details>
|
|
214
257
|
<summary><b>Does this modify the remote app?</b></summary>
|
|
215
258
|
|
|
216
|
-
Only if SSH is disabled
|
|
259
|
+
Only if SSH is disabled and Node PID selection is automatic. In that case, `cf-debugger` runs
|
|
260
|
+
`cf enable-ssh` + `cf restart`; otherwise it only sends `SIGUSR1` to the selected Node.js process.
|
|
261
|
+
With an explicit `--node-pid`, automatic restart is rejected because the old PID cannot identify the
|
|
262
|
+
new process after restart. No code, environment variable, or manifest is changed.
|
|
217
263
|
|
|
218
264
|
</details>
|
|
219
265
|
|
|
@@ -227,7 +273,7 @@ The TCP probe will fail on reconnect and the CLI will exit with the SSH child's
|
|
|
227
273
|
<details>
|
|
228
274
|
<summary><b>Is there a way to reserve a specific local port?</b></summary>
|
|
229
275
|
|
|
230
|
-
Yes โ pass `--port 9230` (CLI) or `preferredPort: 9230` (API). If
|
|
276
|
+
Yes โ pass `--port 9230` (CLI) or `preferredPort: 9230` (API). If any process already owns that port, `cf-debugger` fails closed with `PORT_UNAVAILABLE`; it never terminates an unrelated listener to claim a port.
|
|
231
277
|
|
|
232
278
|
</details>
|
|
233
279
|
|