@saptools/cf-debugger 0.2.0 → 0.2.2

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
@@ -14,6 +14,9 @@ Node.js 20+ and the official `cf` CLI are required. CF CLI v8 is recommended.
14
14
  Non-`web` process targeting uses `cf ssh --process` and requires CF CLI v7+.
15
15
  On macOS, `lsof` is a soft dependency used to prove which PID owns a local
16
16
  listening port. Install it if `cf-debugger` reports `TUNNEL_OWNER_UNVERIFIED`.
17
+ Without it, an explicit stop refuses to signal a state-recorded PID whose port
18
+ ownership cannot be proved; teardown still completes when the spawned child is
19
+ independently confirmed dead.
17
20
 
18
21
  ## Quick start
19
22
 
@@ -43,9 +46,11 @@ Debugger ready for my-app (eu10/my-org/dev).
43
46
  Press Ctrl+C to stop.
44
47
  ```
45
48
 
46
- Attach the IDE to `127.0.0.1:20142`. The remote inspector advertises its own
47
- `ws://127.0.0.1:9229/...` URL through the tunnel; `cf-debugger` validates that
48
- the URL exists but does not print or follow that remote-authority URL.
49
+ Attach the IDE to `127.0.0.1:20142`. Node derives the advertised
50
+ `webSocketDebuggerUrl` from the incoming request's `Host` header, so a URL
51
+ returned through the forward already names the local forwarded port and is
52
+ directly usable. `cf-debugger` validates that the URL is well formed but does
53
+ not print or follow it.
49
54
 
50
55
  ## CLI
51
56
 
@@ -63,13 +68,13 @@ When region, org, or space is omitted, the current `cf target` supplies it.
63
68
  | Flag | Meaning |
64
69
  | --- | --- |
65
70
  | `--region <key>` | SAP region key; defaults to the current CF target |
66
- | `--api-endpoint <url>` | Exact endpoint override, including nonstandard landscapes |
71
+ | `--api-endpoint <url>` | Absolute HTTPS endpoint override, including private/nonstandard hosts |
67
72
  | `--org <name>`, `--space <name>`, `--app <name>` | CF target and app |
68
73
  | `--process <name>` | CF process, default `web` |
69
74
  | `-i, --instance <index>` | Zero-based process instance, default `0` |
70
75
  | `--node-pid <pid>` | Exact remote Node PID |
71
76
  | `--port <number>` | Preferred local port; otherwise choose from `20000–20999` |
72
- | `--remote-port <number>` | Remote inspector port, default `9229` |
77
+ | `--remote-port <number>` | Port where cf-debugger looks for an existing remote inspector, default `9229` |
73
78
  | `--timeout <seconds>` | Local-tunnel and inspector-readiness budget, default `180` |
74
79
  | `--startup-timeout <seconds>` | Overall startup deadline, default `300`, maximum `1800` |
75
80
  | `--allow-ssh-enable-restart` | Permit app-level SSH enablement and one app restart |
@@ -81,7 +86,15 @@ When region, org, or space is omitted, the current `cf target` supplies it.
81
86
  > `SSH_NOT_ENABLED` and no deployment mutation. Use
82
87
  > `--allow-ssh-enable-restart`, or set `CF_DEBUGGER_ALLOW_RESTART=1`, only when
83
88
  > restarting the named app is acceptable. `CF_DEBUGGER_ALLOW_RESTART=0` keeps
84
- > the guard enabled for a production shell.
89
+ > the guard enabled for a production shell and also vetoes a programmatic
90
+ > `allowSshEnableRestart: true`. The environment value `1` can opt in the CLI,
91
+ > but never grants permission to a programmatic caller by itself.
92
+
93
+ `--api-endpoint` accepts arbitrary hostnames but requires an absolute `https:`
94
+ URL with no userinfo, query, fragment, or non-root path. Invalid values are
95
+ rejected before `cf api` or `cf auth`. Version 0.2.2 intentionally rejects
96
+ plaintext `http://` landscapes because credentials would be sent to that
97
+ endpoint.
85
98
 
86
99
  Even with permission, a restart occurs only when all of these are true:
87
100
 
@@ -96,15 +109,18 @@ stderr names the app and org/space. With an explicit `--node-pid`, automatic
96
109
  restart is always rejected because the container replacement invalidates that
97
110
  PID.
98
111
 
