@saptools/cf-debugger 0.1.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 +333 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1088 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +890 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ `@saptools/cf-debugger`
|
|
4
|
+
|
|
5
|
+
**Open a Node.js inspector tunnel to any SAP BTP Cloud Foundry app โ in one command.**
|
|
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) โข [API](#-programmatic-usage) โข [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
|
+
- ๐ก๏ธ **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
|
|
27
|
+
- ๐ **Deterministic ports** โ auto-assigned from a safe range (`20000โ20999`), or pick your own with `--port`
|
|
28
|
+
- ๐งฉ **CLI & typed API** โ every command has a zero-config Node.js equivalent with full TypeScript definitions
|
|
29
|
+
- ๐ชถ **Small + boring** โ one runtime dep (`commander`), no daemons, no magic
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## ๐ฆ Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Global CLI
|
|
37
|
+
npm install -g @saptools/cf-debugger
|
|
38
|
+
|
|
39
|
+
# Or as a dependency
|
|
40
|
+
npm install @saptools/cf-debugger
|
|
41
|
+
# pnpm add @saptools/cf-debugger
|
|
42
|
+
# yarn add @saptools/cf-debugger
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
> [!NOTE]
|
|
46
|
+
> Requires **Node.js โฅ 20** and the official **`cf` CLI** on `PATH` (v8 recommended).
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## ๐ Quick Start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# 1. Export your SAP SSO credentials (used for `cf auth` under the hood)
|
|
54
|
+
export SAP_EMAIL="you@company.com"
|
|
55
|
+
export SAP_PASSWORD="your-sap-password"
|
|
56
|
+
|
|
57
|
+
# 2. Open a debug tunnel for one app
|
|
58
|
+
cf-debugger start \
|
|
59
|
+
--region eu10 \
|
|
60
|
+
--org my-org \
|
|
61
|
+
--space dev \
|
|
62
|
+
--app my-srv \
|
|
63
|
+
--verbose
|
|
64
|
+
|
|
65
|
+
# โ Debugger ready for my-srv (eu10/my-org/dev).
|
|
66
|
+
# Local port: 20142
|
|
67
|
+
# Remote port: 9229
|
|
68
|
+
# Session id: 01HXYZ...
|
|
69
|
+
# PID: 83421
|
|
70
|
+
# Press Ctrl+C to stop.
|
|
71
|
+
|
|
72
|
+
# 3. Attach your IDE (VSCode, Chrome DevTools, ...) to localhost:20142
|
|
73
|
+
```
|
|
74
|
+
|
|
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.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ๐งฐ CLI
|
|
80
|
+
|
|
81
|
+
### โถ๏ธ `cf-debugger start`
|
|
82
|
+
|
|
83
|
+
Open a tunnel for one app and keep running until interrupted.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cf-debugger start --region eu10 --org my-org --space dev --app my-srv
|
|
87
|
+
cf-debugger start --region eu10 --org my-org --space dev --app my-srv --port 9230
|
|
88
|
+
cf-debugger start --region eu10 --org my-org --space dev --app my-srv --timeout 60 --verbose
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
| Flag | Description |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `--region <key>` | **Required.** CF region key (e.g. `eu10`, `ap10`, `us10`) |
|
|
94
|
+
| `--org <name>` | **Required.** CF org name |
|
|
95
|
+
| `--space <name>` | **Required.** CF space name |
|
|
96
|
+
| `--app <name>` | **Required.** CF app name |
|
|
97
|
+
| `--port <number>` | Preferred local port (auto-assigned in `20000โ20999` if omitted) |
|
|
98
|
+
| `--timeout <seconds>` | Tunnel-ready timeout (default: `30`) |
|
|
99
|
+
| `--verbose` | Print every status transition |
|
|
100
|
+
|
|
101
|
+
### โน๏ธ `cf-debugger stop`
|
|
102
|
+
|
|
103
|
+
Stop a specific session or everything at once.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
cf-debugger stop --region eu10 --org my-org --space dev --app my-srv
|
|
107
|
+
cf-debugger stop --session-id 01HXYZABCD...
|
|
108
|
+
cf-debugger stop --all
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
| Flag | Description |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `--region` / `--org` / `--space` / `--app` | Match session by key (all four required together) |
|
|
114
|
+
| `--session-id <id>` | Match session by its ID |
|
|
115
|
+
| `--all` | Stop every active session on this machine |
|
|
116
|
+
|
|
117
|
+
### ๐ `cf-debugger list`
|
|
118
|
+
|
|
119
|
+
Print every active session this machine owns as JSON.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
cf-debugger list | jq '.[] | {app, localPort, status}'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### ๐ `cf-debugger status`
|
|
126
|
+
|
|
127
|
+
Print one session by key (or `null` if no active session matches).
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cf-debugger status --region eu10 --org my-org --space dev --app my-srv
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## ๐งโ๐ป Programmatic Usage
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
import {
|
|
139
|
+
startDebugger,
|
|
140
|
+
stopDebugger,
|
|
141
|
+
listSessions,
|
|
142
|
+
getSession,
|
|
143
|
+
resolveApiEndpoint,
|
|
144
|
+
} from "@saptools/cf-debugger";
|
|
145
|
+
|
|
146
|
+
const handle = await startDebugger({
|
|
147
|
+
region: "eu10",
|
|
148
|
+
org: "my-org",
|
|
149
|
+
space: "dev",
|
|
150
|
+
app: "my-srv",
|
|
151
|
+
email: process.env["SAP_EMAIL"],
|
|
152
|
+
password: process.env["SAP_PASSWORD"],
|
|
153
|
+
verbose: true,
|
|
154
|
+
onStatus: (status, message) => {
|
|
155
|
+
console.log(`[${status}]`, message ?? "");
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
console.log(`Attach your debugger to localhost:${handle.session.localPort}`);
|
|
160
|
+
|
|
161
|
+
// Later โ shut the tunnel down and clean up state:
|
|
162
|
+
await handle.dispose();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
<details>
|
|
166
|
+
<summary><b>๐ Full export list</b></summary>
|
|
167
|
+
|
|
168
|
+
| Export | Description |
|
|
169
|
+
| --- | --- |
|
|
170
|
+
| `startDebugger(options)` | Open a tunnel; returns a `DebuggerHandle` |
|
|
171
|
+
| `stopDebugger({ sessionId?, key? })` | Stop one session by id or by key |
|
|
172
|
+
| `stopAllDebuggers()` | Stop every session owned by this process/machine |
|
|
173
|
+
| `listSessions()` | Return every live session as `ActiveSession[]` |
|
|
174
|
+
| `getSession(key)` | Return one session matching `{ region, org, space, app }` |
|
|
175
|
+
| `resolveApiEndpoint(key, override?)` | Map a region key to its API endpoint |
|
|
176
|
+
| `sessionKeyString(key)` | Stable string form of a session key |
|
|
177
|
+
| `CfDebuggerError` | Rich error class with typed `code` |
|
|
178
|
+
|
|
179
|
+
</details>
|
|
180
|
+
|
|
181
|
+
<details>
|
|
182
|
+
<summary><b>๐งช Error codes</b></summary>
|
|
183
|
+
|
|
184
|
+
| Code | When |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| `MISSING_CREDENTIALS` | No `SAP_EMAIL` / `SAP_PASSWORD` in env or options |
|
|
187
|
+
| `SESSION_ALREADY_RUNNING` | A session already exists for the same `region/org/space/app` |
|
|
188
|
+
| `CF_LOGIN_FAILED` | `cf api` / `cf auth` rejected the credentials |
|
|
189
|
+
| `CF_TARGET_FAILED` | Org or space not reachable |
|
|
190
|
+
| `SSH_NOT_ENABLED` | SSH disabled at space or app level and could not be enabled |
|
|
191
|
+
| `USR1_SIGNAL_FAILED` | Remote `kill -s USR1` could not find the node PID |
|
|
192
|
+
| `TUNNEL_NOT_READY` | Inspector didn't respond on port 9229 before timeout |
|
|
193
|
+
| `PORT_UNAVAILABLE` | Preferred local port is taken and could not be freed |
|
|
194
|
+
|
|
195
|
+
</details>
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## ๐ญ How it works
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
โโโโโโโโโโโโโโโโโโโโโโ 1. cf api + cf auth (retry x3)
|
|
203
|
+
โ cf-debugger start โ 2. cf target -o <org> -s <space>
|
|
204
|
+
โ region/org/ โ 3. cf ssh-enabled <app>
|
|
205
|
+
โ space/app โ โโบ 4. cf enable-ssh + cf restart (only if needed)
|
|
206
|
+
โโโโโโโโโโโโโโโโโโโโโโ 5. cf ssh <app> -c 'kill -s USR1 $(pidof node)'
|
|
207
|
+
โ 6. cf ssh <app> -N -L <localPort>:localhost:9229
|
|
208
|
+
โผ 7. TCP probe localhost:<localPort> until ready
|
|
209
|
+
DebuggerHandle 8. Save ActiveSession to ~/.saptools/cf-debugger-state.json
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
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`.
|
|
213
|
+
|
|
214
|
+
### Concurrency model
|
|
215
|
+
|
|
216
|
+
- **Atomic state** โ `~/.saptools/cf-debugger-state.json` is written via temp-file + `rename`, guarded by a short-lived `.lock` file (`open(..., "wx")`).
|
|
217
|
+
- **Port allocation** โ on register, ports already used by other sessions are excluded; the first free port in `20000โ20999` wins.
|
|
218
|
+
- **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.
|
|
219
|
+
- **Stale pruning** โ reading the state file checks every recorded PID with `process.kill(pid, 0)`; dead entries are dropped before returning the list.
|
|
220
|
+
- **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.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## ๐ Output Files
|
|
225
|
+
|
|
226
|
+
All state lives under your home directory:
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
~/.saptools/cf-debugger-state.json # active sessions (atomic JSON)
|
|
230
|
+
~/.saptools/cf-debugger-state.lock # short-lived lock file
|
|
231
|
+
~/.saptools/cf-debugger-homes/<id>/ # per-session isolated CF_HOME
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
<details>
|
|
235
|
+
<summary><b>๐ฌ Shape of <code>cf-debugger-state.json</code></b></summary>
|
|
236
|
+
|
|
237
|
+
```jsonc
|
|
238
|
+
{
|
|
239
|
+
"version": 1,
|
|
240
|
+
"sessions": [
|
|
241
|
+
{
|
|
242
|
+
"sessionId": "01HXYZABCD...",
|
|
243
|
+
"region": "eu10",
|
|
244
|
+
"org": "my-org",
|
|
245
|
+
"space": "dev",
|
|
246
|
+
"app": "my-srv",
|
|
247
|
+
"localPort": 20142,
|
|
248
|
+
"remotePort": 9229,
|
|
249
|
+
"pid": 83421,
|
|
250
|
+
"status": "ready",
|
|
251
|
+
"startedAt": "2026-04-18T00:00:00.000Z"
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
</details>
|
|
258
|
+
|
|
259
|
+
> [!IMPORTANT]
|
|
260
|
+
> Prefer the CLI commands (`list` / `status`) or the exported APIs over parsing these files โ the on-disk format is an implementation detail.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## โ FAQ
|
|
265
|
+
|
|
266
|
+
<details>
|
|
267
|
+
<summary><b>Can I run multiple debuggers at once?</b></summary>
|
|
268
|
+
|
|
269
|
+
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.
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
<details>
|
|
274
|
+
<summary><b>Does this modify the remote app?</b></summary>
|
|
275
|
+
|
|
276
|
+
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.
|
|
277
|
+
|
|
278
|
+
</details>
|
|
279
|
+
|
|
280
|
+
<details>
|
|
281
|
+
<summary><b>What if my app crashes while the tunnel is open?</b></summary>
|
|
282
|
+
|
|
283
|
+
The TCP probe will fail on reconnect and the CLI will exit with the SSH child's code. The state entry is removed on exit, so the next `start` for the same app works immediately.
|
|
284
|
+
|
|
285
|
+
</details>
|
|
286
|
+
|
|
287
|
+
<details>
|
|
288
|
+
<summary><b>Is there a way to reserve a specific local port?</b></summary>
|
|
289
|
+
|
|
290
|
+
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`.
|
|
291
|
+
|
|
292
|
+
</details>
|
|
293
|
+
|
|
294
|
+
<details>
|
|
295
|
+
<summary><b>Can I use this in CI for integration tests?</b></summary>
|
|
296
|
+
|
|
297
|
+
You can, but it's designed for interactive debugging. CI usually wants a short-lived request against the running app, not a persistent inspector tunnel โ consider `cf ssh -L` directly for that case.
|
|
298
|
+
|
|
299
|
+
</details>
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## ๐ ๏ธ Development
|
|
304
|
+
|
|
305
|
+
From the monorepo root:
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
pnpm install
|
|
309
|
+
pnpm --filter @saptools/cf-debugger build
|
|
310
|
+
pnpm --filter @saptools/cf-debugger typecheck
|
|
311
|
+
pnpm --filter @saptools/cf-debugger test:unit
|
|
312
|
+
pnpm --filter @saptools/cf-debugger test:e2e
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The e2e suite hits live SAP BTP CF. Set `CF_DEBUGGER_E2E_REGIONS=eu10,ap10` (plus `SAP_EMAIL` / `SAP_PASSWORD`) to restrict which regions it searches for a running app.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## ๐ Related
|
|
320
|
+
|
|
321
|
+
- โ๏ธ [`@saptools/cf-sync`](https://www.npmjs.com/package/@saptools/cf-sync) โ snapshot every region / org / space / app you can reach into one JSON file
|
|
322
|
+
- ๐ [`@saptools/cf-xsuaa`](https://www.npmjs.com/package/@saptools/cf-xsuaa) โ fetch XSUAA credentials and cached OAuth2 tokens for any CF app
|
|
323
|
+
- ๐๏ธ [saptools monorepo](https://github.com/dongitran/saptools) โ the full toolbox
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
<div align="center">
|
|
328
|
+
|
|
329
|
+
Made with โค๏ธ for SAP BTP developers who'd rather attach than log-print.
|
|
330
|
+
|
|
331
|
+
**License** ยท [MIT](./LICENSE)
|
|
332
|
+
|
|
333
|
+
</div>
|
package/dist/cli.d.ts
ADDED