@saptools/cf-inspector 0.3.6 → 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 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 the scope, auto-resumes, prints JSON, exits
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 the scope, auto-resume, exit.
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 `pausedDurationMs`, the client-observed time from receiving
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 scope |
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
@@ -1354,14 +1354,16 @@ async function captureSnapshot(session, pause, options = {}) {
1354
1354
  let topFrame;
1355
1355
  let captures = [];
1356
1356
  if (top) {
1357
- const scopes = await captureScopes(session, top);
1358
1357
  topFrame = {
1359
1358
  functionName: top.functionName,
1360
1359
  ...top.url === void 0 ? {} : { url: top.url },
1361
1360
  line: top.lineNumber + 1,
1362
- column: top.columnNumber + 1,
1363
- scopes
1361
+ column: top.columnNumber + 1
1364
1362
  };
1363
+ if (options.includeScopes === true) {
1364
+ const scopes = await captureScopes(session, top);
1365
+ topFrame = { ...topFrame, scopes };
1366
+ }
1365
1367
  if (options.captures !== void 0 && options.captures.length > 0) {
1366
1368
  captures = await Promise.all(
1367
1369
  options.captures.map(async (expression) => {
@@ -1618,10 +1620,12 @@ function writeHumanSnapshot(snapshot) {
1618
1620
  lines.push(
1619
1621
  ` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
1620
1622
  );
1621
- for (const scope of frame.scopes) {
1622
- lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
1623
- for (const variable of scope.variables) {
1624
- lines.push(` ${variable.name} = ${variable.value}`);
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
+ }
1625
1629
  }
1626
1630
  }
1627
1631
  }
@@ -1679,7 +1683,10 @@ async function handleSnapshot(opts) {
1679
1683
  }
1680
1684
  });
1681
1685
  const pausedStartedAt = pause.receivedAtMs ?? performance2.now();
1682
- const snapshot = await captureSnapshot(session, pause, { captures });
1686
+ const snapshot = await captureSnapshot(session, pause, {
1687
+ captures,
1688
+ includeScopes: opts.includeScopes === true
1689
+ });
1683
1690
  if (opts.keepPaused === true) {
1684
1691
  return withPausedDuration(snapshot, null);
1685
1692
  }
@@ -1843,7 +1850,7 @@ async function main(argv) {
1843
1850
  value
1844
1851
  ];
1845
1852
  applyTargetOptions(
1846
- program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture the scope, and resume")
1853
+ program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
1847
1854
  ).option(
1848
1855
  "--bp <file:line>",
1849
1856
  "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42",
@@ -1852,7 +1859,7 @@ async function main(argv) {
1852
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(
1853
1860
  "--condition <expr>",
1854
1861
  "Only pause when this JS expression evaluates truthy in the paused frame"
1855
- ).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) => {
1856
1863
  await handleSnapshot(opts);
1857
1864
  });
1858
1865
  applyTargetOptions(