99
- The per-attempt CF command timeout is 60 seconds. Transient transport errors
100
- retry with bounded exponential delays while the single overall startup budget
101
- still has time. `cf auth` has no outer retry loop, and rejected credentials fail
102
- after one attempt; repeated manual failures may count toward a tenant's
103
- identity-provider lockout policy. The one-shot SSH signal and readiness wait are
104
- also clamped to the same overall deadline. No retry configuration multiplies
105
- startup work past `--startup-timeout`. After a timeout, fail-closed tunnel and
106
- state cleanup still runs; that safety teardown can briefly extend the observed
107
- command wall time rather than abandoning an unverified child.
112
+ Read-only CF commands have a 60-second per-attempt cap and retry transient
113
+ transport errors with bounded exponential delays while the single overall
114
+ startup budget still has time. Deployment-mutating commands have a separate
115
+ 180-second cap and are never retried: a timed-out enable/restart reports
116
+ `CF_MUTATION_TIMEOUT` because the server-side mutation may still be completing.
117
+ `cf auth` has no outer retry loop, and rejected credentials fail after one
118
+ attempt; repeated manual failures may count toward a tenant's identity-provider
119
+ lockout policy. The one-shot SSH signal and readiness wait are also clamped to
120
+ the same overall deadline. No retry configuration multiplies startup work past
121
+ `--startup-timeout`. After a timeout, fail-closed tunnel and state cleanup still
122
+ runs; that safety teardown can briefly extend the observed command wall time
123
+ rather than abandoning an unverified child.
108
124
 
109
125
  ### `stop`
110
126
 
@@ -117,19 +133,20 @@ cf-debugger stop --all
117
133
  cf-debugger stop --session-id 550e8400-e29b-41d4-a716-446655440000 --force
