@saptools/cf-inspector 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/LICENSE +21 -0
- package/README.md +323 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1496 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +1177 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dongtran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# ๐ `@saptools/cf-inspector`
|
|
4
|
+
|
|
5
|
+
**Set breakpoints, capture variable snapshots, and evaluate expressions on a remote Node.js process โ over the Chrome DevTools Protocol, no IDE required.**
|
|
6
|
+
|
|
7
|
+
Built so an AI agent (or a CI job) can drive a debugger from a single shell command. Pairs with [`@saptools/cf-debugger`](https://www.npmjs.com/package/@saptools/cf-debugger) when the target lives behind a Cloud Foundry SSH tunnel.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@saptools/cf-inspector)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](https://nodejs.org)
|
|
12
|
+
|
|
13
|
+
[Install](#-install) โข [Quick Start](#-quick-start) โข [CLI](#-cli) โข [API](#-programmatic-usage) โข [How it works](#-how-it-works)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## โจ Features
|
|
20
|
+
|
|
21
|
+
- ๐ฏ **One-shot snapshot** โ `cf-inspector snapshot --bp src/handler.ts:42` sets the breakpoint, waits for it to hit, captures the scope, auto-resumes, prints JSON, exits
|
|
22
|
+
- โ
**Conditional breakpoints** โ `--condition 'req.userId === "abc"'` only pauses when the predicate is truthy
|
|
23
|
+
- ๐ญ **Multi-breakpoint** โ repeat `--bp` to race several locations; first hit wins
|
|
24
|
+
- ๐ก **Non-pausing logpoints** โ `cf-inspector log --at file:line --expr 'JSON.stringify({โฆ})'` streams JSON Lines as the line executes, **without ever pausing the inspectee** (safe for production traffic)
|
|
25
|
+
- ๐ง **Agent-friendly** โ JSON-by-default I/O, deterministic shape, sensitive-name redaction (`password`, `token`, `secret`, `cookie`, โฆ) baked in
|
|
26
|
+
- ๐งญ **Path mapping** โ local `src/handler.ts:42` is matched against the remote URL via a `urlRegex`, with optional `--remote-root` literal or regex (same DSL as `cds-debug`)
|
|
27
|
+
- ๐ **Composes with `cf-debugger`** โ pass `--app/--region/--org/--space` and the tunnel is opened automatically; pass `--port` to attach to anything CDP-speaking
|
|
28
|
+
- ๐ชถ **Tiny dependency footprint** โ `commander` + `ws` only, no heavy CDP framework
|
|
29
|
+
- ๐งฉ **Typed API** โ every CLI command has a programmatic equivalent with full TypeScript definitions
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## ๐ฆ Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @saptools/cf-inspector
|
|
37
|
+
# or
|
|
38
|
+
pnpm add @saptools/cf-inspector
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> [!NOTE]
|
|
42
|
+
> Requires **Node.js โฅ 20**.
|
|
43
|
+
> For Cloud Foundry targets, also install [`@saptools/cf-debugger`](https://www.npmjs.com/package/@saptools/cf-debugger) (added automatically as a peer-style runtime dep).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## ๐ Quick Start
|
|
48
|
+
|
|
49
|
+
### Local Node process
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Terminal 1 โ run any Node app with the inspector enabled
|
|
53
|
+
node --inspect=9229 my-app.js
|
|
54
|
+
|
|
55
|
+
# Terminal 2 โ capture a snapshot when handler.ts:42 hits
|
|
56
|
+
cf-inspector snapshot \
|
|
57
|
+
--port 9229 \
|
|
58
|
+
--bp src/handler.ts:42 \
|
|
59
|
+
--capture 'this.user, req.body' \
|
|
60
|
+
--timeout 30
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Cloud Foundry app (auto-tunnel)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
export SAP_EMAIL=...
|
|
67
|
+
export SAP_PASSWORD=...
|
|
68
|
+
|
|
69
|
+
cf-inspector snapshot \
|
|
70
|
+
--region eu10 --org my-org --space dev --app my-srv \
|
|
71
|
+
--bp src/handler.ts:42 \
|
|
72
|
+
--remote-root 'regex:^/(home/vcap/app|srv-.*)$'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The first form connects directly to `localhost:9229`. The second internally calls `@saptools/cf-debugger` to open the SSH tunnel, runs the snapshot through it, and tears the tunnel down on exit.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ๐งฐ CLI
|
|
80
|
+
|
|
81
|
+
### ๐ธ `cf-inspector snapshot`
|
|
82
|
+
|
|
83
|
+
Set one or more breakpoints, wait for any of them to hit, capture the scope, auto-resume, exit.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Simple snapshot
|
|
87
|
+
cf-inspector snapshot \
|
|
88
|
+
--port 9229 \
|
|
89
|
+
--bp src/handler.ts:42 \
|
|
90
|
+
--capture 'this.user, req.body' \
|
|
91
|
+
--timeout 30
|
|
92
|
+
|
|
93
|
+
# Conditional snapshot โ only pauses for the user we care about
|
|
94
|
+
cf-inspector snapshot --port 9229 \
|
|
95
|
+
--bp src/handler.ts:42 \
|
|
96
|
+
--condition 'req.userId === "abc"' \
|
|
97
|
+
--capture 'req.body'
|
|
98
|
+
|
|
99
|
+
# Multi-breakpoint โ first hit wins (useful when you don't know which path is taken)
|
|
100
|
+
cf-inspector snapshot --port 9229 \
|
|
101
|
+
--bp src/auth.ts:120 \
|
|
102
|
+
--bp src/auth.ts:155 \
|
|
103
|
+
--bp src/auth.ts:180 \
|
|
104
|
+
--capture 'req.url, this.user'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
| Flag | Description |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| `--port <number>` | Local port the inspector or tunnel listens on. **Required** unless `--app/--region/--org/--space` are all set |
|
|
110
|
+
| `--bp <file:line>` | **Required.** Source location to break at. Pass multiple times to race several locations โ the first one to hit wins |
|
|
111
|
+
| `--condition <expr>` | Only pause when this JS expression evaluates truthy in the paused frame. Errors in the condition are silently treated as `false` by V8 |
|
|
112
|
+
| `--capture <expr,โฆ>` | Comma-separated expressions to evaluate in the paused frame; results merged into the snapshot |
|
|
113
|
+
| `--timeout <seconds>` | How long to wait for the breakpoint to hit (default: `30`) |
|
|
114
|
+
| `--remote-root <value>` | Optional path-mapping anchor: literal path or `regex:<pattern>` / `/pattern/flags` |
|
|
115
|
+
| `--no-json` | Print a human-readable summary instead of JSON |
|
|
116
|
+
| `--keep-paused` | Skip the auto-resume after capture (useful for diagnostics) |
|
|
117
|
+
|
|
118
|
+
For Cloud Foundry targets, replace `--port` with `--region/--org/--space/--app` (and optionally `--cf-timeout <seconds>` for the tunnel).
|
|
119
|
+
|
|
120
|
+
### ๐ก `cf-inspector log`
|
|
121
|
+
|
|
122
|
+
Set a non-pausing logpoint and stream the evaluated expression each time the line executes. Safe for production traffic โ the inspectee **never pauses**.
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Stream user IDs hitting handler.ts:42 for 30 seconds
|
|
126
|
+
cf-inspector log \
|
|
127
|
+
--port 9229 \
|
|
128
|
+
--at src/handler.ts:42 \
|
|
129
|
+
--expr 'JSON.stringify({ user: req.user, body: req.body })' \
|
|
130
|
+
--duration 30
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Output is **JSON Lines** on stdout (one event per line) plus a summary trailer on stderr:
|
|
134
|
+
|
|
135
|
+
```jsonc
|
|
136
|
+
{"ts":"2026-04-29T...","at":"src/handler.ts:42","value":"{\"user\":\"alice\",\"body\":{}}"}
|
|
137
|
+
{"ts":"2026-04-29T...","at":"src/handler.ts:42","value":"{\"user\":\"bob\",\"body\":{}}"}
|
|
138
|
+
// stderr:
|
|
139
|
+
{"stopped":"duration","emitted":2}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
When the user expression throws, the event is emitted with `error` instead of `value` so the stream never silently gaps:
|
|
143
|
+
|
|
144
|
+
```jsonc
|
|
145
|
+
{"ts":"โฆ","at":"src/handler.ts:42","error":"Cannot read properties of undefined (reading 'user')"}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Flag | Description |
|
|
149
|
+
| --- | --- |
|
|
150
|
+
| `--port <number>` | Local port the inspector or tunnel listens on. **Required** unless `--app/--region/--org/--space` are all set |
|
|
151
|
+
| `--at <file:line>` | **Required.** Source location to log at |
|
|
152
|
+
| `--expr <expression>` | **Required.** JS expression to evaluate at each hit (wrapped in try/catch on the inspectee side) |
|
|
153
|
+
| `--duration <seconds>` | Stop streaming after N seconds (default: run until SIGINT) |
|
|
154
|
+
| `--remote-root <value>` | Optional path-mapping anchor (same DSL as `snapshot`) |
|
|
155
|
+
| `--no-json` | Print human-readable lines instead of JSON Lines |
|
|
156
|
+
|
|
157
|
+
### ๐งฎ `cf-inspector eval`
|
|
158
|
+
|
|
159
|
+
Evaluate one expression and print the result. If a breakpoint is currently paused, it runs in the top frame; otherwise it runs against `Runtime.evaluate` in the global scope.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
cf-inspector eval --port 9229 --expr 'process.uptime()'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### ๐ `cf-inspector list-scripts`
|
|
166
|
+
|
|
167
|
+
Print every script the V8 instance knows about (useful for debugging path-mapping issues).
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
cf-inspector list-scripts --port 9229
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### ๐ `cf-inspector attach`
|
|
174
|
+
|
|
175
|
+
Connect, fetch the runtime version, print it, disconnect. Useful as a smoke-test that the tunnel is healthy.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
cf-inspector attach --port 9229
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## ๐งโ๐ป Programmatic Usage
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
import {
|
|
187
|
+
connectInspector,
|
|
188
|
+
setBreakpoint,
|
|
189
|
+
waitForPause,
|
|
190
|
+
captureSnapshot,
|
|
191
|
+
evaluateOnFrame,
|
|
192
|
+
resume,
|
|
193
|
+
} from "@saptools/cf-inspector";
|
|
194
|
+
|
|
195
|
+
const session = await connectInspector({ port: 9229 });
|
|
196
|
+
const bp = await setBreakpoint(session, {
|
|
197
|
+
file: "src/handler.ts",
|
|
198
|
+
line: 42,
|
|
199
|
+
});
|
|
200
|
+
const pause = await waitForPause(session, { timeoutMs: 30_000 });
|
|
201
|
+
const snapshot = await captureSnapshot(session, pause);
|
|
202
|
+
const customValue = await evaluateOnFrame(session, pause.callFrames[0]!.callFrameId, "this.user");
|
|
203
|
+
await resume(session);
|
|
204
|
+
await session.dispose();
|
|
205
|
+
|
|
206
|
+
console.log({ bp, snapshot, customValue });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
<details>
|
|
210
|
+
<summary><b>๐ Full export list</b></summary>
|
|
211
|
+
|
|
212
|
+
| Export | Description |
|
|
213
|
+
| --- | --- |
|
|
214
|
+
| `connectInspector(options)` | Open a CDP WebSocket session against a port |
|
|
215
|
+
| `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root |
|
|
216
|
+
| `removeBreakpoint(session, id)` | Remove a breakpoint by id |
|
|
217
|
+
| `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires |
|
|
218
|
+
| `captureSnapshot(session, pause)` | Build a structured snapshot of the paused scope |
|
|
219
|
+
| `evaluateOnFrame(session, frameId, expression)` | Evaluate in a paused frame |
|
|
220
|
+
| `evaluateGlobal(session, expression)` | Evaluate against the global Runtime |
|
|
221
|
+
| `listScripts(session)` | Return the scripts the V8 instance knows about |
|
|
222
|
+
| `resume(session)` | Resume execution |
|
|
223
|
+
| `streamLogpoint(session, options)` | Stream a non-pausing logpoint until duration / signal / transport-close |
|
|
224
|
+
| `buildLogpointCondition(sentinel, expression)` | Build the CDP `condition` string for a logpoint (low-level helper) |
|
|
225
|
+
| `parseRemoteRoot(value)` | Parse a literal/regex remote-root setting |
|
|
226
|
+
| `buildBreakpointUrlRegex(input)` | Build a CDP `urlRegex` for a file path |
|
|
227
|
+
| `CfInspectorError` | Rich error class with typed `code` |
|
|
228
|
+
|
|
229
|
+
</details>
|
|
230
|
+
|
|
231
|
+
<details>
|
|
232
|
+
<summary><b>๐งช Error codes</b></summary>
|
|
233
|
+
|
|
234
|
+
| Code | When |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| `INVALID_BREAKPOINT` | `--bp` is not in `file:line` form, or line is not a positive integer |
|
|
237
|
+
| `INVALID_REMOTE_ROOT` | `--remote-root` regex did not compile |
|
|
238
|
+
| `INSPECTOR_DISCOVERY_FAILED` | `/json/list` did not return a usable WebSocket URL |
|
|
239
|
+
| `INSPECTOR_CONNECTION_FAILED` | WebSocket handshake failed |
|
|
240
|
+
| `CDP_REQUEST_FAILED` | A CDP method returned an error result |
|
|
241
|
+
| `BREAKPOINT_NOT_HIT` | The breakpoint did not hit before the timeout elapsed |
|
|
242
|
+
| `EVALUATION_FAILED` | `Runtime.evaluate` / `Debugger.evaluateOnCallFrame` returned a remote exception |
|
|
243
|
+
| `MISSING_TARGET` | Neither `--port` nor a CF target was provided |
|
|
244
|
+
|
|
245
|
+
</details>
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## ๐ญ How it works
|
|
250
|
+
|
|
251
|
+
```
|
|
252
|
+
โโโโโโโโโโโโโโโโโโโโโโโโ 1. GET http://127.0.0.1:<port>/json/list
|
|
253
|
+
โ cf-inspector โ 2. Open ws:// debugger URL
|
|
254
|
+
โ snapshot --bp X:Y โ โโบ3. Debugger.enable + Runtime.enable
|
|
255
|
+
โโโโโโโโโโโโโโโโโโโโโโโโ 4. Debugger.setBreakpointByUrl({ urlRegex, lineNumber: Y - 1 })
|
|
256
|
+
โ 5. Wait for `Debugger.paused`
|
|
257
|
+
โผ 6. Debugger.evaluateOnCallFrame(...) for each --capture expression
|
|
258
|
+
JSON snapshot 7. Runtime.getProperties(scopeChain[i].object.objectId)
|
|
259
|
+
8. Debugger.resume (unless --keep-paused)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Path mapping uses CDP's first-class `urlRegex`:
|
|
263
|
+
|
|
264
|
+
| `--remote-root` | Resulting urlRegex (line `42` of `src/handler.ts`) |
|
|
265
|
+
| --- | --- |
|
|
266
|
+
| _omitted_ | `(?:^|/)src/handler\.(?:ts\|js)$` |
|
|
267
|
+
| `/home/vcap/app` (literal) | `^file:///home/vcap/app/src/handler\.(?:ts\|js)$` |
|
|
268
|
+
| `regex:^/srv-.*$` | `^file:///srv-[^/]+/src/handler\.(?:ts\|js)$` |
|
|
269
|
+
| `/^/srv-.*$/` | same as above |
|
|
270
|
+
|
|
271
|
+
`.ts โ .js` is folded into the regex automatically because Node's V8 inspector normally serves both the source-mapped TypeScript URL and the runtime JavaScript URL โ matching either is correct.
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## โ๏ธ Composing with `cf-debugger`
|
|
276
|
+
|
|
277
|
+
If `--port` is omitted but `--region/--org/--space/--app` are given, the CLI internally calls `startDebugger(...)` from `@saptools/cf-debugger`, attaches over the SSH tunnel, and disposes the tunnel on exit. You get the same one-shot UX whether the target is local or in CF.
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
cf-inspector snapshot \
|
|
281
|
+
--region eu10 --org my-org --space dev --app my-srv \
|
|
282
|
+
--bp src/handler.ts:42 \
|
|
283
|
+
--capture 'req.url, this.user'
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## ๐ ๏ธ Development
|
|
289
|
+
|
|
290
|
+
From the monorepo root:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
pnpm install
|
|
294
|
+
pnpm --filter @saptools/cf-inspector build
|
|
295
|
+
pnpm --filter @saptools/cf-inspector typecheck
|
|
296
|
+
pnpm --filter @saptools/cf-inspector lint
|
|
297
|
+
pnpm --filter @saptools/cf-inspector test:unit
|
|
298
|
+
pnpm --filter @saptools/cf-inspector test:e2e
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The e2e suite is fully self-contained: it spawns a small Node fixture under `--inspect=0`, drives the CLI against it, and asserts the JSON output. No CF / live network required.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## ๐ Related
|
|
306
|
+
|
|
307
|
+
- ๐ [`@saptools/cf-debugger`](https://www.npmjs.com/package/@saptools/cf-debugger) โ opens the SSH inspector tunnel
|
|
308
|
+
- โ๏ธ [`@saptools/cf-sync`](https://www.npmjs.com/package/@saptools/cf-sync) โ snapshot CF topology + DB bindings into JSON
|
|
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!
|
package/dist/cli.d.ts
ADDED