@saptools/cf-inspector 0.2.2 → 0.3.1
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 +13 -5
- package/dist/cli.js +76 -20
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +32 -2
- package/dist/index.js +52 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -115,6 +115,10 @@ cf-inspector snapshot --port 9229 \
|
|
|
115
115
|
| `--no-json` | Print a human-readable summary instead of JSON |
|
|
116
116
|
| `--keep-paused` | Skip the auto-resume after capture (useful for diagnostics) |
|
|
117
117
|
|
|
118
|
+
JSON output includes `captureDurationMs`, the time spent collecting scopes and
|
|
119
|
+
`--capture` expressions after the breakpoint has paused the process. It does
|
|
120
|
+
not include the time spent waiting for the breakpoint to hit.
|
|
121
|
+
|
|
118
122
|
For Cloud Foundry targets, replace `--port` with `--region/--org/--space/--app` (and optionally `--cf-timeout <seconds>` for the tunnel).
|
|
119
123
|
|
|
120
124
|
### 📡 `cf-inspector log`
|
|
@@ -233,14 +237,18 @@ console.log({ bp, snapshot, customValue });
|
|
|
233
237
|
|
|
234
238
|
| Code | When |
|
|
235
239
|
| --- | --- |
|
|
236
|
-
| `
|
|
240
|
+
| `INVALID_ARGUMENT` | A numeric flag (`--port`, `--timeout`, `--duration`, …) is not a positive integer |
|
|
241
|
+
| `INVALID_BREAKPOINT` | `--bp` / `--at` is not in `file:line` form, or line is not a positive integer |
|
|
237
242
|
| `INVALID_REMOTE_ROOT` | `--remote-root` regex did not compile |
|
|
243
|
+
| `INVALID_EXPRESSION` | `--condition` or `--expr` failed to parse on V8 (`Runtime.compileScript` reported a SyntaxError) — fast-fail before the breakpoint is set |
|
|
244
|
+
| `BREAKPOINT_DID_NOT_BIND` | Reserved: a breakpoint resolved to no scripts. Currently surfaced as a stderr warning only — see `BreakpointHandle.resolvedLocations` for programmatic detection |
|
|
238
245
|
| `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 |
|
|
246
|
+
| `INSPECTOR_CONNECTION_FAILED` | WebSocket handshake failed, or the connection closed mid-request |
|
|
247
|
+
| `CDP_REQUEST_FAILED` | A CDP method returned an error result, timed out, or failed to send |
|
|
241
248
|
| `BREAKPOINT_NOT_HIT` | The breakpoint did not hit before the timeout elapsed |
|
|
242
|
-
| `EVALUATION_FAILED` |
|
|
243
|
-
| `MISSING_TARGET` | Neither `--port` nor a CF target was provided |
|
|
249
|
+
| `EVALUATION_FAILED` | Reserved for future use — current evaluation paths surface remote exceptions inline via `CapturedExpression.error` instead of throwing |
|
|
250
|
+
| `MISSING_TARGET` | Neither `--port` nor a complete CF target (`--region/--org/--space/--app`) was provided |
|
|
251
|
+
| `ABORTED` | Reserved for future use by long-running streams when an `AbortSignal` fires |
|
|
244
252
|
|
|
245
253
|
</details>
|
|
246
254
|
|
package/dist/cli.js
CHANGED
|
@@ -606,7 +606,11 @@ async function connectInspector(options) {
|
|
|
606
606
|
});
|
|
607
607
|
const PAUSE_BUFFER_LIMIT = 32;
|
|
608
608
|
const pauseBuffer = [];
|
|
609
|
+
const pauseWaitGate = { active: false };
|
|
609
610
|
client.on("Debugger.paused", (raw) => {
|
|
611
|
+
if (pauseWaitGate.active) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
610
614
|
const params = raw;
|
|
611
615
|
const event = {
|
|
612
616
|
reason: asString(params.reason),
|
|
@@ -625,6 +629,7 @@ async function connectInspector(options) {
|
|
|
625
629
|
target,
|
|
626
630
|
scripts,
|
|
627
631
|
pauseBuffer,
|
|
632
|
+
pauseWaitGate,
|
|
628
633
|
dispose: async () => {
|
|
629
634
|
try {
|
|
630
635
|
await client.send("Debugger.disable");
|
|
@@ -750,17 +755,23 @@ async function waitForPause(session, options) {
|
|
|
750
755
|
return head;
|
|
751
756
|
}
|
|
752
757
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
758
|
+
session.pauseWaitGate.active = true;
|
|
759
|
+
let params;
|
|
760
|
+
try {
|
|
761
|
+
const expected = options.breakpointIds;
|
|
762
|
+
params = await session.client.waitFor("Debugger.paused", {
|
|
763
|
+
timeoutMs: options.timeoutMs,
|
|
764
|
+
predicate: (raw) => {
|
|
765
|
+
if (expected === void 0 || expected.length === 0) {
|
|
766
|
+
return true;
|
|
767
|
+
}
|
|
768
|
+
const ids = Array.isArray(raw.hitBreakpoints) ? raw.hitBreakpoints.filter((id) => typeof id === "string") : [];
|
|
769
|
+
return ids.some((id) => expected.includes(id));
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
} finally {
|
|
773
|
+
session.pauseWaitGate.active = false;
|
|
774
|
+
}
|
|
764
775
|
return {
|
|
765
776
|
reason: asString(params.reason),
|
|
766
777
|
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
@@ -790,6 +801,18 @@ async function evaluateGlobal(session, expression) {
|
|
|
790
801
|
function listScripts(session) {
|
|
791
802
|
return [...session.scripts.values()];
|
|
792
803
|
}
|
|
804
|
+
async function validateExpression(session, expression) {
|
|
805
|
+
const result = await session.client.send("Runtime.compileScript", {
|
|
806
|
+
expression,
|
|
807
|
+
sourceURL: "<cf-inspector-validate>",
|
|
808
|
+
persistScript: false
|
|
809
|
+
});
|
|
810
|
+
if (result.exceptionDetails === void 0) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
|
|
814
|
+
throw new CfInspectorError("INVALID_EXPRESSION", description);
|
|
815
|
+
}
|
|
793
816
|
async function getProperties(session, objectId) {
|
|
794
817
|
const result = await session.client.send("Runtime.getProperties", {
|
|
795
818
|
objectId,
|
|
@@ -870,12 +893,6 @@ function generateSentinel() {
|
|
|
870
893
|
async function streamLogpoint(session, options) {
|
|
871
894
|
const sentinel = generateSentinel();
|
|
872
895
|
const condition = buildLogpointCondition(sentinel, options.expression);
|
|
873
|
-
const handle = await setBreakpoint(session, {
|
|
874
|
-
file: options.location.file,
|
|
875
|
-
line: options.location.line,
|
|
876
|
-
...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
|
|
877
|
-
condition
|
|
878
|
-
});
|
|
879
896
|
let emitted = 0;
|
|
880
897
|
const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
|
|
881
898
|
const params = raw;
|
|
@@ -890,6 +907,19 @@ async function streamLogpoint(session, options) {
|
|
|
890
907
|
emitted += 1;
|
|
891
908
|
options.onEvent(event);
|
|
892
909
|
});
|
|
910
|
+
let handle;
|
|
911
|
+
try {
|
|
912
|
+
handle = await setBreakpoint(session, {
|
|
913
|
+
file: options.location.file,
|
|
914
|
+
line: options.location.line,
|
|
915
|
+
...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
|
|
916
|
+
condition
|
|
917
|
+
});
|
|
918
|
+
} catch (err) {
|
|
919
|
+
offEvent();
|
|
920
|
+
throw err;
|
|
921
|
+
}
|
|
922
|
+
options.onBreakpointSet?.(handle);
|
|
893
923
|
const cleanup = async () => {
|
|
894
924
|
offEvent();
|
|
895
925
|
try {
|
|
@@ -939,6 +969,7 @@ async function waitForStop(session, options) {
|
|
|
939
969
|
}
|
|
940
970
|
|
|
941
971
|
// src/snapshot.ts
|
|
972
|
+
import { performance } from "perf_hooks";
|
|
942
973
|
var MAX_SCOPES = 3;
|
|
943
974
|
var MAX_SCOPE_VARIABLES = 20;
|
|
944
975
|
var MAX_CHILD_VARIABLES = 8;
|
|
@@ -1097,6 +1128,7 @@ function evalResultToCaptured(expression, result) {
|
|
|
1097
1128
|
return buildCaptured("undefined");
|
|
1098
1129
|
}
|
|
1099
1130
|
async function captureSnapshot(session, pause, options = {}) {
|
|
1131
|
+
const startedAt = performance.now();
|
|
1100
1132
|
const top = pause.callFrames[0];
|
|
1101
1133
|
let topFrame;
|
|
1102
1134
|
let captures = [];
|
|
@@ -1123,10 +1155,12 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1123
1155
|
);
|
|
1124
1156
|
}
|
|
1125
1157
|
}
|
|
1158
|
+
const captureDurationMs = Math.round((performance.now() - startedAt) * 1e3) / 1e3;
|
|
1126
1159
|
return {
|
|
1127
1160
|
reason: pause.reason,
|
|
1128
1161
|
hitBreakpoints: pause.hitBreakpoints,
|
|
1129
1162
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1163
|
+
captureDurationMs,
|
|
1130
1164
|
...topFrame === void 0 ? {} : { topFrame },
|
|
1131
1165
|
captures
|
|
1132
1166
|
};
|
|
@@ -1164,8 +1198,8 @@ function parsePositiveInt(raw, label) {
|
|
|
1164
1198
|
return void 0;
|
|
1165
1199
|
}
|
|
1166
1200
|
const value = Number.parseInt(raw, 10);
|
|
1167
|
-
if (Number.isNaN(value) || value <= 0) {
|
|
1168
|
-
throw new CfInspectorError("
|
|
1201
|
+
if (Number.isNaN(value) || value <= 0 || value.toString() !== raw.trim()) {
|
|
1202
|
+
throw new CfInspectorError("INVALID_ARGUMENT", `Invalid ${label}: "${raw}" \u2014 expected a positive integer`);
|
|
1169
1203
|
}
|
|
1170
1204
|
return value;
|
|
1171
1205
|
}
|
|
@@ -1236,9 +1270,23 @@ function writeJson(value) {
|
|
|
1236
1270
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
1237
1271
|
`);
|
|
1238
1272
|
}
|
|
1273
|
+
function warnOnUnboundBreakpoints(handles) {
|
|
1274
|
+
for (const handle of handles) {
|
|
1275
|
+
if (handle.resolvedLocations.length === 0) {
|
|
1276
|
+
process.stderr.write(
|
|
1277
|
+
`[cf-inspector] warning: breakpoint ${handle.file}:${handle.line.toString()} did not bind to any loaded script. Check the path or pass --remote-root. Use 'list-scripts' to inspect what V8 currently has loaded.
|
|
1278
|
+
`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1239
1283
|
function writeHumanSnapshot(snapshot) {
|
|
1240
1284
|
const lines = [];
|
|
1241
|
-
lines.push(
|
|
1285
|
+
lines.push(
|
|
1286
|
+
`Snapshot @ ${snapshot.capturedAt}`,
|
|
1287
|
+
` reason: ${snapshot.reason}`,
|
|
1288
|
+
` capture: ${snapshot.captureDurationMs.toFixed(1)}ms`
|
|
1289
|
+
);
|
|
1242
1290
|
if (snapshot.topFrame) {
|
|
1243
1291
|
const frame = snapshot.topFrame;
|
|
1244
1292
|
const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
|
|
@@ -1278,6 +1326,9 @@ async function handleSnapshot(opts) {
|
|
|
1278
1326
|
const timeoutMs = timeoutSec * 1e3;
|
|
1279
1327
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
1280
1328
|
const result = await withSession(target, async (session) => {
|
|
1329
|
+
if (condition !== void 0) {
|
|
1330
|
+
await validateExpression(session, condition);
|
|
1331
|
+
}
|
|
1281
1332
|
const handles = await Promise.all(
|
|
1282
1333
|
breakpoints.map(
|
|
1283
1334
|
(bp) => setBreakpoint(session, {
|
|
@@ -1288,6 +1339,7 @@ async function handleSnapshot(opts) {
|
|
|
1288
1339
|
})
|
|
1289
1340
|
)
|
|
1290
1341
|
);
|
|
1342
|
+
warnOnUnboundBreakpoints(handles);
|
|
1291
1343
|
const breakpointIds = handles.map((h) => h.breakpointId);
|
|
1292
1344
|
const pause = await waitForPause(session, { timeoutMs, breakpointIds });
|
|
1293
1345
|
const snapshot = await captureSnapshot(session, pause, { captures });
|
|
@@ -1370,6 +1422,7 @@ async function handleLog(opts) {
|
|
|
1370
1422
|
process.once("SIGTERM", onSig);
|
|
1371
1423
|
try {
|
|
1372
1424
|
await withSession(target, async (session) => {
|
|
1425
|
+
await validateExpression(session, expression);
|
|
1373
1426
|
const result = await streamLogpoint(session, {
|
|
1374
1427
|
location,
|
|
1375
1428
|
expression,
|
|
@@ -1378,6 +1431,9 @@ async function handleLog(opts) {
|
|
|
1378
1431
|
signal: abort.signal,
|
|
1379
1432
|
onEvent: (event) => {
|
|
1380
1433
|
writeLogEvent(event, opts.json);
|
|
1434
|
+
},
|
|
1435
|
+
onBreakpointSet: (handle) => {
|
|
1436
|
+
warnOnUnboundBreakpoints([handle]);
|
|
1381
1437
|
}
|
|
1382
1438
|
});
|
|
1383
1439
|
if (opts.json) {
|