@saptools/cf-inspector 0.3.6 â 0.3.8
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 +15 -7
- package/dist/cli.js +24 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +12 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,11 +18,11 @@ 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)
|
|
25
|
-
- ð§ **Agent-friendly** â JSON-by-default I/O, deterministic shape,
|
|
25
|
+
- ð§ **Agent-friendly** â JSON-by-default I/O, deterministic shape, bounded value previews for large debugger payloads
|
|
26
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
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
28
|
- ðŠķ **Tiny dependency footprint** â `commander` + `ws` only, no heavy CDP framework
|
|
@@ -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,17 @@ 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 contains raw debugger values; use it only against trusted targets and be
|
|
124
|
+
careful when sharing logs. The output also includes `pausedDurationMs`, the
|
|
125
|
+
client-observed time from receiving
|
|
120
126
|
the matching pause event until `Debugger.resume` completes. It does not include
|
|
121
127
|
the time spent waiting for the breakpoint to hit. When `--keep-paused` is used,
|
|
122
128
|
`pausedDurationMs` is `null` because `cf-inspector` intentionally skips
|
|
@@ -213,7 +219,9 @@ const bp = await setBreakpoint(session, {
|
|
|
213
219
|
line: 42,
|
|
214
220
|
});
|
|
215
221
|
const pause = await waitForPause(session, { timeoutMs: 30_000 });
|
|
216
|
-
const snapshot = await captureSnapshot(session, pause
|
|
222
|
+
const snapshot = await captureSnapshot(session, pause, {
|
|
223
|
+
captures: ["this.user"],
|
|
224
|
+
});
|
|
217
225
|
const topFrame = pause.callFrames[0];
|
|
218
226
|
if (topFrame === undefined) {
|
|
219
227
|
throw new Error("Breakpoint paused without a call frame");
|
|
@@ -234,7 +242,7 @@ console.log({ bp, snapshot, customValue });
|
|
|
234
242
|
| `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root |
|
|
235
243
|
| `removeBreakpoint(session, id)` | Remove a breakpoint by id |
|
|
236
244
|
| `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires |
|
|
237
|
-
| `captureSnapshot(session, pause)` | Build a structured snapshot of the paused
|
|
245
|
+
| `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes |
|
|
238
246
|
| `evaluateOnFrame(session, frameId, expression)` | Evaluate in a paused frame |
|
|
239
247
|
| `evaluateGlobal(session, expression)` | Evaluate against the global Runtime |
|
|
240
248
|
| `listScripts(session)` | Return the scripts the V8 instance knows about |
|
|
@@ -280,7 +288,7 @@ console.log({ bp, snapshot, customValue });
|
|
|
280
288
|
ââââââââââââââââââââââââ 4. Debugger.setBreakpointByUrl({ urlRegex, lineNumber: Y - 1 })
|
|
281
289
|
â 5. Wait for `Debugger.paused`
|
|
282
290
|
âž 6. Debugger.evaluateOnCallFrame(...) for each --capture expression
|
|
283
|
-
JSON snapshot 7. Runtime.getProperties(scopeChain[i].object.objectId)
|
|
291
|
+
JSON snapshot 7. Runtime.getProperties(scopeChain[i].object.objectId) when --include-scopes is set
|
|
284
292
|
8. Debugger.resume (unless --keep-paused)
|
|
285
293
|
```
|
|
286
294
|
|
package/dist/cli.js
CHANGED
|
@@ -1072,7 +1072,6 @@ var MAX_SCOPE_VARIABLES = 20;
|
|
|
1072
1072
|
var MAX_CHILD_VARIABLES = 8;
|
|
1073
1073
|
var MAX_VARIABLE_DEPTH = 2;
|
|
1074
1074
|
var MAX_VALUE_LENGTH = 240;
|
|
1075
|
-
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|credentials?|creds?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
1076
1075
|
var PRIORITY_BY_TYPE = {
|
|
1077
1076
|
local: 0,
|
|
1078
1077
|
arguments: 1,
|
|
@@ -1133,13 +1132,7 @@ function formatPrimitive(value) {
|
|
|
1133
1132
|
}
|
|
1134
1133
|
return String(value);
|
|
1135
1134
|
}
|
|
1136
|
-
function
|
|
1137
|
-
return SENSITIVE_NAME_REGEX.test(name);
|
|
1138
|
-
}
|
|
1139
|
-
function sanitizeValue(name, raw) {
|
|
1140
|
-
if (isSensitiveName(name)) {
|
|
1141
|
-
return "[REDACTED]";
|
|
1142
|
-
}
|
|
1135
|
+
function limitValueLength(raw) {
|
|
1143
1136
|
if (raw.length <= MAX_VALUE_LENGTH) {
|
|
1144
1137
|
return raw;
|
|
1145
1138
|
}
|
|
@@ -1155,9 +1148,8 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
1155
1148
|
limited.map(async (prop) => {
|
|
1156
1149
|
const name = typeof prop.name === "string" ? prop.name : "?";
|
|
1157
1150
|
const described = describeProperty(prop);
|
|
1158
|
-
const sensitive = isSensitiveName(name);
|
|
1159
1151
|
let children;
|
|
1160
|
-
if (
|
|
1152
|
+
if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
1161
1153
|
try {
|
|
1162
1154
|
const nested = await captureProperties(
|
|
1163
1155
|
session,
|
|
@@ -1171,7 +1163,7 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
1171
1163
|
} catch {
|
|
1172
1164
|
}
|
|
1173
1165
|
}
|
|
1174
|
-
const sanitizedValue =
|
|
1166
|
+
const sanitizedValue = limitValueLength(described.value);
|
|
1175
1167
|
const base = { name, value: sanitizedValue };
|
|
1176
1168
|
const withType = described.type === void 0 ? base : { ...base, type: described.type };
|
|
1177
1169
|
return children === void 0 ? withType : { ...withType, children };
|
|
@@ -1206,7 +1198,7 @@ async function captureScopes(session, frame) {
|
|
|
1206
1198
|
function evalResultToCaptured(expression, result) {
|
|
1207
1199
|
if (result.exceptionDetails !== void 0) {
|
|
1208
1200
|
const text = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
1209
|
-
return { expression, error:
|
|
1201
|
+
return { expression, error: limitValueLength(text) };
|
|
1210
1202
|
}
|
|
1211
1203
|
const inner = result.result;
|
|
1212
1204
|
if (!inner) {
|
|
@@ -1214,7 +1206,7 @@ function evalResultToCaptured(expression, result) {
|
|
|
1214
1206
|
}
|
|
1215
1207
|
const type = typeof inner.type === "string" ? inner.type : void 0;
|
|
1216
1208
|
const buildCaptured = (rendered) => {
|
|
1217
|
-
const sanitized =
|
|
1209
|
+
const sanitized = limitValueLength(rendered);
|
|
1218
1210
|
const base = { expression, value: sanitized };
|
|
1219
1211
|
return type === void 0 ? base : { ...base, type };
|
|
1220
1212
|
};
|
|
@@ -1346,7 +1338,7 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
|
|
|
1346
1338
|
if (normalized === void 0) {
|
|
1347
1339
|
return captured;
|
|
1348
1340
|
}
|
|
1349
|
-
const value =
|
|
1341
|
+
const value = limitValueLength(normalized);
|
|
1350
1342
|
return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
|
|
1351
1343
|
}
|
|
1352
1344
|
async function captureSnapshot(session, pause, options = {}) {
|
|
@@ -1354,14 +1346,16 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1354
1346
|
let topFrame;
|
|
1355
1347
|
let captures = [];
|
|
1356
1348
|
if (top) {
|
|
1357
|
-
const scopes = await captureScopes(session, top);
|
|
1358
1349
|
topFrame = {
|
|
1359
1350
|
functionName: top.functionName,
|
|
1360
1351
|
...top.url === void 0 ? {} : { url: top.url },
|
|
1361
1352
|
line: top.lineNumber + 1,
|
|
1362
|
-
column: top.columnNumber + 1
|
|
1363
|
-
scopes
|
|
1353
|
+
column: top.columnNumber + 1
|
|
1364
1354
|
};
|
|
1355
|
+
if (options.includeScopes === true) {
|
|
1356
|
+
const scopes = await captureScopes(session, top);
|
|
1357
|
+
topFrame = { ...topFrame, scopes };
|
|
1358
|
+
}
|
|
1365
1359
|
if (options.captures !== void 0 && options.captures.length > 0) {
|
|
1366
1360
|
captures = await Promise.all(
|
|
1367
1361
|
options.captures.map(async (expression) => {
|
|
@@ -1371,7 +1365,7 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1371
1365
|
return await withSerializedObjectCapture(session, expression, result, captured);
|
|
1372
1366
|
} catch (err) {
|
|
1373
1367
|
const message = err instanceof Error ? err.message : String(err);
|
|
1374
|
-
return { expression, error: message };
|
|
1368
|
+
return { expression, error: limitValueLength(message) };
|
|
1375
1369
|
}
|
|
1376
1370
|
})
|
|
1377
1371
|
);
|
|
@@ -1618,10 +1612,12 @@ function writeHumanSnapshot(snapshot) {
|
|
|
1618
1612
|
lines.push(
|
|
1619
1613
|
` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
|
|
1620
1614
|
);
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1615
|
+
if (frame.scopes !== void 0) {
|
|
1616
|
+
for (const scope of frame.scopes) {
|
|
1617
|
+
lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
|
|
1618
|
+
for (const variable of scope.variables) {
|
|
1619
|
+
lines.push(` ${variable.name} = ${variable.value}`);
|
|
1620
|
+
}
|
|
1625
1621
|
}
|
|
1626
1622
|
}
|
|
1627
1623
|
}
|
|
@@ -1679,7 +1675,10 @@ async function handleSnapshot(opts) {
|
|
|
1679
1675
|
}
|
|
1680
1676
|
});
|
|
1681
1677
|
const pausedStartedAt = pause.receivedAtMs ?? performance2.now();
|
|
1682
|
-
const snapshot = await captureSnapshot(session, pause, {
|
|
1678
|
+
const snapshot = await captureSnapshot(session, pause, {
|
|
1679
|
+
captures,
|
|
1680
|
+
includeScopes: opts.includeScopes === true
|
|
1681
|
+
});
|
|
1683
1682
|
if (opts.keepPaused === true) {
|
|
1684
1683
|
return withPausedDuration(snapshot, null);
|
|
1685
1684
|
}
|
|
@@ -1843,7 +1842,7 @@ async function main(argv) {
|
|
|
1843
1842
|
value
|
|
1844
1843
|
];
|
|
1845
1844
|
applyTargetOptions(
|
|
1846
|
-
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture
|
|
1845
|
+
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
|
|
1847
1846
|
).option(
|
|
1848
1847
|
"--bp <file:line>",
|
|
1849
1848
|
"Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42",
|
|
@@ -1852,7 +1851,7 @@ async function main(argv) {
|
|
|
1852
1851
|
).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
1852
|
"--condition <expr>",
|
|
1854
1853
|
"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) => {
|
|
1854
|
+
).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
1855
|
await handleSnapshot(opts);
|
|
1857
1856
|
});
|
|
1858
1857
|
applyTargetOptions(
|