118
134
  ```
119
135
 
120
- `stop --all` attempts every local session, reports stopped, stale, pending, and
121
- failed outcomes separately, and exits nonzero if any session failed. It cannot
136
+ `stop --all` attempts every local session, reports stopped, forced, stale,
137
+ pending, and failed outcomes separately, and exits `70` if any tunnel cleanup
138
+ failed (`1` for other failures). It cannot
122
139
  be combined with a positional selector, `--session-id`, or target selector
123
140
  options; ambiguous scope is rejected instead of widening to every session.
124
141
 
125
142
  `stop --force` means “forget safely,” not “kill harder.” It never signals a PID
126
- whose tunnel ownership is unproven. It removes the state record and only the
127
- exact derived v2 `CF_HOME`, then warns with the abandoned PID and port so they
128
- can be investigated manually. This is the supported recovery for a dead tunnel
129
- whose old port is now occupied by another program, or an abandoned startup
130
- record that cannot be verified. If a damaged record names a non-owned home
131
- path, that path is left untouched and named in the warning while the record is
132
- still forgotten.
143
+ whose tunnel ownership is unproven. It forgets the state record first, then
144
+ best-effort removes only the exact derived v2 `CF_HOME`. A deletion failure is
145
+ non-fatal and warns that the retained path may contain a live refresh token;
146
+ `doctor` then reports it as an orphan eligible for `doctor --cleanup`. The
147
+ warning also names the abandoned PID and port for manual investigation. If a
148
+ damaged record names a non-owned home path, that path is left untouched while
149
+ the record is still forgotten.
133
150
 
134
151
  ### `list` and `status`
135
152
 
@@ -161,12 +178,16 @@ The default is read-only JSON reporting. It includes:
161
178
  - v2 session homes with no state record;
162
179
  - listeners in `20000–20999` that no state record claims;
163
180
  - leftover state temp, lock, recovery, stop-intent, and corrupt-backup files;
164
- - legacy v1 state/homes and their credential-retention risk.
181
+ - legacy v1 state/homes, home count, parseable claimed sessions, conservative
182
+ PID liveness, and their credential-retention risk.
165
183
 
166
184
  `--cleanup` removes only canonical orphan v2 homes and sufficiently old,
167
185
  package-owned temp/lock/recovery/stop-intent artifacts. It revalidates orphan
168
186
  homes against lock-guarded state immediately before deletion. It never signals
169
187
  an unclaimed listener, removes corrupt evidence, or removes legacy v1 artifacts.
188
+ Corrupt backups include a manual-removal command in the report while remaining
189
+ ineligible for automatic cleanup. A symlinked v2 homes root is reported and
190
+ never traversed.
170
191
 
171
192
  ## Region resolution
172
193
 
@@ -205,7 +226,13 @@ explicitly if a worker is the intended target.
205
226
 
206
227
  The remote probe reads `/proc/<pid>/exe`, not argv. Its marker lines are filtered
207
228
  by a strict `saptools-inspector-*` prefix, so CF SSH banners cannot impersonate
208
- results.
229
+ results. GNU `find -lname` is capability-probed and is normally fast; the
230
+ portable fallback walks descriptors with `readlink` and can take roughly
231
+ 10–12 seconds in a descriptor-heavy non-GNU container.
232
+
233
+ `--remote-port` does not choose the port opened by `SIGUSR1`; Node always uses
234
+ its default inspector port for that signal. A non-default value is therefore
235
+ for an app already started with matching `--inspect=<port>`.
209
236
 
210
237
  ### Status sequence
211
238
 
@@ -251,6 +278,13 @@ individually, and the original state file is moved to a private
251
278
  version, or root shape is likewise preserved before a fresh empty v2 file is
252
279
  written.
253
280
 
281
+ During mixed-version use, an older cf-debugger sharing this v2 file may strip
282
+ optional fields it does not understand, including process-identity and startup
283
+ budget data. Newer code treats an absent/legacy identity as “cannot tell” and
284
+ retains the record with PID-only compatibility rather than pruning it; `doctor`
285
+ surfaces that degraded verdict. Updating every installed consumer is the only
286
+ way to restore the stronger identity check persistently.
287
+
254
288
  Legacy `~/.saptools/cf-debugger-homes/` directories may still contain live
255
289
  refresh and access tokens. `doctor` reports but never deletes them. After
256
290
  confirming no v1 tunnel is running, remove them explicitly:
@@ -274,7 +308,7 @@ and redact sensitive values before they reach errors or verbose output.
274
308
  | Variable | Meaning |
275
309
  | --- | --- |
276
310
  | `SAP_EMAIL`, `SAP_PASSWORD` | CF authentication credentials |
277
- | `CF_DEBUGGER_ALLOW_RESTART=0\|1` | Shell-level restart guard/default |
311
+ | `CF_DEBUGGER_ALLOW_RESTART=0\|1` | CLI default; `0` is also a hard veto for the programmatic API |
278
312
  | `CF_DEBUGGER_CF_BIN` | Replace the `cf` executable; useful for wrappers and deterministic test stubs |
279
313
 
280
314
  A caller-provided child environment cannot override the per-session `CF_HOME`.
@@ -289,6 +323,7 @@ domain code; it is never converted into success.
289
323
  | --- | --- |
290
324
  | `UNKNOWN_REGION`, `UNSAFE_INPUT`, `MISSING_CREDENTIALS` | Invalid endpoint/key, selector, numeric input, or credentials |
291
325
  | `CF_LOGIN_FAILED`, `CF_AUTH_FAILED`, `CF_TARGET_FAILED`, `CF_CLI_FAILED`, `CF_CLI_TIMEOUT` | CF command failure; rejected auth is not retried |
326
+ | `CF_MUTATION_TIMEOUT` | A deployment mutation timed out, was not retried, and may still be completing remotely |
292
327
  | `STARTUP_TIMEOUT`, `ABORTED` | Overall deadline expired, or caller/stop intent cancelled startup |
293
328
  | `APP_NOT_FOUND` | The targeted app does not exist in the selected org/space |
294
329
  | `SSH_NOT_ENABLED`, `SSH_PERMISSION_DENIED`, `SSH_STATE_UNKNOWN` | Restart refused/not useful, permission denied, or SSH state ambiguous |
@@ -302,13 +337,15 @@ domain code; it is never converted into success.
302
337
  | `TUNNEL_EXITED`, `TUNNEL_TERMINATION_FAILED`, `TUNNEL_OWNERSHIP_UNVERIFIED` | Tunnel died, cleanup could not terminate it, or stop could not safely signal it |
303
338
  | `SESSION_ALREADY_RUNNING`, `SESSION_AMBIGUOUS`, `SESSION_NOT_FOUND` | Session selection conflict |
304
339
  | `SESSION_STATE_LOST`, `SESSION_STATE_CONFLICT`, `STATE_LOCK_TIMEOUT` | Atomic state/lifecycle invariant failed |
340
+ | `CF_HOME_CLEANUP_FAILED` | Session state was removed but lifecycle cleanup could not remove its exact credential-bearing CF home |
341
+ | `PACKAGE_METADATA_INVALID` | Runtime package metadata has no usable version |
305
342
  | `STOP_FAILED` | One entry in a batch stop failed with a non-coded internal error |
306
343
 
307
344
  | Exit | Meaning |
308
345
  | --- | --- |
309
346
  | `0` | Clean completion |
310
347
  | `1` | Coded operational/startup error, including `STARTUP_TIMEOUT` |
311
- | `70` | Cleanup/termination failed and state was retained |
348
+ | `70` | Tunnel or lifecycle cleanup failed; ownership state or a credential-bearing home may remain |
312
349
  | SSH child's nonzero code | An unexpected numeric SSH exit is preserved, for example `255` |
313
350
  | `128 + signal` | Tunnel child died from a signal, for example `137` for `SIGKILL` |
314
351
  | `130`, `143` | User interrupted with `SIGINT` or `SIGTERM` |
@@ -337,7 +374,7 @@ const handle = await startDebugger({
337
374
  await handle.dispose();
338
375
 
339
376
  const summary = await stopAllDebuggers();
340
- // 0.2.0 returns per-session outcomes plus stopped/stale/pending/failed counts.
377
+ // Outcomes and counts distinguish stopped/forced/stale/pending/failed.
341
378
  ```
342
379
 
343
380
  Persisted schema additions in 0.2.0 are optional. Absence means compatible