@saptools/cf-inspector 0.3.7 → 0.3.9
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 +7 -3
- package/dist/cli.js +58 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +54 -29
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
|
|
|
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
|
|
@@ -111,6 +111,7 @@ cf-inspector snapshot --port 9229 \
|
|
|
111
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
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
|
+
| `--max-value-length <chars>` | Maximum characters per captured value before truncation (default: `4096`) |
|
|
114
115
|
| `--remote-root <value>` | Optional path-mapping anchor: literal path or `regex:<pattern>` / `/pattern/flags` |
|
|
115
116
|
| `--include-scopes` | Include expanded paused-frame scopes under `topFrame.scopes`. Omitted by default to keep targeted captures concise |
|
|
116
117
|
| `--no-json` | Print a human-readable summary instead of JSON |
|
|
@@ -120,7 +121,9 @@ cf-inspector snapshot --port 9229 \
|
|
|
120
121
|
JSON output includes frame metadata and `captures` by default. `topFrame.scopes`
|
|
121
122
|
is only present with `--include-scopes`, because Cloud Foundry Node apps often
|
|
122
123
|
carry large local/closure/module objects that drown out targeted captures. The
|
|
123
|
-
output
|
|
124
|
+
output contains raw debugger values; use it only against trusted targets and be
|
|
125
|
+
careful when sharing logs. The output also includes `pausedDurationMs`, the
|
|
126
|
+
client-observed time from receiving
|
|
124
127
|
the matching pause event until `Debugger.resume` completes. It does not include
|
|
125
128
|
the time spent waiting for the breakpoint to hit. When `--keep-paused` is used,
|
|
126
129
|
`pausedDurationMs` is `null` because `cf-inspector` intentionally skips
|
|
@@ -219,6 +222,7 @@ const bp = await setBreakpoint(session, {
|
|
|
219
222
|
const pause = await waitForPause(session, { timeoutMs: 30_000 });
|
|
220
223
|
const snapshot = await captureSnapshot(session, pause, {
|
|
221
224
|
captures: ["this.user"],
|
|
225
|
+
maxValueLength: 4096,
|
|
222
226
|
});
|
|
223
227
|
const topFrame = pause.callFrames[0];
|
|
224
228
|
if (topFrame === undefined) {
|
|
@@ -240,7 +244,7 @@ console.log({ bp, snapshot, customValue });
|
|
|
240
244
|
| `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root |
|
|
241
245
|
| `removeBreakpoint(session, id)` | Remove a breakpoint by id |
|
|
242
246
|
| `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires |
|
|
243
|
-
| `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes |
|
|
247
|
+
| `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes or `maxValueLength` to override the default captured value limit |
|
|
244
248
|
| `evaluateOnFrame(session, frameId, expression)` | Evaluate in a paused frame |
|
|
245
249
|
| `evaluateGlobal(session, expression)` | Evaluate against the global Runtime |
|
|
246
250
|
| `listScripts(session)` | Return the scripts the V8 instance knows about |
|
package/dist/cli.js
CHANGED
|
@@ -1067,12 +1067,12 @@ async function waitForStop(session, options) {
|
|
|
1067
1067
|
}
|
|
1068
1068
|
|
|
1069
1069
|
// src/snapshot.ts
|
|
1070
|
+
init_types();
|
|
1070
1071
|
var MAX_SCOPES = 3;
|
|
1071
1072
|
var MAX_SCOPE_VARIABLES = 20;
|
|
1072
1073
|
var MAX_CHILD_VARIABLES = 8;
|
|
1073
1074
|
var MAX_VARIABLE_DEPTH = 2;
|
|
1074
|
-
var
|
|
1075
|
-
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|credentials?|creds?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
1075
|
+
var DEFAULT_MAX_VALUE_LENGTH = 4096;
|
|
1076
1076
|
var PRIORITY_BY_TYPE = {
|
|
1077
1077
|
local: 0,
|
|
1078
1078
|
arguments: 1,
|
|
@@ -1133,37 +1133,43 @@ function formatPrimitive(value) {
|
|
|
1133
1133
|
}
|
|
1134
1134
|
return String(value);
|
|
1135
1135
|
}
|
|
1136
|
-
function
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
if (
|
|
1141
|
-
|
|
1136
|
+
function resolveMaxValueLength(value) {
|
|
1137
|
+
if (value === void 0) {
|
|
1138
|
+
return DEFAULT_MAX_VALUE_LENGTH;
|
|
1139
|
+
}
|
|
1140
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
1141
|
+
throw new CfInspectorError(
|
|
1142
|
+
"INVALID_ARGUMENT",
|
|
1143
|
+
`Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
|
|
1144
|
+
);
|
|
1142
1145
|
}
|
|
1143
|
-
|
|
1146
|
+
return value;
|
|
1147
|
+
}
|
|
1148
|
+
function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
|
|
1149
|
+
if (raw.length <= maxValueLength) {
|
|
1144
1150
|
return raw;
|
|
1145
1151
|
}
|
|
1146
|
-
return `${raw.slice(0,
|
|
1152
|
+
return `${raw.slice(0, maxValueLength)}...`;
|
|
1147
1153
|
}
|
|
1148
1154
|
function isExpandable(type) {
|
|
1149
1155
|
return type === "object" || type === "function";
|
|
1150
1156
|
}
|
|
1151
|
-
async function captureProperties(session, objectId, limit, depth) {
|
|
1157
|
+
async function captureProperties(session, objectId, limit, depth, maxValueLength) {
|
|
1152
1158
|
const properties = await getProperties(session, objectId);
|
|
1153
1159
|
const limited = properties.slice(0, limit);
|
|
1154
1160
|
const variables = await Promise.all(
|
|
1155
1161
|
limited.map(async (prop) => {
|
|
1156
1162
|
const name = typeof prop.name === "string" ? prop.name : "?";
|
|
1157
1163
|
const described = describeProperty(prop);
|
|
1158
|
-
const sensitive = isSensitiveName(name);
|
|
1159
1164
|
let children;
|
|
1160
|
-
if (
|
|
1165
|
+
if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
1161
1166
|
try {
|
|
1162
1167
|
const nested = await captureProperties(
|
|
1163
1168
|
session,
|
|
1164
1169
|
described.objectId,
|
|
1165
1170
|
MAX_CHILD_VARIABLES,
|
|
1166
|
-
depth - 1
|
|
1171
|
+
depth - 1,
|
|
1172
|
+
maxValueLength
|
|
1167
1173
|
);
|
|
1168
1174
|
if (nested.length > 0) {
|
|
1169
1175
|
children = nested;
|
|
@@ -1171,7 +1177,7 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
1171
1177
|
} catch {
|
|
1172
1178
|
}
|
|
1173
1179
|
}
|
|
1174
|
-
const sanitizedValue =
|
|
1180
|
+
const sanitizedValue = limitValueLength(described.value, maxValueLength);
|
|
1175
1181
|
const base = { name, value: sanitizedValue };
|
|
1176
1182
|
const withType = described.type === void 0 ? base : { ...base, type: described.type };
|
|
1177
1183
|
return children === void 0 ? withType : { ...withType, children };
|
|
@@ -1186,7 +1192,7 @@ function selectScopes(scopeChain) {
|
|
|
1186
1192
|
function priorityOf(type) {
|
|
1187
1193
|
return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
|
|
1188
1194
|
}
|
|
1189
|
-
async function captureScopes(session, frame) {
|
|
1195
|
+
async function captureScopes(session, frame, maxValueLength) {
|
|
1190
1196
|
const scopes = selectScopes(frame.scopeChain);
|
|
1191
1197
|
return await Promise.all(
|
|
1192
1198
|
scopes.map(async (scope) => {
|
|
@@ -1195,7 +1201,13 @@ async function captureScopes(session, frame) {
|
|
|
1195
1201
|
return { type: scope.type, variables: [] };
|
|
1196
1202
|
}
|
|
1197
1203
|
try {
|
|
1198
|
-
const variables = await captureProperties(
|
|
1204
|
+
const variables = await captureProperties(
|
|
1205
|
+
session,
|
|
1206
|
+
objectId,
|
|
1207
|
+
MAX_SCOPE_VARIABLES,
|
|
1208
|
+
MAX_VARIABLE_DEPTH,
|
|
1209
|
+
maxValueLength
|
|
1210
|
+
);
|
|
1199
1211
|
return { type: scope.type, variables };
|
|
1200
1212
|
} catch {
|
|
1201
1213
|
return { type: scope.type, variables: [] };
|
|
@@ -1203,10 +1215,10 @@ async function captureScopes(session, frame) {
|
|
|
1203
1215
|
})
|
|
1204
1216
|
);
|
|
1205
1217
|
}
|
|
1206
|
-
function evalResultToCaptured(expression, result) {
|
|
1218
|
+
function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
|
|
1207
1219
|
if (result.exceptionDetails !== void 0) {
|
|
1208
1220
|
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:
|
|
1221
|
+
return { expression, error: limitValueLength(text, maxValueLength) };
|
|
1210
1222
|
}
|
|
1211
1223
|
const inner = result.result;
|
|
1212
1224
|
if (!inner) {
|
|
@@ -1214,7 +1226,7 @@ function evalResultToCaptured(expression, result) {
|
|
|
1214
1226
|
}
|
|
1215
1227
|
const type = typeof inner.type === "string" ? inner.type : void 0;
|
|
1216
1228
|
const buildCaptured = (rendered) => {
|
|
1217
|
-
const sanitized =
|
|
1229
|
+
const sanitized = limitValueLength(rendered, maxValueLength);
|
|
1218
1230
|
const base = { expression, value: sanitized };
|
|
1219
1231
|
return type === void 0 ? base : { ...base, type };
|
|
1220
1232
|
};
|
|
@@ -1309,9 +1321,15 @@ function toStructuredValue(variable) {
|
|
|
1309
1321
|
}
|
|
1310
1322
|
return out;
|
|
1311
1323
|
}
|
|
1312
|
-
async function renderObjectCapture(session, objectId) {
|
|
1324
|
+
async function renderObjectCapture(session, objectId, maxValueLength) {
|
|
1313
1325
|
try {
|
|
1314
|
-
const properties = await captureProperties(
|
|
1326
|
+
const properties = await captureProperties(
|
|
1327
|
+
session,
|
|
1328
|
+
objectId,
|
|
1329
|
+
MAX_SCOPE_VARIABLES,
|
|
1330
|
+
MAX_VARIABLE_DEPTH,
|
|
1331
|
+
maxValueLength
|
|
1332
|
+
);
|
|
1315
1333
|
const structured = {};
|
|
1316
1334
|
for (const variable of properties) {
|
|
1317
1335
|
structured[variable.name] = toStructuredValue(variable);
|
|
@@ -1330,7 +1348,7 @@ function normalizeRenderedObjectCapture(rendered, original) {
|
|
|
1330
1348
|
}
|
|
1331
1349
|
return rendered;
|
|
1332
1350
|
}
|
|
1333
|
-
async function withSerializedObjectCapture(session, expression, evalResult, captured) {
|
|
1351
|
+
async function withSerializedObjectCapture(session, expression, evalResult, captured, maxValueLength) {
|
|
1334
1352
|
if (captured.error !== void 0 || captured.value === void 0) {
|
|
1335
1353
|
return captured;
|
|
1336
1354
|
}
|
|
@@ -1338,7 +1356,7 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
|
|
|
1338
1356
|
if (objectId === void 0) {
|
|
1339
1357
|
return captured;
|
|
1340
1358
|
}
|
|
1341
|
-
const rendered = await renderObjectCapture(session, objectId);
|
|
1359
|
+
const rendered = await renderObjectCapture(session, objectId, maxValueLength);
|
|
1342
1360
|
if (rendered === void 0) {
|
|
1343
1361
|
return captured;
|
|
1344
1362
|
}
|
|
@@ -1346,10 +1364,11 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
|
|
|
1346
1364
|
if (normalized === void 0) {
|
|
1347
1365
|
return captured;
|
|
1348
1366
|
}
|
|
1349
|
-
const value =
|
|
1367
|
+
const value = limitValueLength(normalized, maxValueLength);
|
|
1350
1368
|
return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
|
|
1351
1369
|
}
|
|
1352
1370
|
async function captureSnapshot(session, pause, options = {}) {
|
|
1371
|
+
const maxValueLength = resolveMaxValueLength(options.maxValueLength);
|
|
1353
1372
|
const top = pause.callFrames[0];
|
|
1354
1373
|
let topFrame;
|
|
1355
1374
|
let captures = [];
|
|
@@ -1361,7 +1380,7 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1361
1380
|
column: top.columnNumber + 1
|
|
1362
1381
|
};
|
|
1363
1382
|
if (options.includeScopes === true) {
|
|
1364
|
-
const scopes = await captureScopes(session, top);
|
|
1383
|
+
const scopes = await captureScopes(session, top, maxValueLength);
|
|
1365
1384
|
topFrame = { ...topFrame, scopes };
|
|
1366
1385
|
}
|
|
1367
1386
|
if (options.captures !== void 0 && options.captures.length > 0) {
|
|
@@ -1369,11 +1388,17 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1369
1388
|
options.captures.map(async (expression) => {
|
|
1370
1389
|
try {
|
|
1371
1390
|
const result = await evaluateOnFrame(session, top.callFrameId, expression);
|
|
1372
|
-
const captured = evalResultToCaptured(expression, result);
|
|
1373
|
-
return await withSerializedObjectCapture(
|
|
1391
|
+
const captured = evalResultToCaptured(expression, result, maxValueLength);
|
|
1392
|
+
return await withSerializedObjectCapture(
|
|
1393
|
+
session,
|
|
1394
|
+
expression,
|
|
1395
|
+
result,
|
|
1396
|
+
captured,
|
|
1397
|
+
maxValueLength
|
|
1398
|
+
);
|
|
1374
1399
|
} catch (err) {
|
|
1375
1400
|
const message = err instanceof Error ? err.message : String(err);
|
|
1376
|
-
return { expression, error: message };
|
|
1401
|
+
return { expression, error: limitValueLength(message, maxValueLength) };
|
|
1377
1402
|
}
|
|
1378
1403
|
})
|
|
1379
1404
|
);
|
|
@@ -1651,6 +1676,7 @@ async function handleSnapshot(opts) {
|
|
|
1651
1676
|
const remoteRoot = parseRemoteRoot(opts.remoteRoot);
|
|
1652
1677
|
const captures = parseCaptureList(opts.capture);
|
|
1653
1678
|
const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
|
|
1679
|
+
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
|
|
1654
1680
|
const timeoutMs = timeoutSec * 1e3;
|
|
1655
1681
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
1656
1682
|
const result = await withSession(target, async (session) => {
|
|
@@ -1685,7 +1711,8 @@ async function handleSnapshot(opts) {
|
|
|
1685
1711
|
const pausedStartedAt = pause.receivedAtMs ?? performance2.now();
|
|
1686
1712
|
const snapshot = await captureSnapshot(session, pause, {
|
|
1687
1713
|
captures,
|
|
1688
|
-
includeScopes: opts.includeScopes === true
|
|
1714
|
+
includeScopes: opts.includeScopes === true,
|
|
1715
|
+
...maxValueLength === void 0 ? {} : { maxValueLength }
|
|
1689
1716
|
});
|
|
1690
1717
|
if (opts.keepPaused === true) {
|
|
1691
1718
|
return withPausedDuration(snapshot, null);
|
|
@@ -1856,7 +1883,7 @@ async function main(argv) {
|
|
|
1856
1883
|
"Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42",
|
|
1857
1884
|
collectStrings,
|
|
1858
1885
|
[]
|
|
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(
|
|
1886
|
+
).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("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option(
|
|
1860
1887
|
"--condition <expr>",
|
|
1861
1888
|
"Only pause when this JS expression evaluates truthy in the paused frame"
|
|
1862
1889
|
).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) => {
|