@saptools/cf-inspector 0.3.5 → 0.3.7
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 +12 -6
- package/dist/cli.js +31 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
|
|
|
18
18
|
|
|
19
19
|
## ✨ Features
|
|
20
20
|
|
|
21
|
-
- 🎯 **One-shot snapshot** — `cf-inspector snapshot --bp src/handler.ts:42` sets the breakpoint, waits for it to hit, captures
|
|
21
|
+
- 🎯 **One-shot snapshot** — `cf-inspector snapshot --bp src/handler.ts:42` sets the breakpoint, waits for it to hit, captures requested expressions, auto-resumes, prints JSON, exits
|
|
22
22
|
- ✅ **Conditional breakpoints** — `--condition 'req.userId === "abc"'` only pauses when the predicate is truthy
|
|
23
23
|
- 🎭 **Multi-breakpoint** — repeat `--bp` to race several locations; first hit wins
|
|
24
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)
|
|
@@ -80,7 +80,7 @@ The first form connects directly to `localhost:9229`. The second internally call
|
|
|
80
80
|
|
|
81
81
|
### 📸 `cf-inspector snapshot`
|
|
82
82
|
|
|
83
|
-
Set one or more breakpoints, wait for any of them to hit, capture
|
|
83
|
+
Set one or more breakpoints, wait for any of them to hit, capture frame metadata and requested expressions, auto-resume, exit.
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
86
|
# Simple snapshot
|
|
@@ -112,11 +112,15 @@ cf-inspector snapshot --port 9229 \
|
|
|
112
112
|
| `--capture <expr,…>` | Top-level comma-separated expressions to evaluate in the paused frame; nested commas inside objects, arrays, calls, or strings are preserved. Object results are materialized to JSON strings when serializable, with fallback to CDP descriptions for non-serializable values |
|
|
113
113
|
| `--timeout <seconds>` | How long to wait for the breakpoint to hit (default: `30`) |
|
|
114
114
|
| `--remote-root <value>` | Optional path-mapping anchor: literal path or `regex:<pattern>` / `/pattern/flags` |
|
|
115
|
+
| `--include-scopes` | Include expanded paused-frame scopes under `topFrame.scopes`. Omitted by default to keep targeted captures concise |
|
|
115
116
|
| `--no-json` | Print a human-readable summary instead of JSON |
|
|
116
117
|
| `--keep-paused` | Skip `Debugger.resume` after capture; Node may resume when the CLI disconnects |
|
|
117
118
|
| `--fail-on-unmatched-pause` | Fail immediately if the target pauses somewhere else instead of waiting cooperatively |
|
|
118
119
|
|
|
119
|
-
JSON output includes
|
|
120
|
+
JSON output includes frame metadata and `captures` by default. `topFrame.scopes`
|
|
121
|
+
is only present with `--include-scopes`, because Cloud Foundry Node apps often
|
|
122
|
+
carry large local/closure/module objects that drown out targeted captures. The
|
|
123
|
+
output also includes `pausedDurationMs`, the client-observed time from receiving
|
|
120
124
|
the matching pause event until `Debugger.resume` completes. It does not include
|
|
121
125
|
the time spent waiting for the breakpoint to hit. When `--keep-paused` is used,
|
|
122
126
|
`pausedDurationMs` is `null` because `cf-inspector` intentionally skips
|
|
@@ -213,7 +217,9 @@ const bp = await setBreakpoint(session, {
|
|
|
213
217
|
line: 42,
|
|
214
218
|
});
|
|
215
219
|
const pause = await waitForPause(session, { timeoutMs: 30_000 });
|
|
216
|
-
const snapshot = await captureSnapshot(session, pause
|
|
220
|
+
const snapshot = await captureSnapshot(session, pause, {
|
|
221
|
+
captures: ["this.user"],
|
|
222
|
+
});
|
|
217
223
|
const topFrame = pause.callFrames[0];
|
|
218
224
|
if (topFrame === undefined) {
|
|
219
225
|
throw new Error("Breakpoint paused without a call frame");
|
|
@@ -234,7 +240,7 @@ console.log({ bp, snapshot, customValue });
|
|
|
234
240
|
| `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root |
|
|
235
241
|
| `removeBreakpoint(session, id)` | Remove a breakpoint by id |
|
|
236
242
|
| `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires |
|
|
237
|
-
| `captureSnapshot(session, pause)` | Build a structured snapshot of the paused
|
|
243
|
+
| `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes |
|
|
238
244
|
| `evaluateOnFrame(session, frameId, expression)` | Evaluate in a paused frame |
|
|
239
245
|
| `evaluateGlobal(session, expression)` | Evaluate against the global Runtime |
|
|
240
246
|
| `listScripts(session)` | Return the scripts the V8 instance knows about |
|
|
@@ -280,7 +286,7 @@ console.log({ bp, snapshot, customValue });
|
|
|
280
286
|
└──────────────────────┘ 4. Debugger.setBreakpointByUrl({ urlRegex, lineNumber: Y - 1 })
|
|
281
287
|
│ 5. Wait for `Debugger.paused`
|
|
282
288
|
▼ 6. Debugger.evaluateOnCallFrame(...) for each --capture expression
|
|
283
|
-
JSON snapshot 7. Runtime.getProperties(scopeChain[i].object.objectId)
|
|
289
|
+
JSON snapshot 7. Runtime.getProperties(scopeChain[i].object.objectId) when --include-scopes is set
|
|
284
290
|
8. Debugger.resume (unless --keep-paused)
|
|
285
291
|
```
|
|
286
292
|
|
package/dist/cli.js
CHANGED
|
@@ -1321,6 +1321,15 @@ async function renderObjectCapture(session, objectId) {
|
|
|
1321
1321
|
return void 0;
|
|
1322
1322
|
}
|
|
1323
1323
|
}
|
|
1324
|
+
function normalizeRenderedObjectCapture(rendered, original) {
|
|
1325
|
+
if (rendered === "{}" && original !== "Object") {
|
|
1326
|
+
return void 0;
|
|
1327
|
+
}
|
|
1328
|
+
if (original.startsWith("Array(") && rendered === '{"length":0}') {
|
|
1329
|
+
return "[]";
|
|
1330
|
+
}
|
|
1331
|
+
return rendered;
|
|
1332
|
+
}
|
|
1324
1333
|
async function withSerializedObjectCapture(session, expression, evalResult, captured) {
|
|
1325
1334
|
if (captured.error !== void 0 || captured.value === void 0) {
|
|
1326
1335
|
return captured;
|
|
@@ -1333,7 +1342,11 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
|
|
|
1333
1342
|
if (rendered === void 0) {
|
|
1334
1343
|
return captured;
|
|
1335
1344
|
}
|
|
1336
|
-
const
|
|
1345
|
+
const normalized = normalizeRenderedObjectCapture(rendered, captured.value);
|
|
1346
|
+
if (normalized === void 0) {
|
|
1347
|
+
return captured;
|
|
1348
|
+
}
|
|
1349
|
+
const value = sanitizeValue(expression, normalized);
|
|
1337
1350
|
return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
|
|
1338
1351
|
}
|
|
1339
1352
|
async function captureSnapshot(session, pause, options = {}) {
|
|
@@ -1341,14 +1354,16 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1341
1354
|
let topFrame;
|
|
1342
1355
|
let captures = [];
|
|
1343
1356
|
if (top) {
|
|
1344
|
-
const scopes = await captureScopes(session, top);
|
|
1345
1357
|
topFrame = {
|
|
1346
1358
|
functionName: top.functionName,
|
|
1347
1359
|
...top.url === void 0 ? {} : { url: top.url },
|
|
1348
1360
|
line: top.lineNumber + 1,
|
|
1349
|
-
column: top.columnNumber + 1
|
|
1350
|
-
scopes
|
|
1361
|
+
column: top.columnNumber + 1
|
|
1351
1362
|
};
|
|
1363
|
+
if (options.includeScopes === true) {
|
|
1364
|
+
const scopes = await captureScopes(session, top);
|
|
1365
|
+
topFrame = { ...topFrame, scopes };
|
|
1366
|
+
}
|
|
1352
1367
|
if (options.captures !== void 0 && options.captures.length > 0) {
|
|
1353
1368
|
captures = await Promise.all(
|
|
1354
1369
|
options.captures.map(async (expression) => {
|
|
@@ -1605,10 +1620,12 @@ function writeHumanSnapshot(snapshot) {
|
|
|
1605
1620
|
lines.push(
|
|
1606
1621
|
` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
|
|
1607
1622
|
);
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1623
|
+
if (frame.scopes !== void 0) {
|
|
1624
|
+
for (const scope of frame.scopes) {
|
|
1625
|
+
lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
|
|
1626
|
+
for (const variable of scope.variables) {
|
|
1627
|
+
lines.push(` ${variable.name} = ${variable.value}`);
|
|
1628
|
+
}
|
|
1612
1629
|
}
|
|
1613
1630
|
}
|
|
1614
1631
|
}
|
|
@@ -1666,7 +1683,10 @@ async function handleSnapshot(opts) {
|
|
|
1666
1683
|
}
|
|
1667
1684
|
});
|
|
1668
1685
|
const pausedStartedAt = pause.receivedAtMs ?? performance2.now();
|
|
1669
|
-
const snapshot = await captureSnapshot(session, pause, {
|
|
1686
|
+
const snapshot = await captureSnapshot(session, pause, {
|
|
1687
|
+
captures,
|
|
1688
|
+
includeScopes: opts.includeScopes === true
|
|
1689
|
+
});
|
|
1670
1690
|
if (opts.keepPaused === true) {
|
|
1671
1691
|
return withPausedDuration(snapshot, null);
|
|
1672
1692
|
}
|
|
@@ -1830,7 +1850,7 @@ async function main(argv) {
|
|
|
1830
1850
|
value
|
|
1831
1851
|
];
|
|
1832
1852
|
applyTargetOptions(
|
|
1833
|
-
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture
|
|
1853
|
+
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
|
|
1834
1854
|
).option(
|
|
1835
1855
|
"--bp <file:line>",
|
|
1836
1856
|
"Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42",
|
|
@@ -1839,7 +1859,7 @@ async function main(argv) {
|
|
|
1839
1859
|
).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--timeout <seconds>", "How long to wait for the breakpoint to hit (default: 30)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option(
|
|
1840
1860
|
"--condition <expr>",
|
|
1841
1861
|
"Only pause when this JS expression evaluates truthy in the paused frame"
|
|
1842
|
-
).option("--no-json", "Print a human-readable summary instead of JSON").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--fail-on-unmatched-pause", "Fail immediately if the target pauses somewhere else").action(async (opts) => {
|
|
1862
|
+
).option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--no-json", "Print a human-readable summary instead of JSON").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--fail-on-unmatched-pause", "Fail immediately if the target pauses somewhere else").action(async (opts) => {
|
|
1843
1863
|
await handleSnapshot(opts);
|
|
1844
1864
|
});
|
|
1845
1865
|
applyTargetOptions(
|