@saptools/cf-debugger 0.1.16 โ 0.2.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 +297 -246
- package/dist/chunk-ZWAQD7KL.js +4734 -0
- package/dist/chunk-ZWAQD7KL.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +411 -2539
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +92 -8
- package/dist/index.js +25 -2370
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,323 +1,374 @@
|
|
|
1
|
-
|
|
1
|
+
# `@saptools/cf-debugger`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Open a verified Node.js inspector tunnel to an SAP BTP Cloud Foundry app from one
|
|
4
|
+
command. Each concurrent session has its own local port, isolated `CF_HOME`, and
|
|
5
|
+
lock-guarded state entry.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Signal the remote process, enable SSH if needed, forward `9229` to a free local port, and hand you back a ready-to-attach debugger โ with first-class support for **multiple concurrent tunnels** across terminals.
|
|
8
|
-
|
|
9
|
-
[](https://www.npmjs.com/package/@saptools/cf-debugger)
|
|
10
|
-
[](./LICENSE)
|
|
11
|
-
[](https://nodejs.org)
|
|
12
|
-
[](https://packagephobia.com/result?p=@saptools/cf-debugger)
|
|
13
|
-
[](https://www.typescriptlang.org)
|
|
14
|
-
|
|
15
|
-
[Install](#-install) โข [Quick Start](#-quick-start) โข [CLI](#-cli) โข [How it works](#-how-it-works) โข [FAQ](#-faq)
|
|
16
|
-
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## โจ Features
|
|
22
|
-
|
|
23
|
-
- ๐ **One-shot tunnel** โ auth, target, SSH-enable, USR1 signal, port forward, readiness probe โ all hidden behind `cf-debugger start`
|
|
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
|
-
- ๐ฏ **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
|
|
28
|
-
- ๐ **Deterministic ports** โ auto-assigned from a safe range (`20000โ20999`), or pick your own with `--port`
|
|
29
|
-
- ๐งฉ **CLI & typed API** โ every command has a zero-config Node.js equivalent with full TypeScript definitions
|
|
30
|
-
- ๐ชถ **Small + boring** โ one runtime dep (`commander`), no daemons, no magic
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## ๐ฆ Install
|
|
7
|
+
## Install
|
|
35
8
|
|
|
36
9
|
```bash
|
|
37
|
-
# Global CLI
|
|
38
10
|
npm install -g @saptools/cf-debugger
|
|
39
|
-
|
|
40
|
-
# Or as a dependency
|
|
41
|
-
npm install @saptools/cf-debugger
|
|
42
|
-
# pnpm add @saptools/cf-debugger
|
|
43
|
-
# yarn add @saptools/cf-debugger
|
|
44
11
|
```
|
|
45
12
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
---
|
|
13
|
+
Node.js 20+ and the official `cf` CLI are required. CF CLI v8 is recommended.
|
|
14
|
+
Non-`web` process targeting uses `cf ssh --process` and requires CF CLI v7+.
|
|
15
|
+
On macOS, `lsof` is a soft dependency used to prove which PID owns a local
|
|
16
|
+
listening port. Install it if `cf-debugger` reports `TUNNEL_OWNER_UNVERIFIED`.
|
|
52
17
|
|
|
53
|
-
##
|
|
18
|
+
## Quick start
|
|
54
19
|
|
|
55
20
|
```bash
|
|
56
|
-
# 1. Export your SAP SSO credentials (used for `cf auth` under the hood)
|
|
57
21
|
export SAP_EMAIL="you@company.com"
|
|
58
22
|
export SAP_PASSWORD="your-sap-password"
|
|
59
23
|
|
|
60
|
-
# 2. Open a debug tunnel for one app
|
|
61
24
|
cf-debugger start \
|
|
62
25
|
--region eu10 \
|
|
63
26
|
--org my-org \
|
|
64
27
|
--space dev \
|
|
65
28
|
--app my-app \
|
|
66
29
|
--verbose
|
|
67
|
-
|
|
68
|
-
# โ Debugger ready for my-app (eu10/my-org/dev).
|
|
69
|
-
# Local port: 20142
|
|
70
|
-
# Remote port: 9229
|
|
71
|
-
# Session id: 01HXYZ...
|
|
72
|
-
# Tunnel PID: 83421
|
|
73
|
-
# Press Ctrl+C to stop.
|
|
74
|
-
|
|
75
|
-
# 3. Attach your IDE (VSCode, Chrome DevTools, ...) to localhost:20142
|
|
76
30
|
```
|
|
77
31
|
|
|
78
|
-
|
|
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.
|
|
32
|
+
Successful output includes the complete attachment target:
|
|
81
33
|
|
|
82
|
-
|
|
34
|
+
```text
|
|
35
|
+
Debugger ready for my-app (eu10/my-org/dev).
|
|
36
|
+
Process: web
|
|
37
|
+
Instance: 0
|
|
38
|
+
Local port: 20142
|
|
39
|
+
Remote port: 9229
|
|
40
|
+
Session id: 550e8400-e29b-41d4-a716-446655440000
|
|
41
|
+
Tunnel PID: 83421
|
|
42
|
+
Node PID: 4312
|
|
43
|
+
Press Ctrl+C to stop.
|
|
44
|
+
```
|
|
83
45
|
|
|
84
|
-
|
|
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.
|
|
85
49
|
|
|
86
|
-
|
|
50
|
+
## CLI
|
|
87
51
|
|
|
88
|
-
|
|
52
|
+
### `start`
|
|
89
53
|
|
|
90
54
|
```bash
|
|
91
55
|
cf-debugger start --region eu10 --org my-org --space dev --app my-app
|
|
92
|
-
cf-debugger start
|
|
93
|
-
cf-debugger start
|
|
94
|
-
cf-debugger start
|
|
56
|
+
cf-debugger start eu10/my-org/dev/my-app --remote-port 9230
|
|
57
|
+
cf-debugger start my-app --process worker --instance 2 --node-pid 4312
|
|
58
|
+
cf-debugger start my-app --startup-timeout 300 --timeout 180 --verbose
|
|
95
59
|
```
|
|
96
60
|
|
|
97
|
-
|
|
61
|
+
When region, org, or space is omitted, the current `cf target` supplies it.
|
|
62
|
+
|
|
63
|
+
| Flag | Meaning |
|
|
98
64
|
| --- | --- |
|
|
99
|
-
| `--region <key>` |
|
|
100
|
-
| `--
|
|
101
|
-
| `--space <name>` |
|
|
102
|
-
| `--
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
| `--
|
|
106
|
-
| `--port <number>` |
|
|
107
|
-
| `--timeout <seconds>` |
|
|
108
|
-
| `--
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
`
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`
|
|
124
|
-
|
|
125
|
-
|
|
65
|
+
| `--region <key>` | SAP region key; defaults to the current CF target |
|
|
66
|
+
| `--api-endpoint <url>` | Exact endpoint override, including nonstandard landscapes |
|
|
67
|
+
| `--org <name>`, `--space <name>`, `--app <name>` | CF target and app |
|
|
68
|
+
| `--process <name>` | CF process, default `web` |
|
|
69
|
+
| `-i, --instance <index>` | Zero-based process instance, default `0` |
|
|
70
|
+
| `--node-pid <pid>` | Exact remote Node PID |
|
|
71
|
+
| `--port <number>` | Preferred local port; otherwise choose from `20000โ20999` |
|
|
72
|
+
| `--remote-port <number>` | Remote inspector port, default `9229` |
|
|
73
|
+
| `--timeout <seconds>` | Local-tunnel and inspector-readiness budget, default `180` |
|
|
74
|
+
| `--startup-timeout <seconds>` | Overall startup deadline, default `300`, maximum `1800` |
|
|
75
|
+
| `--allow-ssh-enable-restart` | Permit app-level SSH enablement and one app restart |
|
|
76
|
+
| `--no-ssh-enable-restart` | Explicitly forbid that mutation, overriding flag/environment |
|
|
77
|
+
| `--verbose` | Print status, retry, and redacted tunnel transport diagnostics |
|
|
78
|
+
|
|
79
|
+
> [!IMPORTANT]
|
|
80
|
+
> App restart is opt-in in 0.2.0. By default, a disabled-SSH app fails with
|
|
81
|
+
> `SSH_NOT_ENABLED` and no deployment mutation. Use
|
|
82
|
+
> `--allow-ssh-enable-restart`, or set `CF_DEBUGGER_ALLOW_RESTART=1`, only when
|
|
83
|
+
> restarting the named app is acceptable. `CF_DEBUGGER_ALLOW_RESTART=0` keeps
|
|
84
|
+
> the guard enabled for a production shell.
|
|
85
|
+
|
|
86
|
+
Even with permission, a restart occurs only when all of these are true:
|
|
87
|
+
|
|
88
|
+
1. the one-shot SSH error specifically says SSH support is disabled;
|
|
89
|
+
2. `cf ssh-enabled <app>` proves app-level SSH is disabled;
|
|
90
|
+
3. this invocation runs `cf enable-ssh <app>`;
|
|
91
|
+
4. a second probe proves app-level SSH is now enabled.
|
|
92
|
+
|
|
93
|
+
An unknown probe result, an already-enabled app, a permission error, or a
|
|
94
|
+
space-level SSH policy never causes a restart. Before an allowed restart,
|
|
95
|
+
stderr names the app and org/space. With an explicit `--node-pid`, automatic
|
|
96
|
+
restart is always rejected because the container replacement invalidates that
|
|
97
|
+
PID.
|
|
98
|
+
|
|
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.
|
|
108
|
+
|
|
109
|
+
### `stop`
|
|
126
110
|
|
|
127
111
|
```bash
|
|
128
|
-
cf-debugger stop --
|
|
129
|
-
cf-debugger stop
|
|
130
|
-
cf-debugger stop --
|
|
112
|
+
cf-debugger stop --session-id 550e8400-e29b-41d4-a716-446655440000
|
|
113
|
+
cf-debugger stop eu10/my-org/dev/my-app
|
|
114
|
+
cf-debugger stop my-app --api-endpoint https://api.cf.eu10.hana.ondemand.com
|
|
115
|
+
cf-debugger stop my-app --node-pid 4312
|
|
131
116
|
cf-debugger stop --all
|
|
117
|
+
cf-debugger stop --session-id 550e8400-e29b-41d4-a716-446655440000 --force
|
|
132
118
|
```
|
|
133
119
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
| `--session-id <id>` | Match session by its ID |
|
|
139
|
-
| `--all` | Stop every active session on this machine |
|
|
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
|
|
122
|
+
be combined with a positional selector, `--session-id`, or target selector
|
|
123
|
+
options; ambiguous scope is rejected instead of widening to every session.
|
|
140
124
|
|
|
141
|
-
|
|
125
|
+
`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.
|
|
142
133
|
|
|
143
|
-
|
|
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.
|
|
134
|
+
### `list` and `status`
|
|
148
135
|
|
|
149
136
|
```bash
|
|
150
|
-
cf-debugger list
|
|
137
|
+
cf-debugger list
|
|
138
|
+
cf-debugger status --session-id 550e8400-e29b-41d4-a716-446655440000
|
|
139
|
+
cf-debugger status eu10/my-org/dev/my-app --api-endpoint https://api.example
|
|
151
140
|
```
|
|
152
141
|
|
|
153
|
-
|
|
142
|
+
`list` prunes only records proven stale. PID liveness is paired with an optional
|
|
143
|
+
OS process-start token to detect PID reuse; older records without the additive
|
|
144
|
+
token retain the compatible PID-only check. A ready record is healthy only when
|
|
145
|
+
the complete listener-owner set contains its recorded tunnel PID. Ownership
|
|
146
|
+
that cannot be inspected remains explicit rather than being guessed.
|
|
147
|
+
|
|
148
|
+
`status` prints `null` only when no exact session matches. Ambiguity is a coded
|
|
149
|
+
`SESSION_AMBIGUOUS` failure; refine with session ID, API endpoint, or Node PID.
|
|
154
150
|
|
|
155
|
-
|
|
156
|
-
by `list`).
|
|
151
|
+
### `doctor`
|
|
157
152
|
|
|
158
153
|
```bash
|
|
159
|
-
cf-debugger
|
|
160
|
-
cf-debugger
|
|
154
|
+
cf-debugger doctor
|
|
155
|
+
cf-debugger doctor --cleanup
|
|
161
156
|
```
|
|
162
157
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
158
|
+
The default is read-only JSON reporting. It includes:
|
|
159
|
+
|
|
160
|
+
- every local state record with its health verdict and reason;
|
|
161
|
+
- v2 session homes with no state record;
|
|
162
|
+
- listeners in `20000โ20999` that no state record claims;
|
|
163
|
+
- leftover state temp, lock, recovery, stop-intent, and corrupt-backup files;
|
|
164
|
+
- legacy v1 state/homes and their credential-retention risk.
|
|
165
|
+
|
|
166
|
+
`--cleanup` removes only canonical orphan v2 homes and sufficiently old,
|
|
167
|
+
package-owned temp/lock/recovery/stop-intent artifacts. It revalidates orphan
|
|
168
|
+
homes against lock-guarded state immediately before deletion. It never signals
|
|
169
|
+
an unclaimed listener, removes corrupt evidence, or removes legacy v1 artifacts.
|
|
170
|
+
|
|
171
|
+
## Region resolution
|
|
172
|
+
|
|
173
|
+
Curated region keys are available from `listKnownRegionKeys()`. Keys matching
|
|
174
|
+
`aa00` or `aa00-000` also work before the curated table is updated:
|
|
175
|
+
`cf-debugger` synthesizes `https://api.cf.<key>.hana.ondemand.com`, or the
|
|
176
|
+
`.platform.sapcloud.cn` domain for `cn*`, and warns with the synthesized URL.
|
|
177
|
+
This supports new regions but makes a syntactically valid typo such as `eu99`
|
|
178
|
+
reach DNS; verify the warning or use `--api-endpoint`. Malformed keys fail early
|
|
179
|
+
with `UNKNOWN_REGION`.
|
|
180
|
+
|
|
181
|
+
## How it works
|
|
182
|
+
|
|
183
|
+
1. Resolve and validate the endpoint, deadline, target, and credentials.
|
|
184
|
+
2. Prune only provably stale state and register a unique session/port.
|
|
185
|
+
3. Run `cf api`, environment-only `cf auth`, `cf target`, and an app existence check.
|
|
186
|
+
4. Probe the selected instance through one-shot `cf ssh`.
|
|
187
|
+
5. If an inspector already owns the remote port and is Node, reuse that PID.
|
|
188
|
+
6. Otherwise:
|
|
189
|
+
- choose the sole Node PID when exactly one exists;
|
|
190
|
+
- with several Node PIDs, use the app `$PORT` listener only as a tiebreaker;
|
|
191
|
+
- otherwise fail `NODE_PROCESS_AMBIGUOUS`.
|
|
192
|
+
7. Send `SIGUSR1` when needed and prove the selected PID owns the configured
|
|
193
|
+
remote inspector port.
|
|
194
|
+
8. Spawn detached `cf ssh -N -L <local>:localhost:<remote>`.
|
|
195
|
+
9. Prove the spawned PID is among all owners of the local listener.
|
|
196
|
+
10. Request `GET /json/list` through the tunnel and require HTTP 200 plus a
|
|
197
|
+
non-empty target array whose first entry has a well-formed
|
|
198
|
+
`webSocketDebuggerUrl`.
|
|
199
|
+
11. Persist `ready` state and return a `DebuggerHandle`.
|
|
200
|
+
|
|
201
|
+
Node's Linux `cluster` default can leave the app `$PORT` socket owned by the
|
|
202
|
+
cluster primary rather than a request-handling worker. When several Node
|
|
203
|
+
processes exist, inspect the reported candidates and pass `--node-pid`
|
|
204
|
+
explicitly if a worker is the intended target.
|
|
205
|
+
|
|
206
|
+
The remote probe reads `/proc/<pid>/exe`, not argv. Its marker lines are filtered
|
|
207
|
+
by a strict `saptools-inspector-*` prefix, so CF SSH banners cannot impersonate
|
|
208
|
+
results.
|
|
209
|
+
|
|
210
|
+
### Status sequence
|
|
211
|
+
|
|
212
|
+
The public `onStatus` sequence is:
|
|
166
213
|
|
|
214
|
+
```text
|
|
215
|
+
starting โ logging-in โ targeting โ signaling
|
|
216
|
+
โ [ssh-enabling โ ssh-restarting โ signaling]
|
|
217
|
+
โ tunneling โ ready โ stopping โ stopped
|
|
167
218
|
```
|
|
168
|
-
โโโโโโโโโโโโโโโโโโโโโโ 1. cf api + cf auth (retry x3)
|
|
169
|
-
โ cf-debugger start โ 2. cf target -o <org> -s <space>
|
|
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
|
|
177
|
-
```
|
|
178
|
-
|
|
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`.
|
|
180
|
-
|
|
181
|
-
### Concurrency model
|
|
182
|
-
|
|
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")`).
|
|
184
|
-
- **Port allocation** โ on register, ports already used by other sessions are excluded; the first free port in `20000โ20999` wins.
|
|
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.
|
|
190
|
-
|
|
191
|
-
---
|
|
192
219
|
|
|
193
|
-
|
|
220
|
+
Verbose retries repeat the current phase with a message and remaining budget.
|
|
221
|
+
After `starting` is emitted, startup failure emits terminal `error` and never
|
|
222
|
+
follows it with `stopped`. Option/credential/region validation can reject before
|
|
223
|
+
the status stream begins, in which case no callback is emitted.
|
|
224
|
+
Unexpected tunnel failure after readiness likewise emits terminal `error`;
|
|
225
|
+
cleanup still runs without claiming a normal stop sequence.
|
|
194
226
|
|
|
195
|
-
|
|
227
|
+
## State and security
|
|
196
228
|
|
|
197
229
|
```text
|
|
198
|
-
~/.saptools/cf-debugger-state-v2.json
|
|
199
|
-
~/.saptools/cf-debugger-state-v2.lock
|
|
200
|
-
~/.saptools/cf-debugger-homes-v2/<
|
|
230
|
+
~/.saptools/cf-debugger-state-v2.json
|
|
231
|
+
~/.saptools/cf-debugger-state-v2.lock
|
|
232
|
+
~/.saptools/cf-debugger-homes-v2/<sessionId>/
|
|
201
233
|
```
|
|
202
234
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"remoteNodePid": 4312,
|
|
226
|
-
"cfHomeDir": "/home/developer/.saptools/cf-debugger-homes-v2/01HXYZABCD...",
|
|
227
|
-
"status": "ready",
|
|
228
|
-
"startedAt": "2026-04-18T00:00:00.000Z"
|
|
229
|
-
}
|
|
230
|
-
]
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
</details>
|
|
235
|
-
|
|
236
|
-
> [!IMPORTANT]
|
|
237
|
-
> Prefer the CLI commands (`list` / `status`) or the exported APIs over parsing these files โ the on-disk format is an implementation detail.
|
|
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
|
-
|
|
245
|
-
---
|
|
246
|
-
|
|
247
|
-
## โ FAQ
|
|
235
|
+
State is written as a `0600` temp file plus atomic rename while a token-owned
|
|
236
|
+
`open(..., "wx")` lock is held. The parent and session-home directories are
|
|
237
|
+
`0700`. Keep `~/.saptools` on a local filesystem: exclusive-create locking is
|
|
238
|
+
not reliably atomic on every NFS/SMB implementation. A generous age fallback
|
|
239
|
+
recovers foreign-host lock files, but it cannot make a network filesystem's
|
|
240
|
+
locking semantics safe.
|
|
241
|
+
|
|
242
|
+
Each isolated `CF_HOME` contains CF CLI credentials, including a live refresh
|
|
243
|
+
token. A clean stop removes it. If `TUNNEL_TERMINATION_FAILED` retains state and
|
|
244
|
+
the home, recover the process first, then run `stop --force`. If a record has
|
|
245
|
+
already disappeared but its canonical home remains, use `doctor --cleanup`.
|
|
246
|
+
Do not leave recovered homes indefinitely.
|
|
247
|
+
|
|
248
|
+
A damaged entry no longer erases healthy sessions. Invalid entries are dropped
|
|
249
|
+
individually, and the original state file is moved to a private
|
|
250
|
+
`.corrupt-<timestamp>-<uuid>` backup before any repair. Completely invalid JSON,
|
|
251
|
+
version, or root shape is likewise preserved before a fresh empty v2 file is
|
|
252
|
+
written.
|
|
253
|
+
|
|
254
|
+
Legacy `~/.saptools/cf-debugger-homes/` directories may still contain live
|
|
255
|
+
refresh and access tokens. `doctor` reports but never deletes them. After
|
|
256
|
+
confirming no v1 tunnel is running, remove them explicitly:
|
|
248
257
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
</details>
|
|
258
|
+
```bash
|
|
259
|
+
rm -rf "$HOME/.saptools/cf-debugger-homes" \
|
|
260
|
+
"$HOME/.saptools/cf-debugger-state.json" \
|
|
261
|
+
"$HOME/.saptools/cf-debugger-state.lock"
|
|
262
|
+
```
|
|
255
263
|
|
|
256
|
-
|
|
257
|
-
|
|
264
|
+
The v2 CLI never adopts or mutates v1 sessions. Prefer CLI/API operations over
|
|
265
|
+
editing the v2 JSON by hand.
|
|
258
266
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
267
|
+
Credentials are passed to `cf auth` as `CF_USERNAME` and `CF_PASSWORD` in the
|
|
268
|
+
child environment, never argv. CF command and tunnel diagnostics are bounded
|
|
269
|
+
and redact sensitive values before they reach errors or verbose output.
|
|
270
|
+
`CF_COLOR=false` is forced so ANSI escapes cannot corrupt output parsing.
|
|
263
271
|
|
|
264
|
-
|
|
272
|
+
## Environment variables
|
|
265
273
|
|
|
266
|
-
|
|
267
|
-
|
|
274
|
+
| Variable | Meaning |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| `SAP_EMAIL`, `SAP_PASSWORD` | CF authentication credentials |
|
|
277
|
+
| `CF_DEBUGGER_ALLOW_RESTART=0\|1` | Shell-level restart guard/default |
|
|
278
|
+
| `CF_DEBUGGER_CF_BIN` | Replace the `cf` executable; useful for wrappers and deterministic test stubs |
|
|
268
279
|
|
|
269
|
-
|
|
280
|
+
A caller-provided child environment cannot override the per-session `CF_HOME`.
|
|
270
281
|
|
|
271
|
-
|
|
282
|
+
## Errors and exit status
|
|
272
283
|
|
|
273
|
-
|
|
274
|
-
|
|
284
|
+
Documented domain failures use `Error [CODE]: ...`. An unexpected operating
|
|
285
|
+
system or filesystem failure can still surface as a plain `Error` without a
|
|
286
|
+
domain code; it is never converted into success.
|
|
275
287
|
|
|
276
|
-
|
|
288
|
+
| Code | Meaning |
|
|
289
|
+
| --- | --- |
|
|
290
|
+
| `UNKNOWN_REGION`, `UNSAFE_INPUT`, `MISSING_CREDENTIALS` | Invalid endpoint/key, selector, numeric input, or credentials |
|
|
291
|
+
| `CF_LOGIN_FAILED`, `CF_AUTH_FAILED`, `CF_TARGET_FAILED`, `CF_CLI_FAILED`, `CF_CLI_TIMEOUT` | CF command failure; rejected auth is not retried |
|
|
292
|
+
| `STARTUP_TIMEOUT`, `ABORTED` | Overall deadline expired, or caller/stop intent cancelled startup |
|
|
293
|
+
| `APP_NOT_FOUND` | The targeted app does not exist in the selected org/space |
|
|
294
|
+
| `SSH_NOT_ENABLED`, `SSH_PERMISSION_DENIED`, `SSH_STATE_UNKNOWN` | Restart refused/not useful, permission denied, or SSH state ambiguous |
|
|
295
|
+
| `NODE_PID_RESTART_UNSAFE` | Explicit PID cannot survive an app restart |
|
|
296
|
+
| `NODE_PROCESS_NOT_FOUND`, `NODE_PROCESS_AMBIGUOUS`, `NODE_PID_INVALID` | Remote Node selection failed closed |
|
|
297
|
+
| `USR1_SIGNAL_FAILED`, `INSPECTOR_NOT_READY`, `INSPECTOR_OWNER_MISMATCH`, `INSPECTOR_OUTPUT_TOO_LARGE` | Remote signal/ownership protocol failed |
|
|
298
|
+
| `TUNNEL_NOT_READY` | The spawned local forward did not bind |
|
|
299
|
+
| `PORT_UNAVAILABLE`, `TUNNEL_PROCESS_MISSING` | Local port allocation failed, or the spawned tunnel exposed no PID |
|
|
300
|
+
| `TUNNEL_OWNER_UNVERIFIED`, `TUNNEL_OWNER_MISMATCH` | Local listener ownership could not be proved |
|
|
301
|
+
| `INSPECTOR_UNREACHABLE` | Local forward bound, but `/json/list` did not prove an attachable remote inspector |
|
|
302
|
+
| `TUNNEL_EXITED`, `TUNNEL_TERMINATION_FAILED`, `TUNNEL_OWNERSHIP_UNVERIFIED` | Tunnel died, cleanup could not terminate it, or stop could not safely signal it |
|
|
303
|
+
| `SESSION_ALREADY_RUNNING`, `SESSION_AMBIGUOUS`, `SESSION_NOT_FOUND` | Session selection conflict |
|
|
304
|
+
| `SESSION_STATE_LOST`, `SESSION_STATE_CONFLICT`, `STATE_LOCK_TIMEOUT` | Atomic state/lifecycle invariant failed |
|
|
305
|
+
| `STOP_FAILED` | One entry in a batch stop failed with a non-coded internal error |
|
|
306
|
+
|
|
307
|
+
| Exit | Meaning |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| `0` | Clean completion |
|
|
310
|
+
| `1` | Coded operational/startup error, including `STARTUP_TIMEOUT` |
|
|
311
|
+
| `70` | Cleanup/termination failed and state was retained |
|
|
312
|
+
| SSH child's nonzero code | An unexpected numeric SSH exit is preserved, for example `255` |
|
|
313
|
+
| `128 + signal` | Tunnel child died from a signal, for example `137` for `SIGKILL` |
|
|
314
|
+
| `130`, `143` | User interrupted with `SIGINT` or `SIGTERM` |
|
|
315
|
+
|
|
316
|
+
## Programmatic API
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
import {
|
|
320
|
+
startDebugger,
|
|
321
|
+
stopDebugger,
|
|
322
|
+
stopAllDebuggers,
|
|
323
|
+
listSessions,
|
|
324
|
+
runDoctor,
|
|
325
|
+
} from "@saptools/cf-debugger";
|
|
326
|
+
|
|
327
|
+
const handle = await startDebugger({
|
|
328
|
+
region: "eu10",
|
|
329
|
+
org: "my-org",
|
|
330
|
+
space: "dev",
|
|
331
|
+
app: "my-app",
|
|
332
|
+
startupTimeoutMs: 300_000,
|
|
333
|
+
allowSshEnableRestart: false,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Attach to handle.session.localPort.
|
|
337
|
+
await handle.dispose();
|
|
338
|
+
|
|
339
|
+
const summary = await stopAllDebuggers();
|
|
340
|
+
// 0.2.0 returns per-session outcomes plus stopped/stale/pending/failed counts.
|
|
341
|
+
```
|
|
277
342
|
|
|
278
|
-
|
|
343
|
+
Persisted schema additions in 0.2.0 are optional. Absence means compatible
|
|
344
|
+
fallback behavior because an older mixed-version writer may strip fields it
|
|
345
|
+
does not know. State remains version `"2"`.
|
|
279
346
|
|
|
280
|
-
|
|
281
|
-
<summary><b>Can I use this in CI for integration tests?</b></summary>
|
|
347
|
+
## FAQ
|
|
282
348
|
|
|
283
|
-
|
|
349
|
+
### Does this modify the remote app?
|
|
284
350
|
|
|
285
|
-
|
|
351
|
+
Not by default. Inspector activation sends `SIGUSR1` to the verified Node
|
|
352
|
+
process, but app-level SSH enablement and `cf restart` require explicit
|
|
353
|
+
`--allow-ssh-enable-restart` permission. Even with permission, restart occurs
|
|
354
|
+
only after this run changes app SSH from proven disabled to proven enabled; an
|
|
355
|
+
already-enabled, unknown, permission-denied, or space-policy result never
|
|
356
|
+
restarts the app.
|
|
286
357
|
|
|
287
|
-
|
|
358
|
+
### Is session state always removed when the CLI exits?
|
|
288
359
|
|
|
289
|
-
|
|
360
|
+
Only after cleanup is verified. `TUNNEL_TERMINATION_FAILED` deliberately retains
|
|
361
|
+
state and the isolated credential-bearing `CF_HOME`; recover it with `doctor`
|
|
362
|
+
and `stop --force` instead of assuming the next `start` can proceed.
|
|
290
363
|
|
|
291
|
-
|
|
364
|
+
## Development
|
|
292
365
|
|
|
293
366
|
```bash
|
|
294
|
-
pnpm
|
|
295
|
-
pnpm --filter @saptools/cf-debugger build
|
|
367
|
+
pnpm --filter @saptools/cf-debugger lint
|
|
296
368
|
pnpm --filter @saptools/cf-debugger typecheck
|
|
297
369
|
pnpm --filter @saptools/cf-debugger test:unit
|
|
298
|
-
pnpm --filter @saptools/cf-debugger
|
|
370
|
+
pnpm --filter @saptools/cf-debugger build
|
|
299
371
|
```
|
|
300
372
|
|
|
301
|
-
The
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
## ๐ Related
|
|
306
|
-
|
|
307
|
-
- โ๏ธ [`@saptools/cf-sync`](https://www.npmjs.com/package/@saptools/cf-sync) โ snapshot every region / org / space / app you can reach into one JSON file
|
|
308
|
-
- ๐ [`@saptools/cf-xsuaa`](https://www.npmjs.com/package/@saptools/cf-xsuaa) โ fetch XSUAA credentials and cached OAuth2 tokens for any CF app
|
|
309
|
-
- ๐๏ธ [saptools monorepo](https://github.com/dongitran/saptools) โ the full toolbox
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
## ๐จโ๐ป Author
|
|
314
|
-
|
|
315
|
-
**dongtran** โจ
|
|
316
|
-
|
|
317
|
-
## ๐ License
|
|
318
|
-
|
|
319
|
-
MIT
|
|
320
|
-
|
|
321
|
-
---
|
|
322
|
-
|
|
323
|
-
Made with โค๏ธ to make your work life easier!
|
|
373
|
+
The fake-backed tests set `CF_DEBUGGER_CF_BIN`. Live E2E tests require a safe
|
|
374
|
+
non-production CF target and credentials; do not run them against production.
|