@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 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
+ [![npm version](https://img.shields.io/npm/v/@saptools/cf-debugger.svg?style=flat&color=CB3837&logo=npm)](https://www.npmjs.com/package/@saptools/cf-debugger)
10
+ [![license](https://img.shields.io/npm/l/@saptools/cf-debugger.svg?style=flat&color=blue)](./LICENSE)
11
+ [![node](https://img.shields.io/node/v/@saptools/cf-debugger.svg?style=flat&color=339933&logo=node.js&logoColor=white)](https://nodejs.org)
12
+ [![install size](https://packagephobia.com/badge?p=@saptools/cf-debugger)](https://packagephobia.com/result?p=@saptools/cf-debugger)
13
+ [![types](https://img.shields.io/npm/types/@saptools/cf-debugger.svg?style=flat&color=3178C6&logo=typescript&logoColor=white)](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
@@ -0,0 +1,3 @@
1
+ declare function main(argv: readonly string[]): Promise<void>;
2
+
3
+ export { main };