@saptools/cf-debugger 0.1.13 โ†’ 0.1.15

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 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
- - ๐Ÿ›ก๏ธ **Duplicate-session protection** โ€” the same `region/org/space/app` cannot be debugged twice simultaneously (returns `SESSION_ALREADY_RUNNING`)
26
- - ๐Ÿงน **Crash-proof state** โ€” stale session entries are auto-pruned on next read using PID liveness checks
25
+ - ๐ŸŽฏ **Exact process targeting** โ€” select a CF process, instance, and optional Node PID; ambiguous Node processes fail 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` (v8 recommended).
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: 83421
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 โ€” the SSH subprocess is killed, the local port is released, and the session is removed from the shared state file.
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 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 180 seconds.
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 every active session this machine owns as JSON.
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 session by key (or `null` if no active session matches).
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-enabled <app>
145
- โ”‚ space/app โ”‚ โ”€โ–บ 4. cf enable-ssh + cf restart (only if needed)
146
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ 5. cf ssh <app> -c 'kill -s USR1 $(pidof node)'
147
- โ”‚ 6. cf ssh <app> -N -L <localPort>:localhost:9229
148
- โ–ผ 7. TCP probe localhost:<localPort> until ready
149
- DebuggerHandle 8. Save ActiveSession to ~/.saptools/cf-debugger-state.json
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 `.lock` file (`open(..., "wx")`).
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
- - **Stale pruning** โ€” reading the state file checks every recorded PID with `process.kill(pid, 0)`; dead entries are dropped before returning the list.
160
- - **Duplicate guard** โ€” trying to start a second tunnel for the same `region/org/space/app` fails fast with `SESSION_ALREADY_RUNNING` instead of racing for the port.
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": 1,
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 two different apps, and both tunnels come up on separate local ports. `cf-debugger list` shows you everything at once. The only thing you can't do is debug the same app twice in parallel.
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. If it is, `cf-debugger` runs `cf enable-ssh` + `cf restart` to turn it on โ€” otherwise it only sends a `SIGUSR1` to the Node.js process (which tells Node to start its inspector). No code, no env vars, no manifest is touched.
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 it's occupied by a non-tunnel process, `cf-debugger` will try to free it once; if another tunnel already owns it, you'll get `PORT_UNAVAILABLE`.
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