@saptools/cf-inspector 0.4.12 → 0.5.0
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 +166 -32
- package/dist/cli.js +1124 -380
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +44 -3
- package/dist/index.js +637 -111
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -152,7 +152,7 @@ var init_wsTransport = __esm({
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
// src/cli.ts
|
|
155
|
-
import
|
|
155
|
+
import process11 from "process";
|
|
156
156
|
|
|
157
157
|
// src/cli/program.ts
|
|
158
158
|
import { readFileSync } from "fs";
|
|
@@ -166,37 +166,65 @@ import process3 from "process";
|
|
|
166
166
|
// src/inspector/discovery.ts
|
|
167
167
|
init_types();
|
|
168
168
|
import { request } from "http";
|
|
169
|
+
import { performance } from "perf_hooks";
|
|
170
|
+
var InvalidDiscoveryPayloadError = class extends CfInspectorError {
|
|
171
|
+
};
|
|
169
172
|
async function fetchJson(url, timeoutMs) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
173
|
+
const deadline = performance.now() + timeoutMs;
|
|
174
|
+
let lastError;
|
|
175
|
+
while (performance.now() < deadline) {
|
|
176
|
+
try {
|
|
177
|
+
const remainingMs = deadline - performance.now();
|
|
178
|
+
if (remainingMs <= 0) {
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
return await new Promise((resolve, reject) => {
|
|
182
|
+
const req = request(url, { method: "GET" }, (res) => {
|
|
183
|
+
const chunks = [];
|
|
184
|
+
res.on("data", (chunk) => {
|
|
185
|
+
chunks.push(chunk);
|
|
186
|
+
});
|
|
187
|
+
res.on("end", () => {
|
|
188
|
+
try {
|
|
189
|
+
resolve(parseJsonResponse(chunks));
|
|
190
|
+
} catch (err) {
|
|
191
|
+
reject(parseDiscoveryError(url, err));
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
res.on("error", (err) => {
|
|
195
|
+
reject(newDiscoveryError(`Inspector discovery response error: ${err.message}`));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
const attemptTimeoutMs = Math.min(2e3, remainingMs);
|
|
199
|
+
req.setTimeout(attemptTimeoutMs, () => {
|
|
200
|
+
req.destroy(
|
|
201
|
+
new CfInspectorError(
|
|
202
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
203
|
+
`Inspector discovery at ${url} timed out after ${timeoutMs.toString()}ms`
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
req.on("error", (err) => {
|
|
208
|
+
reject(err instanceof CfInspectorError ? err : formatDiscoveryRequestError(url, err));
|
|
209
|
+
});
|
|
210
|
+
req.end();
|
|
185
211
|
});
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
212
|
+
} catch (err) {
|
|
213
|
+
if (err instanceof InvalidDiscoveryPayloadError) {
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
lastError = err;
|
|
217
|
+
const now = performance.now();
|
|
218
|
+
if (now < deadline) {
|
|
219
|
+
const sleepMs = Math.min(1e3, deadline - now);
|
|
220
|
+
await new Promise((r) => setTimeout(r, sleepMs));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (lastError instanceof Error) {
|
|
225
|
+
throw lastError;
|
|
226
|
+
}
|
|
227
|
+
throw new CfInspectorError("INSPECTOR_DISCOVERY_FAILED", `Inspector discovery at ${url} timed out after ${timeoutMs.toString()}ms`);
|
|
200
228
|
}
|
|
201
229
|
function isNodeSystemError(err) {
|
|
202
230
|
return err instanceof Error;
|
|
@@ -232,7 +260,10 @@ function parseJsonResponse(chunks) {
|
|
|
232
260
|
}
|
|
233
261
|
function parseDiscoveryError(url, err) {
|
|
234
262
|
const message = err instanceof Error ? err.message : String(err);
|
|
235
|
-
return
|
|
263
|
+
return new InvalidDiscoveryPayloadError(
|
|
264
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
265
|
+
`Failed to parse inspector discovery response from ${url}: ${message}`
|
|
266
|
+
);
|
|
236
267
|
}
|
|
237
268
|
function newDiscoveryError(message) {
|
|
238
269
|
return new CfInspectorError("INSPECTOR_DISCOVERY_FAILED", message);
|
|
@@ -332,7 +363,7 @@ function writeHumanSnapshot(snapshot) {
|
|
|
332
363
|
lines.push(" captures:");
|
|
333
364
|
for (const capture of snapshot.captures) {
|
|
334
365
|
const detail = capture.error ?? capture.value ?? "undefined";
|
|
335
|
-
lines.push(` ${capture.expression} = ${detail}`);
|
|
366
|
+
lines.push(` ${capture.expression} = ${renderTruncated(detail, capture)}`);
|
|
336
367
|
}
|
|
337
368
|
}
|
|
338
369
|
if (snapshot.stack !== void 0 && snapshot.stack.length > 0) {
|
|
@@ -350,13 +381,17 @@ function appendFrameLines(lines, frame) {
|
|
|
350
381
|
lines.push(
|
|
351
382
|
` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
|
|
352
383
|
);
|
|
384
|
+
if (frame.truncated === true) {
|
|
385
|
+
lines.push(` scopes: ${truncationLabel(frame)}`);
|
|
386
|
+
}
|
|
353
387
|
if (frame.scopes === void 0) {
|
|
354
388
|
return;
|
|
355
389
|
}
|
|
356
390
|
for (const scope of frame.scopes) {
|
|
357
|
-
|
|
391
|
+
const scopeSuffix = scope.truncated === true ? `; ${truncationLabel(scope)}` : "";
|
|
392
|
+
lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars${scopeSuffix}):`);
|
|
358
393
|
for (const variable of scope.variables) {
|
|
359
|
-
lines.push(` ${variable.name} = ${variable.value}`);
|
|
394
|
+
lines.push(` ${variable.name} = ${renderTruncated(variable.value, variable)}`);
|
|
360
395
|
}
|
|
361
396
|
}
|
|
362
397
|
}
|
|
@@ -367,7 +402,7 @@ function appendStackFrameLine(lines, frame) {
|
|
|
367
402
|
if (frame.captures !== void 0) {
|
|
368
403
|
for (const capture of frame.captures) {
|
|
369
404
|
const detail = capture.error ?? capture.value ?? "undefined";
|
|
370
|
-
lines.push(` ${capture.expression} = ${detail}`);
|
|
405
|
+
lines.push(` ${capture.expression} = ${renderTruncated(detail, capture)}`);
|
|
371
406
|
}
|
|
372
407
|
}
|
|
373
408
|
}
|
|
@@ -376,8 +411,7 @@ function appendExceptionLines(lines, exception) {
|
|
|
376
411
|
lines.push(` exception: !err ${exception.error}`);
|
|
377
412
|
return;
|
|
378
413
|
}
|
|
379
|
-
|
|
380
|
-
lines.push(` exception: ${detail}`);
|
|
414
|
+
lines.push(` exception: ${renderExceptionDetail(exception)}`);
|
|
381
415
|
}
|
|
382
416
|
function writeLogEvent(event, json) {
|
|
383
417
|
if (json) {
|
|
@@ -386,11 +420,11 @@ function writeLogEvent(event, json) {
|
|
|
386
420
|
return;
|
|
387
421
|
}
|
|
388
422
|
if (event.error !== void 0) {
|
|
389
|
-
process.stdout.write(`[${event.ts}] ${event.at} !err ${event.error}
|
|
423
|
+
process.stdout.write(`[${event.ts}] ${event.at} !err ${renderTruncated(event.error, event)}
|
|
390
424
|
`);
|
|
391
425
|
return;
|
|
392
426
|
}
|
|
393
|
-
process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
|
|
427
|
+
process.stdout.write(`[${event.ts}] ${event.at} ${renderTruncated(event.value ?? "", event)}
|
|
394
428
|
`);
|
|
395
429
|
}
|
|
396
430
|
function writeWatchEvent(event, json) {
|
|
@@ -402,23 +436,53 @@ function writeWatchEvent(event, json) {
|
|
|
402
436
|
process.stdout.write(`[${event.ts}] hit#${event.hit.toString()} ${event.at}
|
|
403
437
|
`);
|
|
404
438
|
if (event.exception !== void 0) {
|
|
405
|
-
|
|
406
|
-
process.stdout.write(` exception: ${detail}
|
|
439
|
+
process.stdout.write(` exception: ${renderExceptionDetail(event.exception)}
|
|
407
440
|
`);
|
|
408
441
|
}
|
|
409
442
|
for (const capture of event.captures) {
|
|
410
443
|
const detail = capture.error ?? capture.value ?? "undefined";
|
|
411
|
-
process.stdout.write(` ${capture.expression} = ${detail}
|
|
444
|
+
process.stdout.write(` ${capture.expression} = ${renderTruncated(detail, capture)}
|
|
412
445
|
`);
|
|
413
446
|
}
|
|
414
447
|
}
|
|
448
|
+
function renderExceptionDetail(exception) {
|
|
449
|
+
if (exception.description !== void 0) {
|
|
450
|
+
const originalLength = exception.descriptionOriginalLength;
|
|
451
|
+
return renderTruncated(
|
|
452
|
+
exception.description,
|
|
453
|
+
originalLength === void 0 ? {} : { truncated: true, originalLength }
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (exception.value !== void 0) {
|
|
457
|
+
const originalLength = exception.valueOriginalLength ?? exception.originalLength;
|
|
458
|
+
const summary = {
|
|
459
|
+
...originalLength === void 0 ? {} : { truncated: true, originalLength },
|
|
460
|
+
...exception.omittedCount === void 0 ? {} : { truncated: true, omittedCount: exception.omittedCount }
|
|
461
|
+
};
|
|
462
|
+
return renderTruncated(exception.value, summary);
|
|
463
|
+
}
|
|
464
|
+
return exception.error ?? "(unknown)";
|
|
465
|
+
}
|
|
466
|
+
function renderTruncated(value, summary) {
|
|
467
|
+
if (summary.truncated !== true) {
|
|
468
|
+
return value;
|
|
469
|
+
}
|
|
470
|
+
const visualValue = summary.originalLength === void 0 ? value : `${value}\u2026`;
|
|
471
|
+
return `${visualValue} [${truncationLabel(summary)}]`;
|
|
472
|
+
}
|
|
473
|
+
function truncationLabel(summary) {
|
|
474
|
+
const details = [];
|
|
475
|
+
if (summary.originalLength !== void 0) {
|
|
476
|
+
details.push(`original ${summary.originalLength.toString()} chars`);
|
|
477
|
+
}
|
|
478
|
+
if (summary.omittedCount !== void 0) {
|
|
479
|
+
details.push(`${summary.omittedCount.toString()} omitted`);
|
|
480
|
+
}
|
|
481
|
+
return details.length === 0 ? "truncated" : `truncated: ${details.join(", ")}`;
|
|
482
|
+
}
|
|
415
483
|
|
|
416
484
|
// src/cli/target.ts
|
|
417
|
-
import
|
|
418
|
-
import {
|
|
419
|
-
readCurrentCfTarget,
|
|
420
|
-
requireCurrentCfRegion
|
|
421
|
-
} from "@saptools/cf-debugger";
|
|
485
|
+
import "@saptools/cf-debugger";
|
|
422
486
|
|
|
423
487
|
// src/cf/tunnel.ts
|
|
424
488
|
import { startDebugger } from "@saptools/cf-debugger";
|
|
@@ -485,7 +549,7 @@ function extractExistingTunnelPort(message) {
|
|
|
485
549
|
}
|
|
486
550
|
|
|
487
551
|
// src/inspector/session.ts
|
|
488
|
-
import { performance } from "perf_hooks";
|
|
552
|
+
import { performance as performance2 } from "perf_hooks";
|
|
489
553
|
|
|
490
554
|
// src/cdp/client.ts
|
|
491
555
|
init_types();
|
|
@@ -727,6 +791,102 @@ var CdpClient = class _CdpClient {
|
|
|
727
791
|
this.emitter.removeAllListeners();
|
|
728
792
|
}
|
|
729
793
|
};
|
|
794
|
+
var NodeWorkerTransport = class {
|
|
795
|
+
constructor(parent, sessionId) {
|
|
796
|
+
this.parent = parent;
|
|
797
|
+
this.sessionId = sessionId;
|
|
798
|
+
this.detachParentListeners = [
|
|
799
|
+
parent.on("NodeWorker.receivedMessageFromWorker", (raw) => {
|
|
800
|
+
this.forwardWorkerMessage(raw);
|
|
801
|
+
}),
|
|
802
|
+
parent.on("NodeWorker.detachedFromWorker", (raw) => {
|
|
803
|
+
this.handleWorkerDetach(raw);
|
|
804
|
+
}),
|
|
805
|
+
parent.onClose((error) => {
|
|
806
|
+
this.closeWithError(error);
|
|
807
|
+
})
|
|
808
|
+
];
|
|
809
|
+
}
|
|
810
|
+
parent;
|
|
811
|
+
sessionId;
|
|
812
|
+
emitter = new EventEmitter();
|
|
813
|
+
detachParentListeners;
|
|
814
|
+
readyState = 1;
|
|
815
|
+
send(payload) {
|
|
816
|
+
if (this.readyState !== 1) {
|
|
817
|
+
throw new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Worker inspector session is closed");
|
|
818
|
+
}
|
|
819
|
+
void this.parent.send("NodeWorker.sendMessageToWorker", {
|
|
820
|
+
sessionId: this.sessionId,
|
|
821
|
+
message: payload
|
|
822
|
+
}).catch((error) => {
|
|
823
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
824
|
+
this.closeWithError(normalized);
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
close() {
|
|
828
|
+
this.finishClose();
|
|
829
|
+
}
|
|
830
|
+
on(event, listener) {
|
|
831
|
+
this.emitter.on(event, listener);
|
|
832
|
+
}
|
|
833
|
+
off(event, listener) {
|
|
834
|
+
this.emitter.off(event, listener);
|
|
835
|
+
}
|
|
836
|
+
forwardWorkerMessage(raw) {
|
|
837
|
+
const params = asNodeWorkerEventParams(raw);
|
|
838
|
+
if (params.sessionId !== this.sessionId || typeof params.message !== "string") {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
this.emitter.emit("message", params.message);
|
|
842
|
+
}
|
|
843
|
+
handleWorkerDetach(raw) {
|
|
844
|
+
const params = asNodeWorkerEventParams(raw);
|
|
845
|
+
if (params.sessionId === this.sessionId) {
|
|
846
|
+
this.finishClose();
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
closeWithError(error) {
|
|
850
|
+
if (this.readyState !== 1) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
this.emitter.emit("error", error);
|
|
854
|
+
this.finishClose();
|
|
855
|
+
}
|
|
856
|
+
finishClose() {
|
|
857
|
+
if (this.readyState !== 1) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
this.readyState = 3;
|
|
861
|
+
for (const detach of this.detachParentListeners) {
|
|
862
|
+
detach();
|
|
863
|
+
}
|
|
864
|
+
this.emitter.emit("close");
|
|
865
|
+
this.emitter.removeAllListeners();
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
function asNodeWorkerEventParams(raw) {
|
|
869
|
+
if (!isUnknownRecord(raw)) {
|
|
870
|
+
return {};
|
|
871
|
+
}
|
|
872
|
+
const sessionId = raw["sessionId"];
|
|
873
|
+
const message = raw["message"];
|
|
874
|
+
return {
|
|
875
|
+
...typeof sessionId === "string" ? { sessionId } : {},
|
|
876
|
+
...typeof message === "string" ? { message } : {}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function isUnknownRecord(value) {
|
|
880
|
+
return typeof value === "object" && value !== null;
|
|
881
|
+
}
|
|
882
|
+
async function createNodeWorkerClient(parent, sessionId, requestTimeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
883
|
+
const transport = new NodeWorkerTransport(parent, sessionId);
|
|
884
|
+
return await CdpClient.connect({
|
|
885
|
+
url: `node-worker://${sessionId}`,
|
|
886
|
+
transportFactory: () => Promise.resolve(transport),
|
|
887
|
+
requestTimeoutMs
|
|
888
|
+
});
|
|
889
|
+
}
|
|
730
890
|
|
|
731
891
|
// src/inspector/session.ts
|
|
732
892
|
init_types();
|
|
@@ -843,6 +1003,101 @@ function pauseDetail(pause) {
|
|
|
843
1003
|
var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
|
|
844
1004
|
var DEFAULT_HOST = "127.0.0.1";
|
|
845
1005
|
var PAUSE_BUFFER_LIMIT = 32;
|
|
1006
|
+
var NodeWorkerDiscovery = class {
|
|
1007
|
+
constructor(client) {
|
|
1008
|
+
this.client = client;
|
|
1009
|
+
this.detachListeners = [
|
|
1010
|
+
client.on("NodeWorker.attachedToWorker", (raw) => {
|
|
1011
|
+
const worker = toInspectorWorkerTarget(raw);
|
|
1012
|
+
if (worker !== void 0) {
|
|
1013
|
+
this.workers.set(worker.sessionId, worker);
|
|
1014
|
+
}
|
|
1015
|
+
}),
|
|
1016
|
+
client.on("NodeWorker.detachedFromWorker", (raw) => {
|
|
1017
|
+
const sessionId = readField(raw, "sessionId");
|
|
1018
|
+
if (typeof sessionId === "string") {
|
|
1019
|
+
this.workers.delete(sessionId);
|
|
1020
|
+
}
|
|
1021
|
+
})
|
|
1022
|
+
];
|
|
1023
|
+
}
|
|
1024
|
+
client;
|
|
1025
|
+
workers = /* @__PURE__ */ new Map();
|
|
1026
|
+
detachListeners;
|
|
1027
|
+
supported = false;
|
|
1028
|
+
disposed = false;
|
|
1029
|
+
async enable() {
|
|
1030
|
+
try {
|
|
1031
|
+
await this.client.send("NodeWorker.enable", { waitForDebuggerOnStart: false });
|
|
1032
|
+
this.supported = true;
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (!isUnsupportedNodeWorkerDomain(error)) {
|
|
1035
|
+
throw error;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
list() {
|
|
1040
|
+
return [...this.workers.values()].sort(compareWorkers);
|
|
1041
|
+
}
|
|
1042
|
+
async dispose() {
|
|
1043
|
+
if (this.disposed) {
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
this.disposed = true;
|
|
1047
|
+
if (this.supported && !this.client.isClosed) {
|
|
1048
|
+
try {
|
|
1049
|
+
await this.client.send("NodeWorker.disable");
|
|
1050
|
+
} catch {
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
for (const detach of this.detachListeners) {
|
|
1054
|
+
detach();
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
function isUnsupportedNodeWorkerDomain(error) {
|
|
1059
|
+
if (!(error instanceof CfInspectorError) || error.code !== "CDP_REQUEST_FAILED") {
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
return error.detail?.includes('"code":-32601') === true;
|
|
1063
|
+
}
|
|
1064
|
+
function compareWorkers(left, right) {
|
|
1065
|
+
const leftId = Number.parseInt(left.workerId, 10);
|
|
1066
|
+
const rightId = Number.parseInt(right.workerId, 10);
|
|
1067
|
+
if (!Number.isNaN(leftId) && !Number.isNaN(rightId) && leftId !== rightId) {
|
|
1068
|
+
return leftId - rightId;
|
|
1069
|
+
}
|
|
1070
|
+
return left.workerId.localeCompare(right.workerId);
|
|
1071
|
+
}
|
|
1072
|
+
function toInspectorWorkerTarget(raw) {
|
|
1073
|
+
const sessionId = readField(raw, "sessionId");
|
|
1074
|
+
const info = readField(raw, "workerInfo");
|
|
1075
|
+
if (typeof sessionId !== "string" || !isUnknownRecord2(info)) {
|
|
1076
|
+
return void 0;
|
|
1077
|
+
}
|
|
1078
|
+
const workerId = asString(info["workerId"]);
|
|
1079
|
+
if (workerId.length === 0) {
|
|
1080
|
+
return void 0;
|
|
1081
|
+
}
|
|
1082
|
+
return {
|
|
1083
|
+
sessionId,
|
|
1084
|
+
workerId,
|
|
1085
|
+
type: asString(info["type"]),
|
|
1086
|
+
title: asString(info["title"]),
|
|
1087
|
+
url: asString(info["url"])
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
function readField(value, name) {
|
|
1091
|
+
return isUnknownRecord2(value) ? value[name] : void 0;
|
|
1092
|
+
}
|
|
1093
|
+
function isUnknownRecord2(value) {
|
|
1094
|
+
return typeof value === "object" && value !== null;
|
|
1095
|
+
}
|
|
1096
|
+
async function startNodeWorkerDiscovery(client) {
|
|
1097
|
+
const discovery = new NodeWorkerDiscovery(client);
|
|
1098
|
+
await discovery.enable();
|
|
1099
|
+
return discovery;
|
|
1100
|
+
}
|
|
846
1101
|
async function connectInspector(options) {
|
|
847
1102
|
const host = options.host ?? DEFAULT_HOST;
|
|
848
1103
|
const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
@@ -859,13 +1114,81 @@ async function connectInspector(options) {
|
|
|
859
1114
|
url: target.webSocketDebuggerUrl,
|
|
860
1115
|
connectTimeoutMs
|
|
861
1116
|
});
|
|
1117
|
+
let workerDiscovery;
|
|
862
1118
|
try {
|
|
863
|
-
|
|
1119
|
+
workerDiscovery = await startNodeWorkerDiscovery(client);
|
|
1120
|
+
if (options.workerIndex === void 0) {
|
|
1121
|
+
const session = await initSession(client, target);
|
|
1122
|
+
return withWorkerMetadata(session, workerDiscovery, targetIndex, targets.length);
|
|
1123
|
+
}
|
|
1124
|
+
return await initWorkerSession(
|
|
1125
|
+
client,
|
|
1126
|
+
workerDiscovery,
|
|
1127
|
+
options.workerIndex,
|
|
1128
|
+
targetIndex,
|
|
1129
|
+
targets.length
|
|
1130
|
+
);
|
|
864
1131
|
} catch (err) {
|
|
1132
|
+
await workerDiscovery?.dispose();
|
|
865
1133
|
client.dispose();
|
|
866
1134
|
throw err;
|
|
867
1135
|
}
|
|
868
1136
|
}
|
|
1137
|
+
async function initWorkerSession(parent, discovery, workerIndex, targetIndex, targetCount) {
|
|
1138
|
+
const workers = discovery.list();
|
|
1139
|
+
if (!discovery.supported) {
|
|
1140
|
+
throw new CfInspectorError(
|
|
1141
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
1142
|
+
"This runtime does not expose the NodeWorker CDP domain; --worker cannot be used. Run list-targets for available raw targets."
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
const worker = workers[workerIndex];
|
|
1146
|
+
if (worker === void 0) {
|
|
1147
|
+
throw new CfInspectorError(
|
|
1148
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
1149
|
+
`No NodeWorker sub-session at index ${workerIndex.toString()} (available: ${workers.length.toString()}). Ensure the worker is alive, then rerun list-targets.`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
const client = await createNodeWorkerClient(parent, worker.sessionId);
|
|
1153
|
+
const session = await initSession(client, workerToInspectorTarget(worker));
|
|
1154
|
+
return withWorkerMetadata(session, discovery, targetIndex, targetCount, workerIndex, parent);
|
|
1155
|
+
}
|
|
1156
|
+
function withWorkerMetadata(session, discovery, targetIndex, targetCount, workerIndex, parent) {
|
|
1157
|
+
return {
|
|
1158
|
+
...session,
|
|
1159
|
+
targetIndex,
|
|
1160
|
+
targetCount,
|
|
1161
|
+
...workerIndex === void 0 ? {} : { workerIndex },
|
|
1162
|
+
workerTargets: discovery.list(),
|
|
1163
|
+
workerDiscoverySupported: discovery.supported,
|
|
1164
|
+
dispose: async () => {
|
|
1165
|
+
await session.dispose();
|
|
1166
|
+
await discovery.dispose();
|
|
1167
|
+
parent?.dispose();
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
function workerToInspectorTarget(worker) {
|
|
1172
|
+
return {
|
|
1173
|
+
description: "Node worker sub-session",
|
|
1174
|
+
id: worker.workerId,
|
|
1175
|
+
title: worker.title,
|
|
1176
|
+
type: worker.type,
|
|
1177
|
+
url: worker.url,
|
|
1178
|
+
webSocketDebuggerUrl: `node-worker://${worker.sessionId}`
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
async function discoverNodeWorkerTargets(target, connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MS) {
|
|
1182
|
+
const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl, connectTimeoutMs });
|
|
1183
|
+
let discovery;
|
|
1184
|
+
try {
|
|
1185
|
+
discovery = await startNodeWorkerDiscovery(client);
|
|
1186
|
+
return { supported: discovery.supported, workers: discovery.list() };
|
|
1187
|
+
} finally {
|
|
1188
|
+
await discovery?.dispose();
|
|
1189
|
+
client.dispose();
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
869
1192
|
async function initSession(client, target) {
|
|
870
1193
|
const scripts = /* @__PURE__ */ new Map();
|
|
871
1194
|
client.on("Debugger.scriptParsed", (raw) => {
|
|
@@ -885,14 +1208,14 @@ async function initSession(client, target) {
|
|
|
885
1208
|
return;
|
|
886
1209
|
}
|
|
887
1210
|
const params = raw;
|
|
888
|
-
const event = toPauseEvent(params,
|
|
1211
|
+
const event = toPauseEvent(params, performance2.now(), scripts);
|
|
889
1212
|
if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
|
|
890
1213
|
pauseBuffer.shift();
|
|
891
1214
|
}
|
|
892
1215
|
pauseBuffer.push(event);
|
|
893
1216
|
});
|
|
894
1217
|
client.on("Debugger.resumed", () => {
|
|
895
|
-
debuggerState.lastResumedAtMs =
|
|
1218
|
+
debuggerState.lastResumedAtMs = performance2.now();
|
|
896
1219
|
});
|
|
897
1220
|
await client.send("Runtime.enable");
|
|
898
1221
|
await client.send("Debugger.enable");
|
|
@@ -921,6 +1244,223 @@ var DEFAULT_BREAKPOINT_TIMEOUT_SEC = 30;
|
|
|
921
1244
|
var DEFAULT_CF_TIMEOUT_SEC = 180;
|
|
922
1245
|
var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
|
|
923
1246
|
|
|
1247
|
+
// src/cli/warnings.ts
|
|
1248
|
+
init_types();
|
|
1249
|
+
import process2 from "process";
|
|
1250
|
+
|
|
1251
|
+
// src/cli/captureParser.ts
|
|
1252
|
+
function parseCaptureList(raw) {
|
|
1253
|
+
if (raw === void 0 || raw.trim().length === 0) {
|
|
1254
|
+
return [];
|
|
1255
|
+
}
|
|
1256
|
+
return splitCaptureExpressions(raw);
|
|
1257
|
+
}
|
|
1258
|
+
function isQuoteChar(value) {
|
|
1259
|
+
return value === "'" || value === '"' || value === "`";
|
|
1260
|
+
}
|
|
1261
|
+
function consumeQuotedChar(state, char) {
|
|
1262
|
+
if (state.quote === void 0) {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
if (state.escaped) {
|
|
1266
|
+
state.escaped = false;
|
|
1267
|
+
return true;
|
|
1268
|
+
}
|
|
1269
|
+
if (char === "\\") {
|
|
1270
|
+
state.escaped = true;
|
|
1271
|
+
return true;
|
|
1272
|
+
}
|
|
1273
|
+
if (char === state.quote) {
|
|
1274
|
+
state.quote = void 0;
|
|
1275
|
+
}
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
function stripQuotedText(expression) {
|
|
1279
|
+
const state = { quote: void 0, escaped: false };
|
|
1280
|
+
let stripped = "";
|
|
1281
|
+
for (const char of expression) {
|
|
1282
|
+
if (consumeQuotedChar(state, char)) {
|
|
1283
|
+
stripped += " ";
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (isQuoteChar(char)) {
|
|
1287
|
+
state.quote = char;
|
|
1288
|
+
stripped += " ";
|
|
1289
|
+
continue;
|
|
1290
|
+
}
|
|
1291
|
+
stripped += char;
|
|
1292
|
+
}
|
|
1293
|
+
return stripped;
|
|
1294
|
+
}
|
|
1295
|
+
function looksLikeMutation(expression) {
|
|
1296
|
+
const stripped = stripQuotedText(expression);
|
|
1297
|
+
const hasUpdate = /(?:\+\+|--)/u.test(stripped);
|
|
1298
|
+
const hasAssignment = /(?:\*\*=|&&=|\|\|=|\?\?=|[+\-*/%&|^]=|(?:^|[^=!<>])=(?!=|>))/u.test(stripped);
|
|
1299
|
+
const hasDelete = /\bdelete\b/u.test(stripped);
|
|
1300
|
+
const hasMutatingMethod = /\.\s*(?:push|pop|shift|unshift|splice|sort|reverse|fill|copyWithin|set|add|delete|clear)\s*\(/u.test(stripped);
|
|
1301
|
+
const hasObjectMutation = /\bObject\s*\.\s*(?:assign|defineProperty|defineProperties)\s*\(/u.test(stripped);
|
|
1302
|
+
return hasUpdate || hasAssignment || hasDelete || hasMutatingMethod || hasObjectMutation;
|
|
1303
|
+
}
|
|
1304
|
+
function updateCaptureDepth(state, char) {
|
|
1305
|
+
if (char === "(") {
|
|
1306
|
+
state.parenDepth += 1;
|
|
1307
|
+
} else if (char === ")") {
|
|
1308
|
+
state.parenDepth = Math.max(0, state.parenDepth - 1);
|
|
1309
|
+
} else if (char === "[") {
|
|
1310
|
+
state.bracketDepth += 1;
|
|
1311
|
+
} else if (char === "]") {
|
|
1312
|
+
state.bracketDepth = Math.max(0, state.bracketDepth - 1);
|
|
1313
|
+
} else if (char === "{") {
|
|
1314
|
+
state.braceDepth += 1;
|
|
1315
|
+
} else if (char === "}") {
|
|
1316
|
+
state.braceDepth = Math.max(0, state.braceDepth - 1);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function isTopLevel(state) {
|
|
1320
|
+
return state.parenDepth === 0 && state.bracketDepth === 0 && state.braceDepth === 0;
|
|
1321
|
+
}
|
|
1322
|
+
function appendCapturePiece(raw, state, end) {
|
|
1323
|
+
const piece = raw.slice(state.start, end).trim();
|
|
1324
|
+
if (piece.length > 0) {
|
|
1325
|
+
state.pieces.push(piece);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function splitCaptureExpressions(raw) {
|
|
1329
|
+
const state = {
|
|
1330
|
+
escaped: false,
|
|
1331
|
+
parenDepth: 0,
|
|
1332
|
+
bracketDepth: 0,
|
|
1333
|
+
braceDepth: 0,
|
|
1334
|
+
quote: void 0,
|
|
1335
|
+
start: 0,
|
|
1336
|
+
pieces: []
|
|
1337
|
+
};
|
|
1338
|
+
for (let idx = 0; idx < raw.length; idx += 1) {
|
|
1339
|
+
const char = raw.charAt(idx);
|
|
1340
|
+
if (consumeQuotedChar(state, char)) {
|
|
1341
|
+
continue;
|
|
1342
|
+
}
|
|
1343
|
+
if (isQuoteChar(char)) {
|
|
1344
|
+
state.quote = char;
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
updateCaptureDepth(state, char);
|
|
1348
|
+
if (char === "," && isTopLevel(state)) {
|
|
1349
|
+
appendCapturePiece(raw, state, idx);
|
|
1350
|
+
state.start = idx + 1;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
appendCapturePiece(raw, state, raw.length);
|
|
1354
|
+
return state.pieces;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/cli/warnings.ts
|
|
1358
|
+
function warnOnCaptureMutationRisk(expressions, allowMutation) {
|
|
1359
|
+
const riskyCount = expressions.filter(looksLikeMutation).length;
|
|
1360
|
+
if (riskyCount === 0) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const suffix = allowMutation ? "will run without the V8 side-effect guard because --allow-mutation was passed." : "will be checked by the V8 side-effect guard and blocked unless V8 proves them safe; pass --allow-mutation to run them unrestricted.";
|
|
1364
|
+
process2.stderr.write(
|
|
1365
|
+
`[cf-inspector] warning: ${riskyCount.toString()} capture ${riskyCount === 1 ? "expression looks" : "expressions look"} mutation-capable and ${suffix}
|
|
1366
|
+
`
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
function enforceNativeConditionMutationPolicy(expression, allowMutation, context) {
|
|
1370
|
+
if (!looksLikeMutation(expression)) {
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
if (!allowMutation) {
|
|
1374
|
+
throw new CfInspectorError(
|
|
1375
|
+
"MUTATION_NOT_ALLOWED",
|
|
1376
|
+
`${context} looks mutation-capable. Native breakpoint conditions cannot be protected by V8's side-effect guard; pass --allow-mutation to arm it explicitly.`
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
process2.stderr.write(
|
|
1380
|
+
`[cf-inspector] warning: ${context} looks mutation-capable and will run as a native breakpoint condition; native conditions cannot be side-effect-gated.
|
|
1381
|
+
`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
function warnOnMutationRisk(expression, context) {
|
|
1385
|
+
if (!looksLikeMutation(expression)) {
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
process2.stderr.write(
|
|
1389
|
+
`[cf-inspector] warning: ${context} looks mutation-capable and will execute against the live inspectee without a side-effect guard.
|
|
1390
|
+
`
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
function warnOnUnboundBreakpoints(handles) {
|
|
1394
|
+
for (const handle of handles) {
|
|
1395
|
+
if (handle.resolvedLocations.length === 0) {
|
|
1396
|
+
const tsHint = handle.file.endsWith(".ts") ? " Hint: Source TS breakpoints may not bind. Try inspecting loaded scripts with list-scripts and target the compiled .js file instead." : "";
|
|
1397
|
+
process2.stderr.write(
|
|
1398
|
+
`[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.${tsHint}
|
|
1399
|
+
`
|
|
1400
|
+
);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function warnOnImplicitInspectorSelection(session, targetWasExplicit, workerWasExplicit) {
|
|
1405
|
+
const targetCount = session.targetCount ?? 1;
|
|
1406
|
+
const targetIndex = session.targetIndex ?? 0;
|
|
1407
|
+
if (!targetWasExplicit && targetCount > 1) {
|
|
1408
|
+
process2.stderr.write(
|
|
1409
|
+
`[cf-inspector] notice: attached to inspector target ${targetIndex.toString()} of ${targetCount.toString()}; pass --target <index> to pick another.
|
|
1410
|
+
`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
const workerCount = session.workerTargets?.length ?? 0;
|
|
1414
|
+
if (!workerWasExplicit && workerCount > 0) {
|
|
1415
|
+
process2.stderr.write(
|
|
1416
|
+
`[cf-inspector] notice: attached to the main isolate; ${workerCount.toString()} Node ${workerCount === 1 ? "worker is" : "workers are"} available. Run list-targets and pass --worker <index> to inspect one.
|
|
1417
|
+
`
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function warnOnBoundBreakpointWithoutHit(handles) {
|
|
1422
|
+
const boundCount = handles.reduce((count, handle) => {
|
|
1423
|
+
return count + handle.resolvedLocations.length;
|
|
1424
|
+
}, 0);
|
|
1425
|
+
if (boundCount === 0) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1428
|
+
process2.stderr.write(
|
|
1429
|
+
`[cf-inspector] warning: ${boundCount.toString()} breakpoint ${boundCount === 1 ? "location bound" : "locations bound"}, but no hit was observed. The code may be running in another worker isolate. Run list-targets and retry with --worker <index> for a NodeWorker sub-session or --target <index> for a raw target.
|
|
1430
|
+
`
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
function roundDurationMs(durationMs) {
|
|
1434
|
+
return Math.round(durationMs * 1e3) / 1e3;
|
|
1435
|
+
}
|
|
1436
|
+
function warnOnUnmatchedPause(pause) {
|
|
1437
|
+
const reason = pause.reason.length > 0 ? pause.reason : "unknown";
|
|
1438
|
+
process2.stderr.write(
|
|
1439
|
+
`[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
|
|
1440
|
+
`
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
function withPausedDuration(snapshot, pausedDurationMs) {
|
|
1444
|
+
const base = {
|
|
1445
|
+
reason: snapshot.reason,
|
|
1446
|
+
hitBreakpoints: snapshot.hitBreakpoints,
|
|
1447
|
+
capturedAt: snapshot.capturedAt,
|
|
1448
|
+
pausedDurationMs,
|
|
1449
|
+
captures: snapshot.captures
|
|
1450
|
+
};
|
|
1451
|
+
const withFrame = snapshot.topFrame === void 0 ? base : { ...base, topFrame: snapshot.topFrame };
|
|
1452
|
+
const withStack = snapshot.stack === void 0 ? withFrame : { ...withFrame, stack: snapshot.stack };
|
|
1453
|
+
return snapshot.exception === void 0 ? withStack : { ...withStack, exception: snapshot.exception };
|
|
1454
|
+
}
|
|
1455
|
+
function formatPauseLocation(pause) {
|
|
1456
|
+
const top = pause.callFrames[0];
|
|
1457
|
+
if (top === void 0) {
|
|
1458
|
+
return "(no call frame)";
|
|
1459
|
+
}
|
|
1460
|
+
const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
|
|
1461
|
+
return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
924
1464
|
// src/cli/target.ts
|
|
925
1465
|
var CF_TUNNEL_STATUS_MESSAGES = {
|
|
926
1466
|
starting: "Preparing the Cloud Foundry debugger...",
|
|
@@ -948,41 +1488,48 @@ function parsePositiveInt(raw, label) {
|
|
|
948
1488
|
}
|
|
949
1489
|
return value;
|
|
950
1490
|
}
|
|
951
|
-
|
|
1491
|
+
function resolveTarget(opts, options = {}) {
|
|
952
1492
|
const port = parsePositiveInt(opts.port, "--port");
|
|
953
1493
|
const targetIndex = parseTargetIndex(opts.target);
|
|
1494
|
+
const workerIndex = parseSelectionIndex(opts.worker, "--worker");
|
|
954
1495
|
if (port !== void 0) {
|
|
955
|
-
return {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1496
|
+
return {
|
|
1497
|
+
kind: "port",
|
|
1498
|
+
port,
|
|
1499
|
+
host: opts.host ?? "127.0.0.1",
|
|
1500
|
+
...selectionOptions(targetIndex, workerIndex)
|
|
1501
|
+
};
|
|
960
1502
|
}
|
|
961
|
-
const tunnelTimeoutSec = parseTunnelTimeout(opts, options);
|
|
962
1503
|
const region = optionalText(opts.region);
|
|
963
|
-
const apiEndpoint = optionalText(opts.apiEndpoint);
|
|
964
1504
|
const org = optionalText(opts.org);
|
|
965
1505
|
const space = optionalText(opts.space);
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1506
|
+
const app = optionalText(opts.app);
|
|
1507
|
+
const missingFlags = [
|
|
1508
|
+
...region === void 0 ? ["--region"] : [],
|
|
1509
|
+
...org === void 0 ? ["--org"] : [],
|
|
1510
|
+
...space === void 0 ? ["--space"] : [],
|
|
1511
|
+
...app === void 0 ? ["--app"] : []
|
|
1512
|
+
];
|
|
1513
|
+
if (region === void 0 || org === void 0 || space === void 0 || app === void 0) {
|
|
971
1514
|
throw new CfInspectorError(
|
|
972
1515
|
"MISSING_TARGET",
|
|
973
|
-
|
|
1516
|
+
`Cloud Foundry targeting requires explicit selectors. Missing: ${missingFlags.join(", ")}. cf-inspector does not consult ambient \`cf target\` because it can silently change between runs.`
|
|
974
1517
|
);
|
|
975
1518
|
}
|
|
976
1519
|
return buildCfTarget(
|
|
977
|
-
region
|
|
978
|
-
|
|
979
|
-
org
|
|
980
|
-
space
|
|
1520
|
+
region,
|
|
1521
|
+
optionalText(opts.apiEndpoint),
|
|
1522
|
+
org,
|
|
1523
|
+
space,
|
|
981
1524
|
app,
|
|
982
|
-
|
|
983
|
-
targetIndex
|
|
1525
|
+
parseTunnelTimeout(opts, options),
|
|
1526
|
+
targetIndex,
|
|
1527
|
+
workerIndex
|
|
984
1528
|
);
|
|
985
1529
|
}
|
|
1530
|
+
async function resolveTargetWithCurrentCfTarget(opts, options = {}) {
|
|
1531
|
+
return await Promise.resolve(resolveTarget(opts, options));
|
|
1532
|
+
}
|
|
986
1533
|
function parseTunnelTimeout(opts, options) {
|
|
987
1534
|
if (options.useTimeoutForTunnel === false) {
|
|
988
1535
|
return DEFAULT_CF_TIMEOUT_SEC;
|
|
@@ -990,19 +1537,31 @@ function parseTunnelTimeout(opts, options) {
|
|
|
990
1537
|
return parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_CF_TIMEOUT_SEC;
|
|
991
1538
|
}
|
|
992
1539
|
function parseTargetIndex(raw) {
|
|
1540
|
+
return parseSelectionIndex(raw, "--target");
|
|
1541
|
+
}
|
|
1542
|
+
function parseSelectionIndex(raw, label) {
|
|
993
1543
|
if (raw === void 0) {
|
|
994
1544
|
return void 0;
|
|
995
1545
|
}
|
|
996
1546
|
const value = Number.parseInt(raw, 10);
|
|
997
1547
|
if (Number.isNaN(value) || value < 0 || value.toString() !== raw.trim()) {
|
|
998
|
-
throw new CfInspectorError(
|
|
1548
|
+
throw new CfInspectorError(
|
|
1549
|
+
"INVALID_ARGUMENT",
|
|
1550
|
+
`Invalid ${label}: "${raw}" \u2014 expected a non-negative integer`
|
|
1551
|
+
);
|
|
999
1552
|
}
|
|
1000
1553
|
return value;
|
|
1001
1554
|
}
|
|
1002
1555
|
function targetIndexOption(targetIndex) {
|
|
1003
1556
|
return targetIndex === void 0 ? {} : { targetIndex };
|
|
1004
1557
|
}
|
|
1005
|
-
function
|
|
1558
|
+
function selectionOptions(targetIndex, workerIndex) {
|
|
1559
|
+
return {
|
|
1560
|
+
...targetIndexOption(targetIndex),
|
|
1561
|
+
...workerIndex === void 0 ? {} : { workerIndex }
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex, workerIndex) {
|
|
1006
1565
|
return {
|
|
1007
1566
|
kind: "cf",
|
|
1008
1567
|
region,
|
|
@@ -1011,42 +1570,13 @@ function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, t
|
|
|
1011
1570
|
space,
|
|
1012
1571
|
app,
|
|
1013
1572
|
tunnelTimeoutMs: tunnelTimeoutSec * 1e3,
|
|
1014
|
-
...
|
|
1573
|
+
...selectionOptions(targetIndex, workerIndex)
|
|
1015
1574
|
};
|
|
1016
1575
|
}
|
|
1017
1576
|
function optionalText(value) {
|
|
1018
1577
|
const trimmed = value?.trim();
|
|
1019
1578
|
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
1020
1579
|
}
|
|
1021
|
-
function currentCfOptions() {
|
|
1022
|
-
const command = process2.env["CF_DEBUGGER_CF_BIN"];
|
|
1023
|
-
return command === void 0 ? void 0 : { command };
|
|
1024
|
-
}
|
|
1025
|
-
async function readCurrentTarget() {
|
|
1026
|
-
try {
|
|
1027
|
-
return await readCurrentCfTarget(currentCfOptions());
|
|
1028
|
-
} catch (error) {
|
|
1029
|
-
throw new CfInspectorError(
|
|
1030
|
-
"MISSING_TARGET",
|
|
1031
|
-
"No current CF target found. Run `cf target -o <org> -s <space>` or pass --region/--org/--space.",
|
|
1032
|
-
error instanceof Error ? error.message : String(error)
|
|
1033
|
-
);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
function currentRegion(current) {
|
|
1037
|
-
try {
|
|
1038
|
-
return requireCurrentCfRegion(current, "Pass --region explicitly.");
|
|
1039
|
-
} catch (error) {
|
|
1040
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1041
|
-
throw new CfInspectorError("MISSING_TARGET", message);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
function missingTargetError() {
|
|
1045
|
-
return new CfInspectorError(
|
|
1046
|
-
"MISSING_TARGET",
|
|
1047
|
-
"Provide either --port (and optionally --host), an --app with current cf target, or all of --region, --org, --space, --app."
|
|
1048
|
-
);
|
|
1049
|
-
}
|
|
1050
1580
|
async function withSession(target, fn, reportProgress) {
|
|
1051
1581
|
const tunnel = await openTarget(target, reportProgress);
|
|
1052
1582
|
let session;
|
|
@@ -1057,8 +1587,13 @@ async function withSession(target, fn, reportProgress) {
|
|
|
1057
1587
|
session = await connectInspector({
|
|
1058
1588
|
port: tunnel.port,
|
|
1059
1589
|
host: tunnel.host,
|
|
1060
|
-
...
|
|
1590
|
+
...selectionOptions(target.targetIndex, target.workerIndex)
|
|
1061
1591
|
});
|
|
1592
|
+
warnOnImplicitInspectorSelection(
|
|
1593
|
+
session,
|
|
1594
|
+
target.targetIndex !== void 0,
|
|
1595
|
+
target.workerIndex !== void 0
|
|
1596
|
+
);
|
|
1062
1597
|
reportProgress?.("Inspector session is ready.");
|
|
1063
1598
|
return await fn(session, tunnel.port);
|
|
1064
1599
|
} finally {
|
|
@@ -1133,15 +1668,30 @@ async function resume(session) {
|
|
|
1133
1668
|
async function setPauseOnExceptions(session, state) {
|
|
1134
1669
|
await session.client.send("Debugger.setPauseOnExceptions", { state });
|
|
1135
1670
|
}
|
|
1136
|
-
async function evaluateOnFrame(session, callFrameId, expression) {
|
|
1671
|
+
async function evaluateOnFrame(session, callFrameId, expression, options = {}) {
|
|
1137
1672
|
return await session.client.send("Debugger.evaluateOnCallFrame", {
|
|
1138
1673
|
callFrameId,
|
|
1139
1674
|
expression,
|
|
1140
1675
|
returnByValue: false,
|
|
1141
1676
|
generatePreview: true,
|
|
1142
|
-
silent: true
|
|
1677
|
+
silent: true,
|
|
1678
|
+
...options.throwOnSideEffect === void 0 ? {} : { throwOnSideEffect: options.throwOnSideEffect }
|
|
1143
1679
|
});
|
|
1144
1680
|
}
|
|
1681
|
+
function isSideEffectRefusal(result) {
|
|
1682
|
+
const classNames = [
|
|
1683
|
+
result.result?.className,
|
|
1684
|
+
result.exceptionDetails?.exception?.className
|
|
1685
|
+
];
|
|
1686
|
+
const descriptions = [
|
|
1687
|
+
result.result?.description,
|
|
1688
|
+
result.exceptionDetails?.exception?.description
|
|
1689
|
+
];
|
|
1690
|
+
const isEvalError = classNames.includes("EvalError");
|
|
1691
|
+
return isEvalError && descriptions.some(
|
|
1692
|
+
(description) => typeof description === "string" && description.toLowerCase().includes("possible side-effect in debug-evaluate")
|
|
1693
|
+
);
|
|
1694
|
+
}
|
|
1145
1695
|
async function evaluateGlobal(session, expression) {
|
|
1146
1696
|
return await session.client.send("Runtime.evaluate", {
|
|
1147
1697
|
expression,
|
|
@@ -1194,6 +1744,7 @@ async function getProperties(session, objectId) {
|
|
|
1194
1744
|
|
|
1195
1745
|
// src/cli/commands/eval.ts
|
|
1196
1746
|
async function handleEval(opts) {
|
|
1747
|
+
warnOnMutationRisk(opts.expr, "eval --expr");
|
|
1197
1748
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
1198
1749
|
const result = await withSession(target, async (session) => {
|
|
1199
1750
|
return await evaluateGlobal(session, opts.expr);
|
|
@@ -1235,8 +1786,8 @@ function writeHumanEvalResult(result) {
|
|
|
1235
1786
|
}
|
|
1236
1787
|
|
|
1237
1788
|
// src/cli/commands/exception.ts
|
|
1238
|
-
import { performance as
|
|
1239
|
-
import
|
|
1789
|
+
import { performance as performance4 } from "perf_hooks";
|
|
1790
|
+
import process5 from "process";
|
|
1240
1791
|
|
|
1241
1792
|
// src/pathMapper.ts
|
|
1242
1793
|
init_types();
|
|
@@ -1448,7 +1999,7 @@ async function removeBreakpoint(session, breakpointId) {
|
|
|
1448
1999
|
|
|
1449
2000
|
// src/inspector/pause.ts
|
|
1450
2001
|
init_types();
|
|
1451
|
-
import { performance as
|
|
2002
|
+
import { performance as performance3 } from "perf_hooks";
|
|
1452
2003
|
function pauseMatches(pause, breakpointIds, pauseReasons) {
|
|
1453
2004
|
if (pauseReasons !== void 0 && pauseReasons.length > 0) {
|
|
1454
2005
|
return pauseReasons.includes(pause.reason);
|
|
@@ -1459,7 +2010,7 @@ function pauseMatches(pause, breakpointIds, pauseReasons) {
|
|
|
1459
2010
|
return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
|
|
1460
2011
|
}
|
|
1461
2012
|
function remainingUntil(deadlineMs) {
|
|
1462
|
-
return Math.max(0, deadlineMs -
|
|
2013
|
+
return Math.max(0, deadlineMs - performance3.now());
|
|
1463
2014
|
}
|
|
1464
2015
|
function hasResumedSincePause(session, pause) {
|
|
1465
2016
|
const pauseAt = pause.receivedAtMs;
|
|
@@ -1489,7 +2040,7 @@ async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeout
|
|
|
1489
2040
|
}
|
|
1490
2041
|
try {
|
|
1491
2042
|
await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
|
|
1492
|
-
session.debuggerState.lastResumedAtMs =
|
|
2043
|
+
session.debuggerState.lastResumedAtMs = performance3.now();
|
|
1493
2044
|
} catch (err) {
|
|
1494
2045
|
if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
|
|
1495
2046
|
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
@@ -1512,7 +2063,7 @@ async function handleUnmatchedPause(session, pause, options, deadlineMs) {
|
|
|
1512
2063
|
await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
|
|
1513
2064
|
}
|
|
1514
2065
|
async function waitForPause(session, options) {
|
|
1515
|
-
const deadlineMs =
|
|
2066
|
+
const deadlineMs = performance3.now() + options.timeoutMs;
|
|
1516
2067
|
const buffer = session.pauseBuffer;
|
|
1517
2068
|
while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
|
|
1518
2069
|
while (buffer.length > 0) {
|
|
@@ -1545,19 +2096,23 @@ async function waitForLivePause(session, options, deadlineMs) {
|
|
|
1545
2096
|
params = await session.client.waitFor("Debugger.paused", {
|
|
1546
2097
|
timeoutMs: remainingMs,
|
|
1547
2098
|
predicate: () => {
|
|
1548
|
-
receivedAtMs =
|
|
2099
|
+
receivedAtMs = performance3.now();
|
|
1549
2100
|
return true;
|
|
1550
2101
|
}
|
|
1551
2102
|
});
|
|
1552
2103
|
} finally {
|
|
1553
2104
|
session.pauseWaitGate.active = false;
|
|
1554
2105
|
}
|
|
1555
|
-
return toPauseEvent(params, receivedAtMs ??
|
|
2106
|
+
return toPauseEvent(params, receivedAtMs ?? performance3.now(), session.scripts);
|
|
1556
2107
|
}
|
|
1557
2108
|
|
|
2109
|
+
// src/snapshot/evaluation.ts
|
|
2110
|
+
init_types();
|
|
2111
|
+
|
|
1558
2112
|
// src/snapshot/values.ts
|
|
1559
2113
|
init_types();
|
|
1560
|
-
var DEFAULT_MAX_VALUE_LENGTH =
|
|
2114
|
+
var DEFAULT_MAX_VALUE_LENGTH = 131072;
|
|
2115
|
+
var DEFAULT_STREAM_MAX_VALUE_LENGTH = 4096;
|
|
1561
2116
|
function isPrimitive(value) {
|
|
1562
2117
|
const t = typeof value;
|
|
1563
2118
|
return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
|
|
@@ -1585,9 +2140,16 @@ function resolveMaxValueLength(value) {
|
|
|
1585
2140
|
}
|
|
1586
2141
|
function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
|
|
1587
2142
|
if (raw.length <= maxValueLength) {
|
|
1588
|
-
return raw;
|
|
2143
|
+
return { text: raw, truncated: false };
|
|
1589
2144
|
}
|
|
1590
|
-
return
|
|
2145
|
+
return {
|
|
2146
|
+
text: raw.slice(0, maxValueLength),
|
|
2147
|
+
truncated: true,
|
|
2148
|
+
originalLength: raw.length
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
function textTruncationFields(limited) {
|
|
2152
|
+
return limited.truncated ? { truncated: true, originalLength: limited.originalLength } : {};
|
|
1591
2153
|
}
|
|
1592
2154
|
function parseQuotedString(value) {
|
|
1593
2155
|
try {
|
|
@@ -1672,7 +2234,12 @@ function toStructuredValue(variable) {
|
|
|
1672
2234
|
// src/snapshot/evaluation.ts
|
|
1673
2235
|
function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
|
|
1674
2236
|
if (result.exceptionDetails !== void 0) {
|
|
1675
|
-
|
|
2237
|
+
const limited = readEvalError(result, maxValueLength);
|
|
2238
|
+
return {
|
|
2239
|
+
expression,
|
|
2240
|
+
error: limited.text,
|
|
2241
|
+
...textTruncationFields(limited)
|
|
2242
|
+
};
|
|
1676
2243
|
}
|
|
1677
2244
|
const inner = result.result;
|
|
1678
2245
|
if (!inner) {
|
|
@@ -1680,8 +2247,12 @@ function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_V
|
|
|
1680
2247
|
}
|
|
1681
2248
|
const type = typeof inner.type === "string" ? inner.type : void 0;
|
|
1682
2249
|
const buildCaptured = (rendered) => {
|
|
1683
|
-
const
|
|
1684
|
-
const base = {
|
|
2250
|
+
const limited = limitValueLength(rendered, maxValueLength);
|
|
2251
|
+
const base = {
|
|
2252
|
+
expression,
|
|
2253
|
+
value: limited.text,
|
|
2254
|
+
...textTruncationFields(limited)
|
|
2255
|
+
};
|
|
1685
2256
|
return type === void 0 ? base : { ...base, type };
|
|
1686
2257
|
};
|
|
1687
2258
|
if (type === "string" && typeof inner.value === "string") {
|
|
@@ -1698,6 +2269,18 @@ function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_V
|
|
|
1698
2269
|
}
|
|
1699
2270
|
return buildCaptured("undefined");
|
|
1700
2271
|
}
|
|
2272
|
+
function sideEffectRefusalToCaptured(expression) {
|
|
2273
|
+
const error = new CfInspectorError(
|
|
2274
|
+
"MUTATION_NOT_ALLOWED",
|
|
2275
|
+
`V8 blocked the capture expression "${expression}" because it may have side effects. Pass --allow-mutation to run it explicitly.`
|
|
2276
|
+
);
|
|
2277
|
+
return {
|
|
2278
|
+
expression,
|
|
2279
|
+
error: `${error.code}: ${error.message}`,
|
|
2280
|
+
mutationRisk: true,
|
|
2281
|
+
blocked: true
|
|
2282
|
+
};
|
|
2283
|
+
}
|
|
1701
2284
|
function readEvalError(result, maxValueLength) {
|
|
1702
2285
|
const text = typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
1703
2286
|
return limitValueLength(text, maxValueLength);
|
|
@@ -1755,56 +2338,88 @@ async function captureProperties(session, objectId, limit, depth, maxValueLength
|
|
|
1755
2338
|
return await captureProperty(session, prop, depth, maxValueLength);
|
|
1756
2339
|
})
|
|
1757
2340
|
);
|
|
1758
|
-
|
|
2341
|
+
const omittedCount = Math.max(properties.length - limited.length, 0);
|
|
2342
|
+
return omittedCount === 0 ? { variables } : { variables, omittedCount };
|
|
1759
2343
|
}
|
|
1760
2344
|
async function captureProperty(session, prop, depth, maxValueLength) {
|
|
1761
2345
|
const name = typeof prop.name === "string" ? prop.name : "?";
|
|
1762
2346
|
const described = describeProperty(prop);
|
|
1763
|
-
const
|
|
1764
|
-
|
|
1765
|
-
|
|
2347
|
+
const capturedChildren = await capturePropertyChildren(
|
|
2348
|
+
session,
|
|
2349
|
+
described,
|
|
2350
|
+
depth,
|
|
2351
|
+
maxValueLength
|
|
2352
|
+
);
|
|
2353
|
+
const limited = limitValueLength(described.value, maxValueLength);
|
|
2354
|
+
const base = {
|
|
2355
|
+
name,
|
|
2356
|
+
value: limited.text,
|
|
2357
|
+
...textTruncationFields(limited)
|
|
2358
|
+
};
|
|
1766
2359
|
const withType = described.type === void 0 ? base : { ...base, type: described.type };
|
|
1767
|
-
|
|
2360
|
+
const children = capturedChildren?.variables;
|
|
2361
|
+
const withChildren = children === void 0 || children.length === 0 ? withType : { ...withType, children };
|
|
2362
|
+
const omittedCount = capturedChildren?.omittedCount ?? 0;
|
|
2363
|
+
return omittedCount === 0 ? withChildren : { ...withChildren, truncated: true, omittedCount };
|
|
1768
2364
|
}
|
|
1769
2365
|
async function capturePropertyChildren(session, described, depth, maxValueLength) {
|
|
1770
|
-
if (
|
|
2366
|
+
if (described.objectId === void 0 || !isExpandable(described.type)) {
|
|
1771
2367
|
return void 0;
|
|
1772
2368
|
}
|
|
2369
|
+
if (depth <= 0) {
|
|
2370
|
+
return await countDepthOmissions(session, described.objectId);
|
|
2371
|
+
}
|
|
1773
2372
|
try {
|
|
1774
|
-
|
|
2373
|
+
return await captureProperties(
|
|
1775
2374
|
session,
|
|
1776
2375
|
described.objectId,
|
|
1777
2376
|
MAX_CHILD_VARIABLES,
|
|
1778
2377
|
depth - 1,
|
|
1779
2378
|
maxValueLength
|
|
1780
2379
|
);
|
|
1781
|
-
return nested.length > 0 ? nested : void 0;
|
|
1782
2380
|
} catch {
|
|
1783
2381
|
return void 0;
|
|
1784
2382
|
}
|
|
1785
2383
|
}
|
|
2384
|
+
async function countDepthOmissions(session, objectId) {
|
|
2385
|
+
try {
|
|
2386
|
+
const properties = await getProperties(session, objectId);
|
|
2387
|
+
return properties.length === 0 ? void 0 : { variables: [], omittedCount: properties.length };
|
|
2388
|
+
} catch {
|
|
2389
|
+
return void 0;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
function countPropertyOmissions(captured) {
|
|
2393
|
+
return (captured.omittedCount ?? 0) + captured.variables.reduce((total, variable) => {
|
|
2394
|
+
const childOmissions = variable.children === void 0 ? 0 : countPropertyOmissions({ variables: variable.children });
|
|
2395
|
+
return total + (variable.omittedCount ?? 0) + childOmissions;
|
|
2396
|
+
}, 0);
|
|
2397
|
+
}
|
|
1786
2398
|
|
|
1787
2399
|
// src/snapshot/exception.ts
|
|
1788
2400
|
function asString2(value) {
|
|
1789
2401
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1790
2402
|
}
|
|
1791
|
-
async function materializeObject(session, objectId
|
|
2403
|
+
async function materializeObject(session, objectId) {
|
|
1792
2404
|
try {
|
|
1793
|
-
const
|
|
2405
|
+
const captured = await captureProperties(
|
|
1794
2406
|
session,
|
|
1795
2407
|
objectId,
|
|
1796
2408
|
MAX_SCOPE_VARIABLES,
|
|
1797
2409
|
MAX_VARIABLE_DEPTH,
|
|
1798
|
-
|
|
2410
|
+
Number.MAX_SAFE_INTEGER
|
|
1799
2411
|
);
|
|
1800
|
-
if (
|
|
2412
|
+
if (captured.variables.length === 0) {
|
|
1801
2413
|
return void 0;
|
|
1802
2414
|
}
|
|
1803
2415
|
const structured = {};
|
|
1804
|
-
for (const variable of
|
|
2416
|
+
for (const variable of captured.variables) {
|
|
1805
2417
|
structured[variable.name] = toStructuredValue(variable);
|
|
1806
2418
|
}
|
|
1807
|
-
return
|
|
2419
|
+
return {
|
|
2420
|
+
value: JSON.stringify(structured),
|
|
2421
|
+
omittedCount: countPropertyOmissions(captured)
|
|
2422
|
+
};
|
|
1808
2423
|
} catch {
|
|
1809
2424
|
return void 0;
|
|
1810
2425
|
}
|
|
@@ -1857,18 +2472,44 @@ async function captureException(session, pause, maxValueLength) {
|
|
|
1857
2472
|
return { error: "exception data has no objectId or value" };
|
|
1858
2473
|
}
|
|
1859
2474
|
const message = await readPropertyDescription(session, objectId, "message");
|
|
1860
|
-
const rendered = await materializeObject(session, objectId
|
|
2475
|
+
const rendered = await materializeObject(session, objectId);
|
|
1861
2476
|
if (rendered !== void 0) {
|
|
1862
|
-
|
|
1863
|
-
|
|
2477
|
+
return buildResult(
|
|
2478
|
+
type,
|
|
2479
|
+
message ?? description,
|
|
2480
|
+
rendered.value,
|
|
2481
|
+
maxValueLength,
|
|
2482
|
+
rendered.omittedCount
|
|
2483
|
+
);
|
|
1864
2484
|
}
|
|
1865
2485
|
return buildResult(type, description, description ?? "[exception]", maxValueLength);
|
|
1866
2486
|
}
|
|
1867
|
-
function buildResult(type, description, value, maxValueLength) {
|
|
1868
|
-
const
|
|
1869
|
-
const
|
|
2487
|
+
function buildResult(type, description, value, maxValueLength, omittedCount = 0) {
|
|
2488
|
+
const limitedValue = limitValueLength(value, maxValueLength);
|
|
2489
|
+
const limitedDescription = description === void 0 ? void 0 : limitValueLength(description, maxValueLength);
|
|
2490
|
+
const base = {
|
|
2491
|
+
value: limitedValue.text,
|
|
2492
|
+
...exceptionTruncationFields(limitedValue, limitedDescription)
|
|
2493
|
+
};
|
|
1870
2494
|
const withType = type === void 0 ? base : { ...base, type };
|
|
1871
|
-
|
|
2495
|
+
const withDescription = limitedDescription === void 0 ? withType : { ...withType, description: limitedDescription.text };
|
|
2496
|
+
return omittedCount === 0 ? withDescription : { ...withDescription, truncated: true, omittedCount };
|
|
2497
|
+
}
|
|
2498
|
+
function exceptionTruncationFields(value, description) {
|
|
2499
|
+
const valueLength = value.truncated ? value.originalLength : void 0;
|
|
2500
|
+
const descriptionLength = description?.truncated === true ? description.originalLength : void 0;
|
|
2501
|
+
const lengths = [valueLength, descriptionLength].filter(
|
|
2502
|
+
(length) => length !== void 0
|
|
2503
|
+
);
|
|
2504
|
+
if (lengths.length === 0) {
|
|
2505
|
+
return {};
|
|
2506
|
+
}
|
|
2507
|
+
return {
|
|
2508
|
+
truncated: true,
|
|
2509
|
+
originalLength: Math.max(...lengths),
|
|
2510
|
+
...valueLength === void 0 ? {} : { valueOriginalLength: valueLength },
|
|
2511
|
+
...descriptionLength === void 0 ? {} : { descriptionOriginalLength: descriptionLength }
|
|
2512
|
+
};
|
|
1872
2513
|
}
|
|
1873
2514
|
|
|
1874
2515
|
// src/snapshot/objects.ts
|
|
@@ -1883,20 +2524,23 @@ function objectIdFromEvalResult(result) {
|
|
|
1883
2524
|
}
|
|
1884
2525
|
return objectId;
|
|
1885
2526
|
}
|
|
1886
|
-
async function renderObjectCapture(session, objectId
|
|
2527
|
+
async function renderObjectCapture(session, objectId) {
|
|
1887
2528
|
try {
|
|
1888
|
-
const
|
|
2529
|
+
const captured = await captureProperties(
|
|
1889
2530
|
session,
|
|
1890
2531
|
objectId,
|
|
1891
2532
|
MAX_SCOPE_VARIABLES,
|
|
1892
2533
|
MAX_VARIABLE_DEPTH,
|
|
1893
|
-
|
|
2534
|
+
Number.MAX_SAFE_INTEGER
|
|
1894
2535
|
);
|
|
1895
2536
|
const structured = {};
|
|
1896
|
-
for (const variable of
|
|
2537
|
+
for (const variable of captured.variables) {
|
|
1897
2538
|
structured[variable.name] = toStructuredValue(variable);
|
|
1898
2539
|
}
|
|
1899
|
-
return
|
|
2540
|
+
return {
|
|
2541
|
+
value: JSON.stringify(structured),
|
|
2542
|
+
omittedCount: countPropertyOmissions(captured)
|
|
2543
|
+
};
|
|
1900
2544
|
} catch {
|
|
1901
2545
|
return void 0;
|
|
1902
2546
|
}
|
|
@@ -1918,16 +2562,22 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
|
|
|
1918
2562
|
if (objectId === void 0) {
|
|
1919
2563
|
return captured;
|
|
1920
2564
|
}
|
|
1921
|
-
const rendered = await renderObjectCapture(session, objectId
|
|
2565
|
+
const rendered = await renderObjectCapture(session, objectId);
|
|
1922
2566
|
if (rendered === void 0) {
|
|
1923
2567
|
return captured;
|
|
1924
2568
|
}
|
|
1925
|
-
const normalized = normalizeRenderedObjectCapture(rendered, captured.value);
|
|
2569
|
+
const normalized = normalizeRenderedObjectCapture(rendered.value, captured.value);
|
|
1926
2570
|
if (normalized === void 0) {
|
|
1927
2571
|
return captured;
|
|
1928
2572
|
}
|
|
1929
|
-
const
|
|
1930
|
-
|
|
2573
|
+
const limited = limitValueLength(normalized, maxValueLength);
|
|
2574
|
+
const base = {
|
|
2575
|
+
expression,
|
|
2576
|
+
value: limited.text,
|
|
2577
|
+
...textTruncationFields(limited),
|
|
2578
|
+
...captured.type === void 0 ? {} : { type: captured.type }
|
|
2579
|
+
};
|
|
2580
|
+
return rendered.omittedCount === 0 ? base : { ...base, truncated: true, omittedCount: rendered.omittedCount };
|
|
1931
2581
|
}
|
|
1932
2582
|
|
|
1933
2583
|
// src/snapshot/scopes.ts
|
|
@@ -1942,35 +2592,39 @@ var PRIORITY_BY_TYPE = {
|
|
|
1942
2592
|
module: 6,
|
|
1943
2593
|
script: 7
|
|
1944
2594
|
};
|
|
1945
|
-
function
|
|
2595
|
+
function rankedScopes(scopeChain) {
|
|
1946
2596
|
const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
|
|
1947
|
-
return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type))
|
|
2597
|
+
return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type));
|
|
1948
2598
|
}
|
|
1949
2599
|
function priorityOf(type) {
|
|
1950
2600
|
return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
|
|
1951
2601
|
}
|
|
1952
2602
|
async function captureScopes(session, frame, maxValueLength) {
|
|
1953
|
-
const
|
|
1954
|
-
|
|
2603
|
+
const ranked = rankedScopes(frame.scopeChain);
|
|
2604
|
+
const scopes = ranked.slice(0, MAX_SCOPES);
|
|
2605
|
+
const capturedScopes = await Promise.all(
|
|
1955
2606
|
scopes.map(async (scope) => {
|
|
1956
2607
|
const objectId = scope.objectId;
|
|
1957
2608
|
if (objectId === void 0) {
|
|
1958
2609
|
return { type: scope.type, variables: [] };
|
|
1959
2610
|
}
|
|
1960
2611
|
try {
|
|
1961
|
-
const
|
|
2612
|
+
const captured = await captureProperties(
|
|
1962
2613
|
session,
|
|
1963
2614
|
objectId,
|
|
1964
2615
|
MAX_SCOPE_VARIABLES,
|
|
1965
2616
|
MAX_VARIABLE_DEPTH,
|
|
1966
2617
|
maxValueLength
|
|
1967
2618
|
);
|
|
1968
|
-
|
|
2619
|
+
const base = { type: scope.type, variables: captured.variables };
|
|
2620
|
+
return captured.omittedCount === void 0 ? base : { ...base, truncated: true, omittedCount: captured.omittedCount };
|
|
1969
2621
|
} catch {
|
|
1970
2622
|
return { type: scope.type, variables: [] };
|
|
1971
2623
|
}
|
|
1972
2624
|
})
|
|
1973
2625
|
);
|
|
2626
|
+
const omittedCount = Math.max(ranked.length - capturedScopes.length, 0);
|
|
2627
|
+
return omittedCount === 0 ? { scopes: capturedScopes } : { scopes: capturedScopes, omittedCount };
|
|
1974
2628
|
}
|
|
1975
2629
|
|
|
1976
2630
|
// src/snapshot/stack.ts
|
|
@@ -1990,23 +2644,48 @@ function buildBaseFrame(frame) {
|
|
|
1990
2644
|
};
|
|
1991
2645
|
return frame.url === void 0 ? base : { ...base, url: frame.url };
|
|
1992
2646
|
}
|
|
1993
|
-
async function captureFrameExpression(session, callFrameId, expression, maxValueLength) {
|
|
2647
|
+
async function captureFrameExpression(session, callFrameId, expression, maxValueLength, throwOnSideEffect) {
|
|
2648
|
+
const mutationRisk = throwOnSideEffect === false && looksLikeMutation(expression);
|
|
1994
2649
|
try {
|
|
1995
|
-
const result = await evaluateOnFrame(session, callFrameId, expression
|
|
2650
|
+
const result = await evaluateOnFrame(session, callFrameId, expression, {
|
|
2651
|
+
...throwOnSideEffect === void 0 ? {} : { throwOnSideEffect }
|
|
2652
|
+
});
|
|
2653
|
+
if (isSideEffectRefusal(result)) {
|
|
2654
|
+
return sideEffectRefusalToCaptured(expression);
|
|
2655
|
+
}
|
|
1996
2656
|
const captured = evalResultToCaptured(expression, result, maxValueLength);
|
|
1997
|
-
|
|
2657
|
+
const serialized = await withSerializedObjectCapture(
|
|
2658
|
+
session,
|
|
2659
|
+
expression,
|
|
2660
|
+
result,
|
|
2661
|
+
captured,
|
|
2662
|
+
maxValueLength
|
|
2663
|
+
);
|
|
2664
|
+
return mutationRisk ? { ...serialized, mutationRisk: true } : serialized;
|
|
1998
2665
|
} catch (err) {
|
|
1999
2666
|
const message = err instanceof Error ? err.message : String(err);
|
|
2000
|
-
|
|
2667
|
+
const limited = limitValueLength(message, maxValueLength);
|
|
2668
|
+
const captured = {
|
|
2669
|
+
expression,
|
|
2670
|
+
error: limited.text,
|
|
2671
|
+
...textTruncationFields(limited)
|
|
2672
|
+
};
|
|
2673
|
+
return mutationRisk ? { ...captured, mutationRisk: true } : captured;
|
|
2001
2674
|
}
|
|
2002
2675
|
}
|
|
2003
|
-
async function captureFrameExpressions(session, frame, expressions, maxValueLength) {
|
|
2676
|
+
async function captureFrameExpressions(session, frame, expressions, maxValueLength, throwOnSideEffect) {
|
|
2004
2677
|
if (expressions.length === 0) {
|
|
2005
2678
|
return [];
|
|
2006
2679
|
}
|
|
2007
2680
|
return await Promise.all(
|
|
2008
2681
|
expressions.map(
|
|
2009
|
-
(expression) => captureFrameExpression(
|
|
2682
|
+
(expression) => captureFrameExpression(
|
|
2683
|
+
session,
|
|
2684
|
+
frame.callFrameId,
|
|
2685
|
+
expression,
|
|
2686
|
+
maxValueLength,
|
|
2687
|
+
throwOnSideEffect
|
|
2688
|
+
)
|
|
2010
2689
|
)
|
|
2011
2690
|
);
|
|
2012
2691
|
}
|
|
@@ -2026,7 +2705,8 @@ async function walkStack(session, callFrames, options) {
|
|
|
2026
2705
|
session,
|
|
2027
2706
|
frame,
|
|
2028
2707
|
options.stackCaptures,
|
|
2029
|
-
options.maxValueLength
|
|
2708
|
+
options.maxValueLength,
|
|
2709
|
+
options.throwOnSideEffect
|
|
2030
2710
|
);
|
|
2031
2711
|
return { ...base, captures };
|
|
2032
2712
|
})
|
|
@@ -2048,14 +2728,25 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
2048
2728
|
column: top.columnNumber + 1
|
|
2049
2729
|
};
|
|
2050
2730
|
if (options.includeScopes === true) {
|
|
2051
|
-
const
|
|
2052
|
-
topFrame = {
|
|
2731
|
+
const capturedScopes = await captureScopes(session, top, maxValueLength);
|
|
2732
|
+
topFrame = {
|
|
2733
|
+
...topFrame,
|
|
2734
|
+
scopes: capturedScopes.scopes,
|
|
2735
|
+
...capturedScopes.omittedCount === void 0 ? {} : { truncated: true, omittedCount: capturedScopes.omittedCount }
|
|
2736
|
+
};
|
|
2053
2737
|
}
|
|
2054
|
-
captures = await captureExpressions(
|
|
2738
|
+
captures = await captureExpressions(
|
|
2739
|
+
session,
|
|
2740
|
+
top.callFrameId,
|
|
2741
|
+
options.captures,
|
|
2742
|
+
maxValueLength,
|
|
2743
|
+
options.throwOnSideEffect
|
|
2744
|
+
);
|
|
2055
2745
|
stack = await walkStack(session, pause.callFrames, {
|
|
2056
2746
|
stackDepth: options.stackDepth ?? DEFAULT_STACK_DEPTH,
|
|
2057
2747
|
stackCaptures: options.stackCaptures ?? [],
|
|
2058
|
-
maxValueLength
|
|
2748
|
+
maxValueLength,
|
|
2749
|
+
...options.throwOnSideEffect === void 0 ? {} : { throwOnSideEffect: options.throwOnSideEffect }
|
|
2059
2750
|
});
|
|
2060
2751
|
}
|
|
2061
2752
|
const exception = await captureException(session, pause, maxValueLength);
|
|
@@ -2078,159 +2769,62 @@ function buildResult2(input) {
|
|
|
2078
2769
|
const withStack = input.stack.length > 0 ? { ...withFrame, stack: input.stack } : withFrame;
|
|
2079
2770
|
return input.exception === void 0 ? withStack : { ...withStack, exception: input.exception };
|
|
2080
2771
|
}
|
|
2081
|
-
async function captureExpressions(session, callFrameId, captures, maxValueLength) {
|
|
2772
|
+
async function captureExpressions(session, callFrameId, captures, maxValueLength, throwOnSideEffect) {
|
|
2082
2773
|
if (captures === void 0 || captures.length === 0) {
|
|
2083
2774
|
return [];
|
|
2084
2775
|
}
|
|
2085
2776
|
return await Promise.all(
|
|
2086
2777
|
captures.map(async (expression) => {
|
|
2087
|
-
return await captureExpression(
|
|
2778
|
+
return await captureExpression(
|
|
2779
|
+
session,
|
|
2780
|
+
callFrameId,
|
|
2781
|
+
expression,
|
|
2782
|
+
maxValueLength,
|
|
2783
|
+
throwOnSideEffect
|
|
2784
|
+
);
|
|
2088
2785
|
})
|
|
2089
2786
|
);
|
|
2090
2787
|
}
|
|
2091
|
-
async function captureExpression(session, callFrameId, expression, maxValueLength) {
|
|
2788
|
+
async function captureExpression(session, callFrameId, expression, maxValueLength, throwOnSideEffect) {
|
|
2789
|
+
const mutationRisk = throwOnSideEffect === false && looksLikeMutation(expression);
|
|
2092
2790
|
try {
|
|
2093
|
-
const result = await evaluateOnFrame(session, callFrameId, expression
|
|
2791
|
+
const result = await evaluateOnFrame(session, callFrameId, expression, {
|
|
2792
|
+
...throwOnSideEffect === void 0 ? {} : { throwOnSideEffect }
|
|
2793
|
+
});
|
|
2794
|
+
if (isSideEffectRefusal(result)) {
|
|
2795
|
+
return sideEffectRefusalToCaptured(expression);
|
|
2796
|
+
}
|
|
2094
2797
|
const captured = evalResultToCaptured(expression, result, maxValueLength);
|
|
2095
|
-
|
|
2798
|
+
const serialized = await withSerializedObjectCapture(
|
|
2799
|
+
session,
|
|
2800
|
+
expression,
|
|
2801
|
+
result,
|
|
2802
|
+
captured,
|
|
2803
|
+
maxValueLength
|
|
2804
|
+
);
|
|
2805
|
+
return mutationRisk ? { ...serialized, mutationRisk: true } : serialized;
|
|
2096
2806
|
} catch (err) {
|
|
2097
2807
|
const message = err instanceof Error ? err.message : String(err);
|
|
2098
|
-
|
|
2808
|
+
const limited = limitValueLength(message, maxValueLength);
|
|
2809
|
+
const captured = {
|
|
2810
|
+
expression,
|
|
2811
|
+
error: limited.text,
|
|
2812
|
+
...textTruncationFields(limited)
|
|
2813
|
+
};
|
|
2814
|
+
return mutationRisk ? { ...captured, mutationRisk: true } : captured;
|
|
2099
2815
|
}
|
|
2100
2816
|
}
|
|
2101
2817
|
|
|
2102
2818
|
// src/cli/commands/exception.ts
|
|
2103
2819
|
init_types();
|
|
2104
|
-
|
|
2105
|
-
// src/cli/captureParser.ts
|
|
2106
|
-
function parseCaptureList(raw) {
|
|
2107
|
-
if (raw === void 0 || raw.trim().length === 0) {
|
|
2108
|
-
return [];
|
|
2109
|
-
}
|
|
2110
|
-
return splitCaptureExpressions(raw);
|
|
2111
|
-
}
|
|
2112
|
-
function isQuoteChar(value) {
|
|
2113
|
-
return value === "'" || value === '"' || value === "`";
|
|
2114
|
-
}
|
|
2115
|
-
function consumeQuotedChar(state, char) {
|
|
2116
|
-
if (state.quote === void 0) {
|
|
2117
|
-
return false;
|
|
2118
|
-
}
|
|
2119
|
-
if (state.escaped) {
|
|
2120
|
-
state.escaped = false;
|
|
2121
|
-
return true;
|
|
2122
|
-
}
|
|
2123
|
-
if (char === "\\") {
|
|
2124
|
-
state.escaped = true;
|
|
2125
|
-
return true;
|
|
2126
|
-
}
|
|
2127
|
-
if (char === state.quote) {
|
|
2128
|
-
state.quote = void 0;
|
|
2129
|
-
}
|
|
2130
|
-
return true;
|
|
2131
|
-
}
|
|
2132
|
-
function updateCaptureDepth(state, char) {
|
|
2133
|
-
if (char === "(") {
|
|
2134
|
-
state.parenDepth += 1;
|
|
2135
|
-
} else if (char === ")") {
|
|
2136
|
-
state.parenDepth = Math.max(0, state.parenDepth - 1);
|
|
2137
|
-
} else if (char === "[") {
|
|
2138
|
-
state.bracketDepth += 1;
|
|
2139
|
-
} else if (char === "]") {
|
|
2140
|
-
state.bracketDepth = Math.max(0, state.bracketDepth - 1);
|
|
2141
|
-
} else if (char === "{") {
|
|
2142
|
-
state.braceDepth += 1;
|
|
2143
|
-
} else if (char === "}") {
|
|
2144
|
-
state.braceDepth = Math.max(0, state.braceDepth - 1);
|
|
2145
|
-
}
|
|
2146
|
-
}
|
|
2147
|
-
function isTopLevel(state) {
|
|
2148
|
-
return state.parenDepth === 0 && state.bracketDepth === 0 && state.braceDepth === 0;
|
|
2149
|
-
}
|
|
2150
|
-
function appendCapturePiece(raw, state, end) {
|
|
2151
|
-
const piece = raw.slice(state.start, end).trim();
|
|
2152
|
-
if (piece.length > 0) {
|
|
2153
|
-
state.pieces.push(piece);
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
function splitCaptureExpressions(raw) {
|
|
2157
|
-
const state = {
|
|
2158
|
-
escaped: false,
|
|
2159
|
-
parenDepth: 0,
|
|
2160
|
-
bracketDepth: 0,
|
|
2161
|
-
braceDepth: 0,
|
|
2162
|
-
quote: void 0,
|
|
2163
|
-
start: 0,
|
|
2164
|
-
pieces: []
|
|
2165
|
-
};
|
|
2166
|
-
for (let idx = 0; idx < raw.length; idx += 1) {
|
|
2167
|
-
const char = raw.charAt(idx);
|
|
2168
|
-
if (consumeQuotedChar(state, char)) {
|
|
2169
|
-
continue;
|
|
2170
|
-
}
|
|
2171
|
-
if (isQuoteChar(char)) {
|
|
2172
|
-
state.quote = char;
|
|
2173
|
-
continue;
|
|
2174
|
-
}
|
|
2175
|
-
updateCaptureDepth(state, char);
|
|
2176
|
-
if (char === "," && isTopLevel(state)) {
|
|
2177
|
-
appendCapturePiece(raw, state, idx);
|
|
2178
|
-
state.start = idx + 1;
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
appendCapturePiece(raw, state, raw.length);
|
|
2182
|
-
return state.pieces;
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
// src/cli/warnings.ts
|
|
2186
|
-
import process5 from "process";
|
|
2187
|
-
function warnOnUnboundBreakpoints(handles) {
|
|
2188
|
-
for (const handle of handles) {
|
|
2189
|
-
if (handle.resolvedLocations.length === 0) {
|
|
2190
|
-
const tsHint = handle.file.endsWith(".ts") ? " Hint: Source TS breakpoints may not bind. Try inspecting loaded scripts with list-scripts and target the compiled .js file instead." : "";
|
|
2191
|
-
process5.stderr.write(
|
|
2192
|
-
`[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.${tsHint}
|
|
2193
|
-
`
|
|
2194
|
-
);
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
function roundDurationMs(durationMs) {
|
|
2199
|
-
return Math.round(durationMs * 1e3) / 1e3;
|
|
2200
|
-
}
|
|
2201
|
-
function warnOnUnmatchedPause(pause) {
|
|
2202
|
-
const reason = pause.reason.length > 0 ? pause.reason : "unknown";
|
|
2203
|
-
process5.stderr.write(
|
|
2204
|
-
`[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
|
|
2205
|
-
`
|
|
2206
|
-
);
|
|
2207
|
-
}
|
|
2208
|
-
function withPausedDuration(snapshot, pausedDurationMs) {
|
|
2209
|
-
const base = {
|
|
2210
|
-
reason: snapshot.reason,
|
|
2211
|
-
hitBreakpoints: snapshot.hitBreakpoints,
|
|
2212
|
-
capturedAt: snapshot.capturedAt,
|
|
2213
|
-
pausedDurationMs,
|
|
2214
|
-
captures: snapshot.captures
|
|
2215
|
-
};
|
|
2216
|
-
const withFrame = snapshot.topFrame === void 0 ? base : { ...base, topFrame: snapshot.topFrame };
|
|
2217
|
-
const withStack = snapshot.stack === void 0 ? withFrame : { ...withFrame, stack: snapshot.stack };
|
|
2218
|
-
return snapshot.exception === void 0 ? withStack : { ...withStack, exception: snapshot.exception };
|
|
2219
|
-
}
|
|
2220
|
-
function formatPauseLocation(pause) {
|
|
2221
|
-
const top = pause.callFrames[0];
|
|
2222
|
-
if (top === void 0) {
|
|
2223
|
-
return "(no call frame)";
|
|
2224
|
-
}
|
|
2225
|
-
const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
|
|
2226
|
-
return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
// src/cli/commands/exception.ts
|
|
2230
2820
|
var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
|
|
2231
2821
|
async function handleException(opts) {
|
|
2232
2822
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2233
2823
|
const prepared = prepareExceptionCommand(opts, target);
|
|
2824
|
+
warnOnCaptureMutationRisk(
|
|
2825
|
+
[...prepared.captures, ...prepared.stackCaptures],
|
|
2826
|
+
opts.allowMutation === true
|
|
2827
|
+
);
|
|
2234
2828
|
const result = await runExceptionCommand(prepared, opts);
|
|
2235
2829
|
if (opts.json) {
|
|
2236
2830
|
writeJson(result);
|
|
@@ -2247,7 +2841,7 @@ function prepareExceptionCommand(opts, target) {
|
|
|
2247
2841
|
);
|
|
2248
2842
|
}
|
|
2249
2843
|
const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_EXCEPTION_TIMEOUT_SEC;
|
|
2250
|
-
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
|
|
2844
|
+
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length") ?? DEFAULT_MAX_VALUE_LENGTH;
|
|
2251
2845
|
const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
|
|
2252
2846
|
return {
|
|
2253
2847
|
target,
|
|
@@ -2255,9 +2849,10 @@ function prepareExceptionCommand(opts, target) {
|
|
|
2255
2849
|
captures: parseCaptureList(opts.capture),
|
|
2256
2850
|
remoteRoot: parseRemoteRoot(opts.remoteRoot),
|
|
2257
2851
|
timeoutMs: timeoutSec * 1e3,
|
|
2258
|
-
|
|
2852
|
+
maxValueLength,
|
|
2259
2853
|
...stackDepth === void 0 ? {} : { stackDepth },
|
|
2260
|
-
stackCaptures: parseCaptureList(opts.stackCaptures)
|
|
2854
|
+
stackCaptures: parseCaptureList(opts.stackCaptures),
|
|
2855
|
+
throwOnSideEffect: opts.allowMutation !== true
|
|
2261
2856
|
};
|
|
2262
2857
|
}
|
|
2263
2858
|
async function runExceptionCommand(command, opts) {
|
|
@@ -2269,13 +2864,14 @@ async function runExceptionCommand(command, opts) {
|
|
|
2269
2864
|
pauseReasons: ["exception", "promiseRejection"],
|
|
2270
2865
|
unmatchedPausePolicy: "wait-for-resume"
|
|
2271
2866
|
});
|
|
2272
|
-
const pausedStartedAt = pause.receivedAtMs ??
|
|
2867
|
+
const pausedStartedAt = pause.receivedAtMs ?? performance4.now();
|
|
2273
2868
|
const snapshot = await captureSnapshot(session, pause, {
|
|
2274
2869
|
captures: command.captures,
|
|
2275
2870
|
includeScopes: opts.includeScopes === true,
|
|
2276
|
-
|
|
2871
|
+
maxValueLength: command.maxValueLength,
|
|
2277
2872
|
...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
|
|
2278
|
-
stackCaptures: command.stackCaptures
|
|
2873
|
+
stackCaptures: command.stackCaptures,
|
|
2874
|
+
throwOnSideEffect: command.throwOnSideEffect
|
|
2279
2875
|
});
|
|
2280
2876
|
if (opts.keepPaused === true) {
|
|
2281
2877
|
return withPausedDuration(snapshot, null);
|
|
@@ -2289,9 +2885,9 @@ async function runExceptionCommand(command, opts) {
|
|
|
2289
2885
|
async function resumeAfterException(session, snapshot, pausedStartedAt) {
|
|
2290
2886
|
try {
|
|
2291
2887
|
await resume(session);
|
|
2292
|
-
return withPausedDuration(snapshot, roundDurationMs(
|
|
2888
|
+
return withPausedDuration(snapshot, roundDurationMs(performance4.now() - pausedStartedAt));
|
|
2293
2889
|
} catch {
|
|
2294
|
-
|
|
2890
|
+
process5.stderr.write(
|
|
2295
2891
|
"[cf-inspector] warning: Debugger.resume failed after exception capture; pausedDurationMs is unknown.\n"
|
|
2296
2892
|
);
|
|
2297
2893
|
return withPausedDuration(snapshot, null);
|
|
@@ -2305,7 +2901,7 @@ async function disablePauseOnExceptionsBestEffort(session) {
|
|
|
2305
2901
|
}
|
|
2306
2902
|
|
|
2307
2903
|
// src/cli/commands/listScripts.ts
|
|
2308
|
-
import
|
|
2904
|
+
import process6 from "process";
|
|
2309
2905
|
async function handleListScripts(opts) {
|
|
2310
2906
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2311
2907
|
const filter = compileScriptUrlFilter(opts.filter);
|
|
@@ -2315,7 +2911,7 @@ async function handleListScripts(opts) {
|
|
|
2315
2911
|
return;
|
|
2316
2912
|
}
|
|
2317
2913
|
for (const script of scripts) {
|
|
2318
|
-
|
|
2914
|
+
process6.stdout.write(`${script.scriptId} ${script.url}
|
|
2319
2915
|
`);
|
|
2320
2916
|
}
|
|
2321
2917
|
}
|
|
@@ -2324,19 +2920,86 @@ async function handleListTargets(opts) {
|
|
|
2324
2920
|
const tunnel = await openTarget(target);
|
|
2325
2921
|
try {
|
|
2326
2922
|
const targets = await discoverInspectorTargets(tunnel.host, tunnel.port, 5e3);
|
|
2327
|
-
const indexedTargets =
|
|
2923
|
+
const indexedTargets = await buildListedTargets(targets);
|
|
2924
|
+
const workerCount = indexedTargets.reduce((count, targetEntry) => {
|
|
2925
|
+
return count + targetEntry.workers.length;
|
|
2926
|
+
}, 0);
|
|
2927
|
+
writeTargetCountSummary(indexedTargets.length, workerCount);
|
|
2928
|
+
warnOnMissingWorkers(indexedTargets.length, workerCount, indexedTargets);
|
|
2328
2929
|
if (opts.json) {
|
|
2329
2930
|
writeJson(indexedTargets);
|
|
2330
2931
|
return;
|
|
2331
2932
|
}
|
|
2332
|
-
|
|
2333
|
-
process7.stdout.write(`${entry.index.toString()} ${entry.type} ${entry.title} ${entry.url}
|
|
2334
|
-
`);
|
|
2335
|
-
}
|
|
2933
|
+
writeHumanTargets(indexedTargets);
|
|
2336
2934
|
} finally {
|
|
2337
2935
|
await tunnel.dispose();
|
|
2338
2936
|
}
|
|
2339
2937
|
}
|
|
2938
|
+
async function buildListedTargets(targets) {
|
|
2939
|
+
return await Promise.all(targets.map(async (target, index) => {
|
|
2940
|
+
try {
|
|
2941
|
+
const workerResult = await discoverNodeWorkerTargets(target);
|
|
2942
|
+
return buildListedTarget(target, index, workerResult.supported, workerResult.workers);
|
|
2943
|
+
} catch (error) {
|
|
2944
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2945
|
+
process6.stderr.write(
|
|
2946
|
+
`[cf-inspector] warning: worker discovery failed for raw target ${index.toString()}: ${message}
|
|
2947
|
+
`
|
|
2948
|
+
);
|
|
2949
|
+
return buildListedTarget(target, index, false, []);
|
|
2950
|
+
}
|
|
2951
|
+
}));
|
|
2952
|
+
}
|
|
2953
|
+
function buildListedTarget(target, index, workerDiscoverySupported, workers) {
|
|
2954
|
+
return {
|
|
2955
|
+
index,
|
|
2956
|
+
...target,
|
|
2957
|
+
likelyWorker: looksLikeWorkerTarget(target),
|
|
2958
|
+
workerDiscoverySupported,
|
|
2959
|
+
workers: workers.map((worker, workerIndex) => ({
|
|
2960
|
+
index: workerIndex,
|
|
2961
|
+
workerId: worker.workerId,
|
|
2962
|
+
type: worker.type,
|
|
2963
|
+
title: worker.title,
|
|
2964
|
+
url: worker.url
|
|
2965
|
+
}))
|
|
2966
|
+
};
|
|
2967
|
+
}
|
|
2968
|
+
function looksLikeWorkerTarget(target) {
|
|
2969
|
+
return `${target.type} ${target.title} ${target.url}`.toLowerCase().includes("worker");
|
|
2970
|
+
}
|
|
2971
|
+
function writeTargetCountSummary(targetCount, workerCount) {
|
|
2972
|
+
process6.stderr.write(
|
|
2973
|
+
`[cf-inspector] ${targetCount.toString()} raw inspector ${targetCount === 1 ? "target" : "targets"}; ${workerCount.toString()} ${workerCount === 1 ? "worker" : "workers"}.
|
|
2974
|
+
`
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
function warnOnMissingWorkers(targetCount, workerCount, targets) {
|
|
2978
|
+
if (targetCount !== 1 || workerCount !== 0) {
|
|
2979
|
+
return;
|
|
2980
|
+
}
|
|
2981
|
+
const supported = targets[0]?.workerDiscoverySupported === true;
|
|
2982
|
+
const supportHint = supported ? "NodeWorker discovery is available, but no live worker attached." : "This runtime did not expose NodeWorker discovery.";
|
|
2983
|
+
process6.stderr.write(
|
|
2984
|
+
`[cf-inspector] warning: only the main inspector target is reachable. ${supportHint} If worker code is expected, ensure the worker is alive and rerun list-targets. A worker on a separate inspector port is not carried by a single Cloud Foundry tunnel.
|
|
2985
|
+
`
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
function writeHumanTargets(targets) {
|
|
2989
|
+
for (const target of targets) {
|
|
2990
|
+
const workerLabel = target.likelyWorker ? " likely-worker" : "";
|
|
2991
|
+
process6.stdout.write(
|
|
2992
|
+
`${target.index.toString()} target ${target.type} ${target.title} ${target.url}${workerLabel}
|
|
2993
|
+
`
|
|
2994
|
+
);
|
|
2995
|
+
for (const worker of target.workers) {
|
|
2996
|
+
process6.stdout.write(
|
|
2997
|
+
` ${worker.index.toString()} worker ${worker.type} ${worker.title} ${worker.url}
|
|
2998
|
+
`
|
|
2999
|
+
);
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
2340
3003
|
function compileScriptUrlFilter(pattern) {
|
|
2341
3004
|
if (pattern === void 0 || pattern.length === 0) {
|
|
2342
3005
|
return void 0;
|
|
@@ -2407,7 +3070,7 @@ function matchesFilterTokens(value, tokens) {
|
|
|
2407
3070
|
}
|
|
2408
3071
|
|
|
2409
3072
|
// src/cli/commands/log.ts
|
|
2410
|
-
import
|
|
3073
|
+
import process8 from "process";
|
|
2411
3074
|
|
|
2412
3075
|
// src/logpoint/stream.ts
|
|
2413
3076
|
init_types();
|
|
@@ -2493,7 +3156,7 @@ function readArg(arg, index) {
|
|
|
2493
3156
|
}
|
|
2494
3157
|
return index === 0 ? void 0 : "";
|
|
2495
3158
|
}
|
|
2496
|
-
function parseLogEvent(rawArgs, sentinel, location, timestamp) {
|
|
3159
|
+
function parseLogEvent(rawArgs, sentinel, location, timestamp, maxValueLength = DEFAULT_STREAM_MAX_VALUE_LENGTH) {
|
|
2497
3160
|
if (!Array.isArray(rawArgs) || rawArgs.length < 2) {
|
|
2498
3161
|
return void 0;
|
|
2499
3162
|
}
|
|
@@ -2505,21 +3168,37 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
|
|
|
2505
3168
|
const ts = new Date(typeof timestamp === "number" ? timestamp : Date.now()).toISOString();
|
|
2506
3169
|
const at = `${location.file}:${location.line.toString()}`;
|
|
2507
3170
|
if (payload.startsWith("!err:")) {
|
|
2508
|
-
|
|
3171
|
+
const limited = limitValueLength(payload.slice("!err:".length), maxValueLength);
|
|
3172
|
+
return {
|
|
3173
|
+
ts,
|
|
3174
|
+
at,
|
|
3175
|
+
error: limited.text,
|
|
3176
|
+
...textTruncationFields(limited)
|
|
3177
|
+
};
|
|
2509
3178
|
}
|
|
2510
|
-
return parsePayload(ts, at, payload);
|
|
3179
|
+
return parsePayload(ts, at, payload, maxValueLength);
|
|
2511
3180
|
}
|
|
2512
|
-
function parsePayload(ts, at, payload) {
|
|
3181
|
+
function parsePayload(ts, at, payload, maxValueLength) {
|
|
2513
3182
|
try {
|
|
2514
3183
|
const parsed = JSON.parse(payload);
|
|
2515
3184
|
if (typeof parsed === "string") {
|
|
2516
|
-
return
|
|
3185
|
+
return buildValueEvent(ts, at, parsed, maxValueLength);
|
|
2517
3186
|
}
|
|
2518
|
-
return
|
|
3187
|
+
return buildValueEvent(ts, at, JSON.stringify(parsed), maxValueLength);
|
|
2519
3188
|
} catch {
|
|
2520
|
-
return
|
|
3189
|
+
return buildValueEvent(ts, at, payload, maxValueLength, true);
|
|
2521
3190
|
}
|
|
2522
3191
|
}
|
|
3192
|
+
function buildValueEvent(ts, at, value, maxValueLength, includeRaw = false) {
|
|
3193
|
+
const limited = limitValueLength(value, maxValueLength);
|
|
3194
|
+
return {
|
|
3195
|
+
ts,
|
|
3196
|
+
at,
|
|
3197
|
+
value: limited.text,
|
|
3198
|
+
...includeRaw ? { raw: limited.text } : {},
|
|
3199
|
+
...textTruncationFields(limited)
|
|
3200
|
+
};
|
|
3201
|
+
}
|
|
2523
3202
|
|
|
2524
3203
|
// src/logpoint/stream.ts
|
|
2525
3204
|
function validateMaxEvents(maxEvents) {
|
|
@@ -2549,6 +3228,9 @@ function validateHitCount2(hitCount) {
|
|
|
2549
3228
|
async function streamLogpoint(session, options) {
|
|
2550
3229
|
const maxEvents = validateMaxEvents(options.maxEvents);
|
|
2551
3230
|
const hitCount = validateHitCount2(options.hitCount);
|
|
3231
|
+
const maxValueLength = resolveMaxValueLength(
|
|
3232
|
+
options.maxValueLength ?? DEFAULT_STREAM_MAX_VALUE_LENGTH
|
|
3233
|
+
);
|
|
2552
3234
|
const sentinel = generateSentinel();
|
|
2553
3235
|
const condition = buildLogpointCondition(sentinel, options.expression, {
|
|
2554
3236
|
...options.condition === void 0 ? {} : { predicate: options.condition },
|
|
@@ -2561,7 +3243,7 @@ async function streamLogpoint(session, options) {
|
|
|
2561
3243
|
if (maxEventsReached) {
|
|
2562
3244
|
return;
|
|
2563
3245
|
}
|
|
2564
|
-
const event = toLogpointEvent(raw, sentinel, options.location);
|
|
3246
|
+
const event = toLogpointEvent(raw, sentinel, options.location, maxValueLength);
|
|
2565
3247
|
if (event === void 0) {
|
|
2566
3248
|
return;
|
|
2567
3249
|
}
|
|
@@ -2601,13 +3283,13 @@ async function streamLogpoint(session, options) {
|
|
|
2601
3283
|
await removeBreakpointBestEffort(session, handle.breakpointId);
|
|
2602
3284
|
}
|
|
2603
3285
|
}
|
|
2604
|
-
function toLogpointEvent(raw, sentinel, location) {
|
|
3286
|
+
function toLogpointEvent(raw, sentinel, location, maxValueLength) {
|
|
2605
3287
|
const params = raw;
|
|
2606
3288
|
if (asString3(params.type) !== "log") {
|
|
2607
3289
|
return void 0;
|
|
2608
3290
|
}
|
|
2609
3291
|
const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
|
|
2610
|
-
return parseLogEvent(params.args, sentinel, location, ts);
|
|
3292
|
+
return parseLogEvent(params.args, sentinel, location, ts, maxValueLength);
|
|
2611
3293
|
}
|
|
2612
3294
|
async function removeBreakpointBestEffort(session, breakpointId) {
|
|
2613
3295
|
try {
|
|
@@ -2656,19 +3338,19 @@ async function waitForStop(session, options, registerMaxEventsSignal) {
|
|
|
2656
3338
|
init_types();
|
|
2657
3339
|
|
|
2658
3340
|
// src/cli/signals.ts
|
|
2659
|
-
import
|
|
3341
|
+
import process7 from "process";
|
|
2660
3342
|
async function withTerminationSignal(fn) {
|
|
2661
3343
|
const abort = new AbortController();
|
|
2662
3344
|
const onSignal = () => {
|
|
2663
3345
|
abort.abort();
|
|
2664
3346
|
};
|
|
2665
|
-
|
|
2666
|
-
|
|
3347
|
+
process7.once("SIGINT", onSignal);
|
|
3348
|
+
process7.once("SIGTERM", onSignal);
|
|
2667
3349
|
try {
|
|
2668
3350
|
return await fn(abort.signal);
|
|
2669
3351
|
} finally {
|
|
2670
|
-
|
|
2671
|
-
|
|
3352
|
+
process7.off("SIGINT", onSignal);
|
|
3353
|
+
process7.off("SIGTERM", onSignal);
|
|
2672
3354
|
}
|
|
2673
3355
|
}
|
|
2674
3356
|
|
|
@@ -2680,11 +3362,16 @@ async function handleLog(opts) {
|
|
|
2680
3362
|
const durationSec = parsePositiveInt(opts.duration, "--duration");
|
|
2681
3363
|
const maxEvents = parsePositiveInt(opts.maxEvents, "--max-events");
|
|
2682
3364
|
const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
|
|
3365
|
+
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length") ?? DEFAULT_STREAM_MAX_VALUE_LENGTH;
|
|
2683
3366
|
const expression = opts.expr.trim();
|
|
2684
3367
|
if (expression.length === 0) {
|
|
2685
3368
|
throw new CfInspectorError("INVALID_EXPRESSION", "--expr must not be empty");
|
|
2686
3369
|
}
|
|
2687
3370
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
3371
|
+
warnOnMutationRisk(expression, "log --expr");
|
|
3372
|
+
if (condition !== void 0) {
|
|
3373
|
+
warnOnMutationRisk(condition, "log --condition");
|
|
3374
|
+
}
|
|
2688
3375
|
await withTerminationSignal(async (signal) => {
|
|
2689
3376
|
await withSession(target, async (session) => {
|
|
2690
3377
|
await validateExpression(session, expression);
|
|
@@ -2699,6 +3386,7 @@ async function handleLog(opts) {
|
|
|
2699
3386
|
...maxEvents === void 0 ? {} : { maxEvents },
|
|
2700
3387
|
...hitCount === void 0 ? {} : { hitCount },
|
|
2701
3388
|
...condition === void 0 ? {} : { condition },
|
|
3389
|
+
maxValueLength,
|
|
2702
3390
|
signal,
|
|
2703
3391
|
onEvent: (event) => {
|
|
2704
3392
|
writeLogEvent(event, opts.json);
|
|
@@ -2707,29 +3395,39 @@ async function handleLog(opts) {
|
|
|
2707
3395
|
warnOnUnboundBreakpoints([handle]);
|
|
2708
3396
|
}
|
|
2709
3397
|
});
|
|
3398
|
+
if (result.emitted === 0 && (result.stoppedReason === "duration" || result.stoppedReason === "signal")) {
|
|
3399
|
+
warnOnBoundBreakpointWithoutHit([result.handle]);
|
|
3400
|
+
}
|
|
2710
3401
|
writeLogSummary(result.stoppedReason, result.emitted, opts.json);
|
|
2711
3402
|
});
|
|
2712
3403
|
});
|
|
2713
3404
|
}
|
|
2714
3405
|
function writeLogSummary(stoppedReason, emitted, json) {
|
|
2715
3406
|
if (json) {
|
|
2716
|
-
|
|
3407
|
+
process8.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
|
|
2717
3408
|
`);
|
|
2718
3409
|
return;
|
|
2719
3410
|
}
|
|
2720
|
-
|
|
3411
|
+
process8.stderr.write(
|
|
2721
3412
|
`Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
|
|
2722
3413
|
`
|
|
2723
3414
|
);
|
|
2724
3415
|
}
|
|
2725
3416
|
|
|
2726
3417
|
// src/cli/commands/snapshot.ts
|
|
2727
|
-
import { performance as
|
|
2728
|
-
import
|
|
3418
|
+
import { performance as performance5 } from "perf_hooks";
|
|
3419
|
+
import process9 from "process";
|
|
2729
3420
|
init_types();
|
|
2730
3421
|
async function handleSnapshot(opts) {
|
|
2731
3422
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2732
3423
|
const prepared = prepareSnapshotCommand(opts, target);
|
|
3424
|
+
warnOnCaptureMutationRisk(
|
|
3425
|
+
[...prepared.captures, ...prepared.stackCaptures],
|
|
3426
|
+
opts.allowMutation === true
|
|
3427
|
+
);
|
|
3428
|
+
for (const expression of prepared.setupEvals) {
|
|
3429
|
+
warnOnMutationRisk(expression, "snapshot --setup-eval");
|
|
3430
|
+
}
|
|
2733
3431
|
const reportProgress = opts.quiet === true ? void 0 : writeProgress;
|
|
2734
3432
|
const result = await runSnapshotCommand(prepared, opts, reportProgress);
|
|
2735
3433
|
if (opts.json) {
|
|
@@ -2747,11 +3445,16 @@ function prepareSnapshotCommand(opts, target) {
|
|
|
2747
3445
|
);
|
|
2748
3446
|
}
|
|
2749
3447
|
const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
|
|
2750
|
-
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
|
|
3448
|
+
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length") ?? DEFAULT_MAX_VALUE_LENGTH;
|
|
2751
3449
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
2752
3450
|
const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
|
|
2753
3451
|
const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
|
|
2754
3452
|
const setupEvals = parseSetupEvals(opts.setupEval);
|
|
3453
|
+
enforceNativeConditionMutationPolicy(
|
|
3454
|
+
condition ?? "",
|
|
3455
|
+
opts.allowMutation === true,
|
|
3456
|
+
"snapshot --condition"
|
|
3457
|
+
);
|
|
2755
3458
|
return {
|
|
2756
3459
|
target,
|
|
2757
3460
|
setupEvals,
|
|
@@ -2760,10 +3463,11 @@ function prepareSnapshotCommand(opts, target) {
|
|
|
2760
3463
|
remoteRoot: parseRemoteRoot(opts.remoteRoot),
|
|
2761
3464
|
timeoutMs: timeoutSec * 1e3,
|
|
2762
3465
|
...condition === void 0 ? {} : { condition },
|
|
2763
|
-
|
|
3466
|
+
maxValueLength,
|
|
2764
3467
|
...hitCount === void 0 ? {} : { hitCount },
|
|
2765
3468
|
...stackDepth === void 0 ? {} : { stackDepth },
|
|
2766
|
-
stackCaptures: parseCaptureList(opts.stackCaptures)
|
|
3469
|
+
stackCaptures: parseCaptureList(opts.stackCaptures),
|
|
3470
|
+
throwOnSideEffect: opts.allowMutation !== true
|
|
2767
3471
|
};
|
|
2768
3472
|
}
|
|
2769
3473
|
async function runSnapshotCommand(command, opts, reportProgress) {
|
|
@@ -2804,13 +3508,14 @@ async function runSnapshotOnSession(session, command, opts, reportProgress) {
|
|
|
2804
3508
|
reportProgress?.(
|
|
2805
3509
|
`Breakpoint hit; capturing ${captureCount.toString()} ${captureCount === 1 ? "expression" : "expressions"}...`
|
|
2806
3510
|
);
|
|
2807
|
-
const pausedStartedAt = pause.receivedAtMs ??
|
|
3511
|
+
const pausedStartedAt = pause.receivedAtMs ?? performance5.now();
|
|
2808
3512
|
const snapshot = await captureSnapshot(session, pause, {
|
|
2809
3513
|
captures: command.captures,
|
|
2810
3514
|
includeScopes: opts.includeScopes === true,
|
|
2811
|
-
|
|
3515
|
+
maxValueLength: command.maxValueLength,
|
|
2812
3516
|
...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
|
|
2813
|
-
stackCaptures: command.stackCaptures
|
|
3517
|
+
stackCaptures: command.stackCaptures,
|
|
3518
|
+
throwOnSideEffect: command.throwOnSideEffect
|
|
2814
3519
|
});
|
|
2815
3520
|
if (opts.keepPaused === true) {
|
|
2816
3521
|
reportProgress?.("Snapshot captured; leaving the target paused as requested.");
|
|
@@ -2834,26 +3539,33 @@ async function setCommandBreakpoints(session, command) {
|
|
|
2834
3539
|
}
|
|
2835
3540
|
async function waitForCommandPause(session, opts, handles, timeoutMs) {
|
|
2836
3541
|
let warnedUnmatchedPause = false;
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
3542
|
+
try {
|
|
3543
|
+
return await waitForPause(session, {
|
|
3544
|
+
timeoutMs,
|
|
3545
|
+
breakpointIds: handles.map((h) => h.breakpointId),
|
|
3546
|
+
unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
|
|
3547
|
+
onUnmatchedPause: (unmatchedPause) => {
|
|
3548
|
+
if (warnedUnmatchedPause || opts.failOnUnmatchedPause === true) {
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
warnedUnmatchedPause = true;
|
|
3552
|
+
warnOnUnmatchedPause(unmatchedPause);
|
|
2844
3553
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
3554
|
+
});
|
|
3555
|
+
} catch (error) {
|
|
3556
|
+
if (error instanceof CfInspectorError && (error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT")) {
|
|
3557
|
+
warnOnBoundBreakpointWithoutHit(handles);
|
|
2847
3558
|
}
|
|
2848
|
-
|
|
3559
|
+
throw error;
|
|
3560
|
+
}
|
|
2849
3561
|
}
|
|
2850
3562
|
async function resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress) {
|
|
2851
3563
|
try {
|
|
2852
3564
|
await resume(session);
|
|
2853
3565
|
reportProgress?.("Target resumed.");
|
|
2854
|
-
return withPausedDuration(snapshot, roundDurationMs(
|
|
3566
|
+
return withPausedDuration(snapshot, roundDurationMs(performance5.now() - pausedStartedAt));
|
|
2855
3567
|
} catch {
|
|
2856
|
-
|
|
3568
|
+
process9.stderr.write(
|
|
2857
3569
|
"[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
|
|
2858
3570
|
);
|
|
2859
3571
|
return withPausedDuration(snapshot, null);
|
|
@@ -2865,12 +3577,19 @@ function parseSetupEvals(raw) {
|
|
|
2865
3577
|
}
|
|
2866
3578
|
|
|
2867
3579
|
// src/cli/commands/watch.ts
|
|
2868
|
-
import { performance as
|
|
2869
|
-
import
|
|
3580
|
+
import { performance as performance6 } from "perf_hooks";
|
|
3581
|
+
import process10 from "process";
|
|
2870
3582
|
init_types();
|
|
2871
3583
|
async function handleWatch(opts) {
|
|
2872
3584
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2873
3585
|
const prepared = prepareWatchCommand(opts, target);
|
|
3586
|
+
warnOnCaptureMutationRisk(
|
|
3587
|
+
[...prepared.captures, ...prepared.stackCaptures],
|
|
3588
|
+
opts.allowMutation === true
|
|
3589
|
+
);
|
|
3590
|
+
for (const expression of prepared.setupEvals) {
|
|
3591
|
+
warnOnMutationRisk(expression, "watch --setup-eval");
|
|
3592
|
+
}
|
|
2874
3593
|
let stoppedReason = "signal";
|
|
2875
3594
|
let emitted = 0;
|
|
2876
3595
|
await withTerminationSignal(async (signal) => {
|
|
@@ -2892,11 +3611,16 @@ function prepareWatchCommand(opts, target) {
|
|
|
2892
3611
|
const perHitTimeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
|
|
2893
3612
|
const durationSec = parsePositiveInt(opts.duration, "--duration");
|
|
2894
3613
|
const maxEvents = parsePositiveInt(opts.maxEvents, "--max-events");
|
|
2895
|
-
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
|
|
3614
|
+
const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length") ?? DEFAULT_STREAM_MAX_VALUE_LENGTH;
|
|
2896
3615
|
const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
|
|
2897
3616
|
const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
|
|
2898
3617
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
2899
3618
|
const setupEvals = parseSetupEvals2(opts.setupEval);
|
|
3619
|
+
enforceNativeConditionMutationPolicy(
|
|
3620
|
+
condition ?? "",
|
|
3621
|
+
opts.allowMutation === true,
|
|
3622
|
+
"watch --condition"
|
|
3623
|
+
);
|
|
2900
3624
|
return {
|
|
2901
3625
|
target,
|
|
2902
3626
|
setupEvals,
|
|
@@ -2906,11 +3630,12 @@ function prepareWatchCommand(opts, target) {
|
|
|
2906
3630
|
perHitTimeoutMs: perHitTimeoutSec * 1e3,
|
|
2907
3631
|
...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
|
|
2908
3632
|
...maxEvents === void 0 ? {} : { maxEvents },
|
|
2909
|
-
|
|
3633
|
+
maxValueLength,
|
|
2910
3634
|
...condition === void 0 ? {} : { condition },
|
|
2911
3635
|
...hitCount === void 0 ? {} : { hitCount },
|
|
2912
3636
|
...stackDepth === void 0 ? {} : { stackDepth },
|
|
2913
|
-
stackCaptures: parseCaptureList(opts.stackCaptures)
|
|
3637
|
+
stackCaptures: parseCaptureList(opts.stackCaptures),
|
|
3638
|
+
throwOnSideEffect: opts.allowMutation !== true
|
|
2914
3639
|
};
|
|
2915
3640
|
}
|
|
2916
3641
|
async function runWatchLoop(session, command, opts, signal) {
|
|
@@ -2964,7 +3689,7 @@ async function runWatchLoop(session, command, opts, signal) {
|
|
|
2964
3689
|
break;
|
|
2965
3690
|
}
|
|
2966
3691
|
if (pause === "timeout") {
|
|
2967
|
-
if (deadline !== void 0 &&
|
|
3692
|
+
if (deadline !== void 0 && performance6.now() >= deadline) {
|
|
2968
3693
|
setStop("duration");
|
|
2969
3694
|
break;
|
|
2970
3695
|
}
|
|
@@ -2976,7 +3701,7 @@ async function runWatchLoop(session, command, opts, signal) {
|
|
|
2976
3701
|
try {
|
|
2977
3702
|
await resume(session);
|
|
2978
3703
|
} catch {
|
|
2979
|
-
|
|
3704
|
+
process10.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
|
|
2980
3705
|
setStop("transport-closed");
|
|
2981
3706
|
break;
|
|
2982
3707
|
}
|
|
@@ -2988,19 +3713,22 @@ async function runWatchLoop(session, command, opts, signal) {
|
|
|
2988
3713
|
} finally {
|
|
2989
3714
|
transportClosed.cancel();
|
|
2990
3715
|
}
|
|
3716
|
+
if (emitted === 0 && (state.reason === "duration" || state.reason === "signal")) {
|
|
3717
|
+
warnOnBoundBreakpointWithoutHit(handles);
|
|
3718
|
+
}
|
|
2991
3719
|
return { emitted, stoppedReason: state.reason };
|
|
2992
3720
|
}
|
|
2993
3721
|
function computeDeadline(durationMs) {
|
|
2994
3722
|
if (durationMs === void 0) {
|
|
2995
3723
|
return void 0;
|
|
2996
3724
|
}
|
|
2997
|
-
return
|
|
3725
|
+
return performance6.now() + durationMs;
|
|
2998
3726
|
}
|
|
2999
3727
|
function remainingForLoop(deadline, perHitTimeoutMs) {
|
|
3000
3728
|
if (deadline === void 0) {
|
|
3001
3729
|
return perHitTimeoutMs;
|
|
3002
3730
|
}
|
|
3003
|
-
const remaining = deadline -
|
|
3731
|
+
const remaining = deadline - performance6.now();
|
|
3004
3732
|
if (remaining <= 0) {
|
|
3005
3733
|
return 0;
|
|
3006
3734
|
}
|
|
@@ -3052,9 +3780,10 @@ async function captureWatchEvent(session, command, pause, hit, opts) {
|
|
|
3052
3780
|
const snapshot = await captureSnapshot(session, pause, {
|
|
3053
3781
|
captures: command.captures,
|
|
3054
3782
|
includeScopes: opts.includeScopes === true,
|
|
3055
|
-
|
|
3783
|
+
maxValueLength: command.maxValueLength,
|
|
3056
3784
|
...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
|
|
3057
|
-
stackCaptures: command.stackCaptures
|
|
3785
|
+
stackCaptures: command.stackCaptures,
|
|
3786
|
+
throwOnSideEffect: command.throwOnSideEffect
|
|
3058
3787
|
});
|
|
3059
3788
|
const at = formatLocation(command, snapshot.topFrame);
|
|
3060
3789
|
const base = {
|
|
@@ -3081,11 +3810,11 @@ function formatLocation(command, topFrame) {
|
|
|
3081
3810
|
}
|
|
3082
3811
|
function writeWatchSummary(reason, emitted, json) {
|
|
3083
3812
|
if (json) {
|
|
3084
|
-
|
|
3813
|
+
process10.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
|
|
3085
3814
|
`);
|
|
3086
3815
|
return;
|
|
3087
3816
|
}
|
|
3088
|
-
|
|
3817
|
+
process10.stderr.write(
|
|
3089
3818
|
`Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
|
|
3090
3819
|
`
|
|
3091
3820
|
);
|
|
@@ -3097,8 +3826,16 @@ function parseSetupEvals2(raw) {
|
|
|
3097
3826
|
|
|
3098
3827
|
// src/cli/program.ts
|
|
3099
3828
|
function applyTargetOptions(cmd, options = {}) {
|
|
3100
|
-
const
|
|
3101
|
-
|
|
3829
|
+
const withEndpointOptions = cmd.option("--port <number>", "Local port the inspector or tunnel listens on").option("--host <host>", "Hostname (default: 127.0.0.1)", "127.0.0.1").option("--region <key>", "CF region key (required with --app)").option("--api-endpoint <url>", "CF API endpoint override for --region").option("--org <name>", "CF org name (required with --app)").option("--space <name>", "CF space name (required with --app)").option(
|
|
3830
|
+
"--app <name>",
|
|
3831
|
+
"CF app name; requires explicit --region/--org/--space (ambient cf target is ignored)"
|
|
3832
|
+
);
|
|
3833
|
+
const withTargetOption = options.includeTarget === false ? withEndpointOptions : withEndpointOptions.option(
|
|
3834
|
+
"--target <index>",
|
|
3835
|
+
"Inspector target index from /json/list (default: 0)"
|
|
3836
|
+
);
|
|
3837
|
+
const withWorkerOption = options.includeWorker === false ? withTargetOption : withTargetOption.option("--worker <index>", "NodeWorker sub-session index listed by list-targets");
|
|
3838
|
+
return options.includeTimeout === false ? withWorkerOption : withWorkerOption.option("--timeout <seconds>", "Timeout for CF tunnel readiness in seconds (default: 180)");
|
|
3102
3839
|
}
|
|
3103
3840
|
var collectStrings = (value, prev = []) => [
|
|
3104
3841
|
...prev,
|
|
@@ -3139,14 +3876,14 @@ function registerSnapshot(program) {
|
|
|
3139
3876
|
applyTargetOptions(
|
|
3140
3877
|
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume"),
|
|
3141
3878
|
{ includeTimeout: false }
|
|
3142
|
-
).option("--bp <file:line>", "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--setup-eval <expr>", "Evaluate a global setup expression before breakpoint setup (repeatable)", collectStrings, []).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:
|
|
3879
|
+
).option("--bp <file:line>", "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--setup-eval <expr>", "Evaluate a global setup expression before breakpoint setup (repeatable)", collectStrings, []).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: 131072)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--condition <expr>", "Only pause when this JS expression evaluates truthy in the paused frame").option("--hit-count <n>", "Only pause after the breakpoint has been hit N or more times").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1, only top frame)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--allow-mutation", "Allow mutation-capable captures and native breakpoint conditions to run").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--no-json", "Print a human-readable summary instead of JSON").option("--quiet", "Suppress progress messages on stderr").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) => {
|
|
3143
3880
|
await handleSnapshot(opts);
|
|
3144
3881
|
});
|
|
3145
3882
|
}
|
|
3146
3883
|
function registerLog(program) {
|
|
3147
3884
|
applyTargetOptions(
|
|
3148
3885
|
program.command("log").description("Stream a non-pausing logpoint: log an expression each time a line executes")
|
|
3149
|
-
).requiredOption("--at <file:line>", "Logpoint location, e.g. src/handler.ts:42").requiredOption("--expr <expression>", "JavaScript expression to log on each hit").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N log events").option("--hit-count <n>", "Start logging once the line has been hit N or more times").option("--condition <expr>", "Only log when this JS expression evaluates truthy on the inspectee").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
3886
|
+
).requiredOption("--at <file:line>", "Logpoint location, e.g. src/handler.ts:42").requiredOption("--expr <expression>", "JavaScript expression to log on each hit").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N log events").option("--hit-count <n>", "Start logging once the line has been hit N or more times").option("--condition <expr>", "Only log when this JS expression evaluates truthy on the inspectee").option("--max-value-length <chars>", "Maximum characters per log value before truncation (default: 4096)").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
3150
3887
|
await handleLog(opts);
|
|
3151
3888
|
});
|
|
3152
3889
|
}
|
|
@@ -3154,7 +3891,7 @@ function registerWatch(program) {
|
|
|
3154
3891
|
applyTargetOptions(
|
|
3155
3892
|
program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits"),
|
|
3156
3893
|
{ includeTimeout: false }
|
|
3157
|
-
).option("--bp <file:line>", "Breakpoint location (repeatable), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate per hit").option("--setup-eval <expr>", "Evaluate a global setup expression before breakpoint setup (repeatable)", collectStrings, []).option("--condition <expr>", "Only emit hits where this JS expression evaluates truthy").option("--hit-count <n>", "Start emitting after the line has been hit N or more times").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N watch events").option("--timeout <seconds>", "How long to wait for the next hit before giving up (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--stack-depth <n>", "Walk this many call frames per hit (default: 1)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame").option("--include-scopes", "Include expanded paused-frame scopes per hit").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
3894
|
+
).option("--bp <file:line>", "Breakpoint location (repeatable), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate per hit").option("--setup-eval <expr>", "Evaluate a global setup expression before breakpoint setup (repeatable)", collectStrings, []).option("--condition <expr>", "Only emit hits where this JS expression evaluates truthy").option("--hit-count <n>", "Start emitting after the line has been hit N or more times").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N watch events").option("--timeout <seconds>", "How long to wait for the next hit before giving up (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--stack-depth <n>", "Walk this many call frames per hit (default: 1)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame").option("--allow-mutation", "Allow mutation-capable captures and native breakpoint conditions to run").option("--include-scopes", "Include expanded paused-frame scopes per hit").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
3158
3895
|
await handleWatch(opts);
|
|
3159
3896
|
});
|
|
3160
3897
|
}
|
|
@@ -3162,7 +3899,7 @@ function registerException(program) {
|
|
|
3162
3899
|
applyTargetOptions(
|
|
3163
3900
|
program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume"),
|
|
3164
3901
|
{ includeTimeout: false }
|
|
3165
|
-
).option("--type <state>", "Pause type: uncaught (default), caught, or all").option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--timeout <seconds>", "How long to wait for an exception (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default:
|
|
3902
|
+
).option("--type <state>", "Pause type: uncaught (default), caught, or all").option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--timeout <seconds>", "How long to wait for an exception (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 131072)").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--allow-mutation", "Allow mutation-capable capture expressions to run").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--no-json", "Print a human-readable summary instead of JSON").action(async (opts) => {
|
|
3166
3903
|
await handleException(opts);
|
|
3167
3904
|
});
|
|
3168
3905
|
}
|
|
@@ -3182,14 +3919,21 @@ function registerListScripts(program) {
|
|
|
3182
3919
|
}
|
|
3183
3920
|
function registerListTargets(program) {
|
|
3184
3921
|
applyTargetOptions(
|
|
3185
|
-
program.command("list-targets").description(
|
|
3186
|
-
|
|
3922
|
+
program.command("list-targets").description(
|
|
3923
|
+
"List raw /json/list targets and nested workers; use --target or --worker on other commands"
|
|
3924
|
+
),
|
|
3925
|
+
{ includeTarget: false, includeWorker: false }
|
|
3926
|
+
).option(
|
|
3927
|
+
"--no-json",
|
|
3928
|
+
"Print tab-separated target/worker rows: index, kind, type, title, and URL"
|
|
3929
|
+
).action(async (opts) => {
|
|
3187
3930
|
await handleListTargets(opts);
|
|
3188
3931
|
});
|
|
3189
3932
|
}
|
|
3190
3933
|
function registerAttach(program) {
|
|
3191
3934
|
applyTargetOptions(
|
|
3192
|
-
program.command("attach").description("Connect, fetch the inspector version, and disconnect (smoke-test)")
|
|
3935
|
+
program.command("attach").description("Connect, fetch the inspector version, and disconnect (smoke-test)"),
|
|
3936
|
+
{ includeTarget: false, includeWorker: false }
|
|
3193
3937
|
).option("--no-json", "Print a multi-line summary instead of JSON").action(async (opts) => {
|
|
3194
3938
|
await handleAttach(opts);
|
|
3195
3939
|
});
|
|
@@ -3198,20 +3942,20 @@ function registerAttach(program) {
|
|
|
3198
3942
|
// src/cli.ts
|
|
3199
3943
|
init_types();
|
|
3200
3944
|
try {
|
|
3201
|
-
await main(
|
|
3945
|
+
await main(process11.argv);
|
|
3202
3946
|
} catch (err) {
|
|
3203
3947
|
if (err instanceof CfInspectorError) {
|
|
3204
|
-
|
|
3948
|
+
process11.stderr.write(`Error [${err.code}]: ${err.message}
|
|
3205
3949
|
`);
|
|
3206
3950
|
if (err.detail !== void 0) {
|
|
3207
|
-
|
|
3951
|
+
process11.stderr.write(` detail: ${err.detail}
|
|
3208
3952
|
`);
|
|
3209
3953
|
}
|
|
3210
|
-
|
|
3954
|
+
process11.exit(1);
|
|
3211
3955
|
}
|
|
3212
3956
|
const message = err instanceof Error ? err.message : String(err);
|
|
3213
|
-
|
|
3957
|
+
process11.stderr.write(`Error: ${message}
|
|
3214
3958
|
`);
|
|
3215
|
-
|
|
3959
|
+
process11.exit(1);
|
|
3216
3960
|
}
|
|
3217
3961
|
//# sourceMappingURL=cli.js.map
|