@saptools/cf-inspector 0.6.2 → 0.7.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 +51 -14
- package/dist/cli.js +1371 -480
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +70 -1
- package/dist/index.js +579 -14
- 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 process12 from "process";
|
|
156
156
|
|
|
157
157
|
// src/cli/program.ts
|
|
158
158
|
import { readFileSync } from "fs";
|
|
@@ -334,6 +334,56 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
|
|
|
334
334
|
}
|
|
335
335
|
return { browser, protocolVersion };
|
|
336
336
|
}
|
|
337
|
+
function startInspectorKeepalive(host, port, options = {}) {
|
|
338
|
+
const intervalMs = options.intervalMs ?? 1e4;
|
|
339
|
+
const probeTimeoutMs = options.probeTimeoutMs ?? 2e3;
|
|
340
|
+
const failureThreshold = options.failureThreshold ?? 3;
|
|
341
|
+
const probe = options.probe ?? (async () => await fetchInspectorVersion(host, port, probeTimeoutMs));
|
|
342
|
+
let cancelled = false;
|
|
343
|
+
let consecutiveFailures = 0;
|
|
344
|
+
let timer;
|
|
345
|
+
let rejectFailure;
|
|
346
|
+
const failure = new Promise((_resolve, reject) => {
|
|
347
|
+
rejectFailure = reject;
|
|
348
|
+
});
|
|
349
|
+
const schedule = () => {
|
|
350
|
+
if (cancelled) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
timer = setTimeout(() => {
|
|
354
|
+
void runProbe();
|
|
355
|
+
}, intervalMs);
|
|
356
|
+
};
|
|
357
|
+
const runProbe = async () => {
|
|
358
|
+
try {
|
|
359
|
+
await probe();
|
|
360
|
+
consecutiveFailures = 0;
|
|
361
|
+
} catch (error) {
|
|
362
|
+
consecutiveFailures += 1;
|
|
363
|
+
if (consecutiveFailures >= failureThreshold) {
|
|
364
|
+
cancelled = true;
|
|
365
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
366
|
+
rejectFailure?.(new CfInspectorError(
|
|
367
|
+
"INSPECTOR_CONNECTION_FAILED",
|
|
368
|
+
`Inspector tunnel ${host}:${port.toString()} failed ${failureThreshold.toString()} consecutive keepalive probes and is no longer round-tripping. Retry the command after restarting or investigating the owning tunnel session.`,
|
|
369
|
+
detail
|
|
370
|
+
));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
schedule();
|
|
375
|
+
};
|
|
376
|
+
schedule();
|
|
377
|
+
return {
|
|
378
|
+
failure,
|
|
379
|
+
cancel: () => {
|
|
380
|
+
cancelled = true;
|
|
381
|
+
if (timer !== void 0) {
|
|
382
|
+
clearTimeout(timer);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
337
387
|
|
|
338
388
|
// src/cli/output.ts
|
|
339
389
|
import process from "process";
|
|
@@ -351,6 +401,7 @@ function writeHumanSnapshot(snapshot) {
|
|
|
351
401
|
lines.push(
|
|
352
402
|
`Snapshot @ ${snapshot.capturedAt}`,
|
|
353
403
|
` reason: ${snapshot.reason}`,
|
|
404
|
+
` isolate: ${formatIsolate(snapshot.isolate)}`,
|
|
354
405
|
` paused: ${pausedDuration}`
|
|
355
406
|
);
|
|
356
407
|
if (snapshot.exception !== void 0) {
|
|
@@ -419,12 +470,13 @@ function writeLogEvent(event, json) {
|
|
|
419
470
|
`);
|
|
420
471
|
return;
|
|
421
472
|
}
|
|
473
|
+
const isolateSuffix = event.isolate === void 0 ? "" : ` (${formatIsolate(event.isolate)})`;
|
|
422
474
|
if (event.error !== void 0) {
|
|
423
|
-
process.stdout.write(`[${event.ts}] ${event.at} !err ${renderTruncated(event.error, event)}
|
|
475
|
+
process.stdout.write(`[${event.ts}] ${event.at}${isolateSuffix} !err ${renderTruncated(event.error, event)}
|
|
424
476
|
`);
|
|
425
477
|
return;
|
|
426
478
|
}
|
|
427
|
-
process.stdout.write(`[${event.ts}] ${event.at} ${renderTruncated(event.value ?? "", event)}
|
|
479
|
+
process.stdout.write(`[${event.ts}] ${event.at}${isolateSuffix} ${renderTruncated(event.value ?? "", event)}
|
|
428
480
|
`);
|
|
429
481
|
}
|
|
430
482
|
function writeWatchEvent(event, json) {
|
|
@@ -433,8 +485,10 @@ function writeWatchEvent(event, json) {
|
|
|
433
485
|
`);
|
|
434
486
|
return;
|
|
435
487
|
}
|
|
436
|
-
process.stdout.write(
|
|
437
|
-
`)
|
|
488
|
+
process.stdout.write(
|
|
489
|
+
`[${event.ts}] hit#${event.hit.toString()} ${event.at} (${formatIsolate(event.isolate)})
|
|
490
|
+
`
|
|
491
|
+
);
|
|
438
492
|
if (event.exception !== void 0) {
|
|
439
493
|
process.stdout.write(` exception: ${renderExceptionDetail(event.exception)}
|
|
440
494
|
`);
|
|
@@ -445,6 +499,12 @@ function writeWatchEvent(event, json) {
|
|
|
445
499
|
`);
|
|
446
500
|
}
|
|
447
501
|
}
|
|
502
|
+
function formatIsolate(isolate) {
|
|
503
|
+
if (isolate?.kind === "worker") {
|
|
504
|
+
return `worker ${isolate.workerId}`;
|
|
505
|
+
}
|
|
506
|
+
return "main";
|
|
507
|
+
}
|
|
448
508
|
function renderExceptionDetail(exception) {
|
|
449
509
|
if (exception.description !== void 0) {
|
|
450
510
|
const originalLength = exception.descriptionOriginalLength;
|
|
@@ -486,6 +546,8 @@ import "@saptools/cf-debugger";
|
|
|
486
546
|
|
|
487
547
|
// src/cf/tunnel.ts
|
|
488
548
|
import { startDebugger } from "@saptools/cf-debugger";
|
|
549
|
+
init_types();
|
|
550
|
+
var REUSED_TUNNEL_GRACE_MS = 5e3;
|
|
489
551
|
function targetOptions(target) {
|
|
490
552
|
return {
|
|
491
553
|
...target.apiEndpoint === void 0 ? {} : { apiEndpoint: target.apiEndpoint },
|
|
@@ -547,6 +609,16 @@ async function openCfTunnel(target) {
|
|
|
547
609
|
if (localPort === void 0) {
|
|
548
610
|
throw error;
|
|
549
611
|
}
|
|
612
|
+
try {
|
|
613
|
+
await fetchInspectorVersion("127.0.0.1", localPort, REUSED_TUNNEL_GRACE_MS);
|
|
614
|
+
} catch (livenessError) {
|
|
615
|
+
const detail = livenessError instanceof Error ? livenessError.message : String(livenessError);
|
|
616
|
+
throw new CfInspectorError(
|
|
617
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
618
|
+
`Another debugger session claims tunnel port ${localPort.toString()}, but its Node inspector did not respond within ${REUSED_TUNNEL_GRACE_MS.toString()}ms. The tunnel may be stale or still finishing setup; retry shortly, or inspect and stop the owning cf-debugger session before opening a fresh tunnel.`,
|
|
619
|
+
detail
|
|
620
|
+
);
|
|
621
|
+
}
|
|
550
622
|
target.onStatus?.("ready", `Reusing existing tunnel on port ${localPort.toString()}`);
|
|
551
623
|
return { localPort, dispose: () => Promise.resolve() };
|
|
552
624
|
}
|
|
@@ -983,6 +1055,19 @@ function toResolvedLocations(value) {
|
|
|
983
1055
|
return [url === void 0 ? location : { ...location, url }];
|
|
984
1056
|
});
|
|
985
1057
|
}
|
|
1058
|
+
function toBreakLocations(value) {
|
|
1059
|
+
if (!Array.isArray(value)) {
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
return value.flatMap((entry) => {
|
|
1063
|
+
const location = toScriptLocation(entry);
|
|
1064
|
+
if (location === void 0 || !isRecord(entry)) {
|
|
1065
|
+
return [];
|
|
1066
|
+
}
|
|
1067
|
+
const type = nonEmptyString(entry["type"]);
|
|
1068
|
+
return [type === void 0 ? location : { ...location, type }];
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
986
1071
|
function remoteCompleteness(subtype) {
|
|
987
1072
|
if (subtype === "proxy") {
|
|
988
1073
|
return "unavailable";
|
|
@@ -1207,6 +1292,7 @@ function pauseDetail(pause) {
|
|
|
1207
1292
|
var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
|
|
1208
1293
|
var DEFAULT_HOST = "127.0.0.1";
|
|
1209
1294
|
var PAUSE_BUFFER_LIMIT = 32;
|
|
1295
|
+
var WORKER_DISCOVERY_SETTLE_MS = 500;
|
|
1210
1296
|
var NodeWorkerDiscovery = class {
|
|
1211
1297
|
constructor(client) {
|
|
1212
1298
|
this.client = client;
|
|
@@ -1215,12 +1301,18 @@ var NodeWorkerDiscovery = class {
|
|
|
1215
1301
|
const worker = toInspectorWorkerTarget(raw);
|
|
1216
1302
|
if (worker !== void 0) {
|
|
1217
1303
|
this.workers.set(worker.sessionId, worker);
|
|
1304
|
+
for (const listener of this.attachedListeners) {
|
|
1305
|
+
listener(worker);
|
|
1306
|
+
}
|
|
1218
1307
|
}
|
|
1219
1308
|
}),
|
|
1220
1309
|
client.on("NodeWorker.detachedFromWorker", (raw) => {
|
|
1221
1310
|
const sessionId = readField(raw, "sessionId");
|
|
1222
1311
|
if (typeof sessionId === "string") {
|
|
1223
1312
|
this.workers.delete(sessionId);
|
|
1313
|
+
for (const listener of this.detachedListeners) {
|
|
1314
|
+
listener(sessionId);
|
|
1315
|
+
}
|
|
1224
1316
|
}
|
|
1225
1317
|
})
|
|
1226
1318
|
];
|
|
@@ -1230,6 +1322,8 @@ var NodeWorkerDiscovery = class {
|
|
|
1230
1322
|
detachListeners;
|
|
1231
1323
|
supported = false;
|
|
1232
1324
|
disposed = false;
|
|
1325
|
+
attachedListeners = /* @__PURE__ */ new Set();
|
|
1326
|
+
detachedListeners = /* @__PURE__ */ new Set();
|
|
1233
1327
|
async enable() {
|
|
1234
1328
|
try {
|
|
1235
1329
|
await this.client.send("NodeWorker.enable", { waitForDebuggerOnStart: false });
|
|
@@ -1243,6 +1337,45 @@ var NodeWorkerDiscovery = class {
|
|
|
1243
1337
|
list() {
|
|
1244
1338
|
return [...this.workers.values()].sort(compareWorkers);
|
|
1245
1339
|
}
|
|
1340
|
+
onAttached(listener) {
|
|
1341
|
+
this.attachedListeners.add(listener);
|
|
1342
|
+
return () => {
|
|
1343
|
+
this.attachedListeners.delete(listener);
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
onDetached(listener) {
|
|
1347
|
+
this.detachedListeners.add(listener);
|
|
1348
|
+
return () => {
|
|
1349
|
+
this.detachedListeners.delete(listener);
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
async waitFor(predicate, timeoutMs = WORKER_DISCOVERY_SETTLE_MS) {
|
|
1353
|
+
const existing = predicate(this.list());
|
|
1354
|
+
if (existing !== void 0) {
|
|
1355
|
+
return existing;
|
|
1356
|
+
}
|
|
1357
|
+
return await new Promise((resolve) => {
|
|
1358
|
+
let settled = false;
|
|
1359
|
+
const finish = (worker) => {
|
|
1360
|
+
if (settled) {
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
settled = true;
|
|
1364
|
+
clearTimeout(timer);
|
|
1365
|
+
detach();
|
|
1366
|
+
resolve(worker);
|
|
1367
|
+
};
|
|
1368
|
+
const detach = this.onAttached(() => {
|
|
1369
|
+
const worker = predicate(this.list());
|
|
1370
|
+
if (worker !== void 0) {
|
|
1371
|
+
finish(worker);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
const timer = setTimeout(() => {
|
|
1375
|
+
finish();
|
|
1376
|
+
}, timeoutMs);
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1246
1379
|
async dispose() {
|
|
1247
1380
|
if (this.disposed) {
|
|
1248
1381
|
return;
|
|
@@ -1257,6 +1390,8 @@ var NodeWorkerDiscovery = class {
|
|
|
1257
1390
|
for (const detach of this.detachListeners) {
|
|
1258
1391
|
detach();
|
|
1259
1392
|
}
|
|
1393
|
+
this.attachedListeners.clear();
|
|
1394
|
+
this.detachedListeners.clear();
|
|
1260
1395
|
}
|
|
1261
1396
|
};
|
|
1262
1397
|
function isUnsupportedNodeWorkerDomain(error) {
|
|
@@ -1321,14 +1456,15 @@ async function connectInspector(options) {
|
|
|
1321
1456
|
let workerDiscovery;
|
|
1322
1457
|
try {
|
|
1323
1458
|
workerDiscovery = await startNodeWorkerDiscovery(client);
|
|
1324
|
-
if (options.workerIndex === void 0) {
|
|
1325
|
-
const session = await initSession(client, target);
|
|
1459
|
+
if (options.workerIndex === void 0 && options.workerId === void 0) {
|
|
1460
|
+
const session = await initSession(client, target, { kind: "main" });
|
|
1326
1461
|
return withWorkerMetadata(session, workerDiscovery, targetIndex, targets.length);
|
|
1327
1462
|
}
|
|
1328
1463
|
return await initWorkerSession(
|
|
1329
1464
|
client,
|
|
1330
1465
|
workerDiscovery,
|
|
1331
1466
|
options.workerIndex,
|
|
1467
|
+
options.workerId,
|
|
1332
1468
|
targetIndex,
|
|
1333
1469
|
targets.length
|
|
1334
1470
|
);
|
|
@@ -1338,25 +1474,207 @@ async function connectInspector(options) {
|
|
|
1338
1474
|
throw err;
|
|
1339
1475
|
}
|
|
1340
1476
|
}
|
|
1341
|
-
async function initWorkerSession(parent, discovery, workerIndex, targetIndex, targetCount) {
|
|
1342
|
-
const workers = discovery.list();
|
|
1477
|
+
async function initWorkerSession(parent, discovery, workerIndex, workerId, targetIndex, targetCount) {
|
|
1343
1478
|
if (!discovery.supported) {
|
|
1344
1479
|
throw new CfInspectorError(
|
|
1345
1480
|
"INSPECTOR_DISCOVERY_FAILED",
|
|
1346
1481
|
"This runtime does not expose the NodeWorker CDP domain; --worker cannot be used. Run list-targets for available raw targets."
|
|
1347
1482
|
);
|
|
1348
1483
|
}
|
|
1349
|
-
const worker = workers[workerIndex];
|
|
1484
|
+
const worker = await discovery.waitFor((workers) => workerId === void 0 ? workerIndex === void 0 ? void 0 : workers[workerIndex] : workers.find((candidate) => candidate.workerId === workerId));
|
|
1350
1485
|
if (worker === void 0) {
|
|
1486
|
+
const workers = discovery.list();
|
|
1487
|
+
const selector = workerId === void 0 ? `index ${(workerIndex ?? 0).toString()}` : `workerId ${JSON.stringify(workerId)}`;
|
|
1351
1488
|
throw new CfInspectorError(
|
|
1352
1489
|
"INSPECTOR_DISCOVERY_FAILED",
|
|
1353
|
-
`No NodeWorker sub-session
|
|
1490
|
+
`No NodeWorker sub-session with ${selector} is currently attached (available: ${workers.length.toString()}). Ensure the worker is alive, then rerun list-targets.`
|
|
1354
1491
|
);
|
|
1355
1492
|
}
|
|
1356
1493
|
const client = await createNodeWorkerClient(parent, worker.sessionId);
|
|
1357
|
-
const session = await initSession(client, workerToInspectorTarget(worker)
|
|
1494
|
+
const session = await initSession(client, workerToInspectorTarget(worker), {
|
|
1495
|
+
kind: "worker",
|
|
1496
|
+
workerId: worker.workerId
|
|
1497
|
+
});
|
|
1358
1498
|
return withWorkerMetadata(session, discovery, targetIndex, targetCount, workerIndex, parent);
|
|
1359
1499
|
}
|
|
1500
|
+
async function connectInspectorGroup(options) {
|
|
1501
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
1502
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
1503
|
+
const targets = await discoverInspectorTargets(host, options.port, connectTimeoutMs);
|
|
1504
|
+
const targetIndex = options.targetIndex ?? 0;
|
|
1505
|
+
const target = targets[targetIndex];
|
|
1506
|
+
if (target === void 0) {
|
|
1507
|
+
throw new CfInspectorError(
|
|
1508
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
1509
|
+
`No inspector target at index ${targetIndex.toString()} on ${host}:${options.port.toString()} (available: ${targets.length.toString()})`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
const parent = await CdpClient.connect({ url: target.webSocketDebuggerUrl, connectTimeoutMs });
|
|
1513
|
+
let discovery;
|
|
1514
|
+
try {
|
|
1515
|
+
discovery = await startNodeWorkerDiscovery(parent);
|
|
1516
|
+
const main2 = await initSession(parent, target, { kind: "main" });
|
|
1517
|
+
const group = new DynamicInspectorSessionGroup(
|
|
1518
|
+
parent,
|
|
1519
|
+
discovery,
|
|
1520
|
+
main2,
|
|
1521
|
+
targetIndex,
|
|
1522
|
+
targets.length
|
|
1523
|
+
);
|
|
1524
|
+
await group.initialize();
|
|
1525
|
+
return group;
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
await discovery?.dispose();
|
|
1528
|
+
parent.dispose();
|
|
1529
|
+
throw error;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
var DynamicInspectorSessionGroup = class {
|
|
1533
|
+
constructor(parent, discovery, main2, targetIndex, targetCount) {
|
|
1534
|
+
this.parent = parent;
|
|
1535
|
+
this.discovery = discovery;
|
|
1536
|
+
this.targetIndex = targetIndex;
|
|
1537
|
+
this.targetCount = targetCount;
|
|
1538
|
+
this.workerDiscoverySupported = discovery.supported;
|
|
1539
|
+
this.sessions.set("main", main2);
|
|
1540
|
+
this.detachDiscoveryListeners = [
|
|
1541
|
+
discovery.onAttached((worker) => {
|
|
1542
|
+
this.queueWorker(worker);
|
|
1543
|
+
}),
|
|
1544
|
+
discovery.onDetached((sessionId) => {
|
|
1545
|
+
void this.detachWorker(sessionId);
|
|
1546
|
+
})
|
|
1547
|
+
];
|
|
1548
|
+
}
|
|
1549
|
+
parent;
|
|
1550
|
+
discovery;
|
|
1551
|
+
targetIndex;
|
|
1552
|
+
targetCount;
|
|
1553
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1554
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1555
|
+
removedListeners = /* @__PURE__ */ new Set();
|
|
1556
|
+
errorListeners = /* @__PURE__ */ new Set();
|
|
1557
|
+
pending = /* @__PURE__ */ new Set();
|
|
1558
|
+
detachedSessionIds = /* @__PURE__ */ new Set();
|
|
1559
|
+
detachDiscoveryListeners;
|
|
1560
|
+
initializationError;
|
|
1561
|
+
initializing = true;
|
|
1562
|
+
disposed = false;
|
|
1563
|
+
workerDiscoverySupported;
|
|
1564
|
+
async initialize() {
|
|
1565
|
+
for (const worker of this.discovery.list()) {
|
|
1566
|
+
this.queueWorker(worker);
|
|
1567
|
+
}
|
|
1568
|
+
await this.waitForPending();
|
|
1569
|
+
this.initializing = false;
|
|
1570
|
+
if (this.initializationError !== void 0) {
|
|
1571
|
+
throw this.initializationError;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
list() {
|
|
1575
|
+
return [...this.sessions.values()];
|
|
1576
|
+
}
|
|
1577
|
+
onSession(listener) {
|
|
1578
|
+
this.listeners.add(listener);
|
|
1579
|
+
for (const session of this.sessions.values()) {
|
|
1580
|
+
listener(session);
|
|
1581
|
+
}
|
|
1582
|
+
return () => {
|
|
1583
|
+
this.listeners.delete(listener);
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
onSessionRemoved(listener) {
|
|
1587
|
+
this.removedListeners.add(listener);
|
|
1588
|
+
return () => {
|
|
1589
|
+
this.removedListeners.delete(listener);
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
onError(listener) {
|
|
1593
|
+
this.errorListeners.add(listener);
|
|
1594
|
+
return () => {
|
|
1595
|
+
this.errorListeners.delete(listener);
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
async dispose() {
|
|
1599
|
+
if (this.disposed) {
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
this.disposed = true;
|
|
1603
|
+
for (const detach of this.detachDiscoveryListeners) {
|
|
1604
|
+
detach();
|
|
1605
|
+
}
|
|
1606
|
+
await Promise.allSettled([...this.pending]);
|
|
1607
|
+
const sessions = [...this.list()].reverse();
|
|
1608
|
+
for (const session of sessions) {
|
|
1609
|
+
try {
|
|
1610
|
+
await session.dispose();
|
|
1611
|
+
} catch {
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
this.sessions.clear();
|
|
1615
|
+
this.listeners.clear();
|
|
1616
|
+
this.removedListeners.clear();
|
|
1617
|
+
this.errorListeners.clear();
|
|
1618
|
+
await this.discovery.dispose();
|
|
1619
|
+
this.parent.dispose();
|
|
1620
|
+
}
|
|
1621
|
+
queueWorker(worker) {
|
|
1622
|
+
if (this.disposed || this.sessions.has(worker.sessionId)) {
|
|
1623
|
+
return;
|
|
1624
|
+
}
|
|
1625
|
+
const pending = this.attachWorker(worker).catch((error) => {
|
|
1626
|
+
const normalized = error instanceof Error ? error : new Error("Worker inspector attachment failed");
|
|
1627
|
+
if (this.initializing && this.initializationError === void 0) {
|
|
1628
|
+
this.initializationError = normalized;
|
|
1629
|
+
}
|
|
1630
|
+
for (const listener of this.errorListeners) {
|
|
1631
|
+
listener(normalized);
|
|
1632
|
+
}
|
|
1633
|
+
}).finally(() => {
|
|
1634
|
+
this.pending.delete(pending);
|
|
1635
|
+
});
|
|
1636
|
+
this.pending.add(pending);
|
|
1637
|
+
}
|
|
1638
|
+
async attachWorker(worker) {
|
|
1639
|
+
const client = await createNodeWorkerClient(this.parent, worker.sessionId);
|
|
1640
|
+
try {
|
|
1641
|
+
const session = await initSession(client, workerToInspectorTarget(worker), {
|
|
1642
|
+
kind: "worker",
|
|
1643
|
+
workerId: worker.workerId
|
|
1644
|
+
});
|
|
1645
|
+
if (this.disposed || this.detachedSessionIds.delete(worker.sessionId)) {
|
|
1646
|
+
await session.dispose();
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
this.sessions.set(worker.sessionId, session);
|
|
1650
|
+
for (const listener of this.listeners) {
|
|
1651
|
+
listener(session);
|
|
1652
|
+
}
|
|
1653
|
+
} catch (error) {
|
|
1654
|
+
client.dispose();
|
|
1655
|
+
if (!this.disposed) {
|
|
1656
|
+
throw error;
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
async detachWorker(sessionId) {
|
|
1661
|
+
const session = this.sessions.get(sessionId);
|
|
1662
|
+
if (session === void 0) {
|
|
1663
|
+
this.detachedSessionIds.add(sessionId);
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
this.sessions.delete(sessionId);
|
|
1667
|
+
for (const listener of this.removedListeners) {
|
|
1668
|
+
listener(session);
|
|
1669
|
+
}
|
|
1670
|
+
await session.dispose();
|
|
1671
|
+
}
|
|
1672
|
+
async waitForPending() {
|
|
1673
|
+
while (this.pending.size > 0) {
|
|
1674
|
+
await Promise.all([...this.pending]);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1360
1678
|
function withWorkerMetadata(session, discovery, targetIndex, targetCount, workerIndex, parent) {
|
|
1361
1679
|
return {
|
|
1362
1680
|
...session,
|
|
@@ -1393,16 +1711,16 @@ async function discoverNodeWorkerTargets(target, connectTimeoutMs = DEFAULT_CONN
|
|
|
1393
1711
|
client.dispose();
|
|
1394
1712
|
}
|
|
1395
1713
|
}
|
|
1396
|
-
async function initSession(client, target) {
|
|
1714
|
+
async function initSession(client, target, isolate) {
|
|
1397
1715
|
const scripts = /* @__PURE__ */ new Map();
|
|
1398
1716
|
registerScriptTracking(client, scripts);
|
|
1399
1717
|
const pauseBuffer = [];
|
|
1400
1718
|
const pauseWaitGate = { active: false };
|
|
1401
|
-
const debuggerState = {};
|
|
1719
|
+
const debuggerState = { paused: false };
|
|
1402
1720
|
registerPauseTracking(client, scripts, pauseBuffer, pauseWaitGate, debuggerState);
|
|
1403
1721
|
await client.send("Runtime.enable");
|
|
1404
1722
|
await client.send("Debugger.enable");
|
|
1405
|
-
return createSession(client, target, scripts, pauseBuffer, pauseWaitGate, debuggerState);
|
|
1723
|
+
return createSession(client, target, isolate, scripts, pauseBuffer, pauseWaitGate, debuggerState);
|
|
1406
1724
|
}
|
|
1407
1725
|
function registerScriptTracking(client, scripts) {
|
|
1408
1726
|
client.on("Debugger.scriptParsed", (raw) => {
|
|
@@ -1414,23 +1732,28 @@ function registerScriptTracking(client, scripts) {
|
|
|
1414
1732
|
}
|
|
1415
1733
|
function registerPauseTracking(client, scripts, pauseBuffer, pauseWaitGate, debuggerState) {
|
|
1416
1734
|
client.on("Debugger.paused", (raw) => {
|
|
1735
|
+
const event = toPauseEvent(raw, performance2.now(), scripts);
|
|
1736
|
+
debuggerState.paused = true;
|
|
1737
|
+
debuggerState.currentPause = event;
|
|
1417
1738
|
if (pauseWaitGate.active) {
|
|
1418
1739
|
return;
|
|
1419
1740
|
}
|
|
1420
|
-
const event = toPauseEvent(raw, performance2.now(), scripts);
|
|
1421
1741
|
if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
|
|
1422
1742
|
pauseBuffer.shift();
|
|
1423
1743
|
}
|
|
1424
1744
|
pauseBuffer.push(event);
|
|
1425
1745
|
});
|
|
1426
1746
|
client.on("Debugger.resumed", () => {
|
|
1747
|
+
debuggerState.paused = false;
|
|
1748
|
+
delete debuggerState.currentPause;
|
|
1427
1749
|
debuggerState.lastResumedAtMs = performance2.now();
|
|
1428
1750
|
});
|
|
1429
1751
|
}
|
|
1430
|
-
function createSession(client, target, scripts, pauseBuffer, pauseWaitGate, debuggerState) {
|
|
1752
|
+
function createSession(client, target, isolate, scripts, pauseBuffer, pauseWaitGate, debuggerState) {
|
|
1431
1753
|
return {
|
|
1432
1754
|
client,
|
|
1433
1755
|
target,
|
|
1756
|
+
isolate,
|
|
1434
1757
|
scripts,
|
|
1435
1758
|
pauseBuffer,
|
|
1436
1759
|
pauseWaitGate,
|
|
@@ -1622,7 +1945,7 @@ function warnOnImplicitInspectorSelection(session, targetWasExplicit, workerWasE
|
|
|
1622
1945
|
const workerCount = session.workerTargets?.length ?? 0;
|
|
1623
1946
|
if (!workerWasExplicit && workerCount > 0) {
|
|
1624
1947
|
process2.stderr.write(
|
|
1625
|
-
`[cf-inspector] notice: attached to the main isolate; ${workerCount.toString()} Node ${workerCount === 1 ? "worker is" : "workers are"} available.
|
|
1948
|
+
`[cf-inspector] notice: attached to the main isolate; ${workerCount.toString()} Node ${workerCount === 1 ? "worker is" : "workers are"} available. This command is single-isolate by nature; use --worker-id <id> to inspect one worker explicitly.
|
|
1626
1949
|
`
|
|
1627
1950
|
);
|
|
1628
1951
|
}
|
|
@@ -1635,7 +1958,7 @@ function warnOnBoundBreakpointWithoutHit(handles) {
|
|
|
1635
1958
|
return;
|
|
1636
1959
|
}
|
|
1637
1960
|
process2.stderr.write(
|
|
1638
|
-
`[cf-inspector] warning: ${boundCount.toString()} breakpoint ${boundCount === 1 ? "location bound" : "locations bound"}, but no hit was observed.
|
|
1961
|
+
`[cf-inspector] warning: ${boundCount.toString()} breakpoint ${boundCount === 1 ? "location bound" : "locations bound"}, but no hit was observed. No selected isolate executed the location before the command stopped. Check conditions, hit counts, request traffic, and use check-breakpoint to verify the exact line.
|
|
1639
1962
|
`
|
|
1640
1963
|
);
|
|
1641
1964
|
}
|
|
@@ -1701,12 +2024,15 @@ function resolveTarget(opts, options = {}) {
|
|
|
1701
2024
|
const port = parsePositiveInt(opts.port, "--port");
|
|
1702
2025
|
const targetIndex = parseTargetIndex(opts.target);
|
|
1703
2026
|
const workerIndex = parseSelectionIndex(opts.worker, "--worker");
|
|
2027
|
+
const workerId = parseWorkerId(opts.workerId);
|
|
2028
|
+
const mainOnly = opts.mainOnly === true;
|
|
2029
|
+
validateIsolateSelectors(targetIndex, workerIndex, workerId, mainOnly);
|
|
1704
2030
|
if (port !== void 0) {
|
|
1705
2031
|
return {
|
|
1706
2032
|
kind: "port",
|
|
1707
2033
|
port,
|
|
1708
2034
|
host: opts.host ?? "127.0.0.1",
|
|
1709
|
-
...selectionOptions(targetIndex, workerIndex)
|
|
2035
|
+
...selectionOptions(targetIndex, workerIndex, workerId, mainOnly)
|
|
1710
2036
|
};
|
|
1711
2037
|
}
|
|
1712
2038
|
const region = optionalText(opts.region);
|
|
@@ -1733,7 +2059,9 @@ function resolveTarget(opts, options = {}) {
|
|
|
1733
2059
|
app,
|
|
1734
2060
|
parseTunnelTimeout(opts, options),
|
|
1735
2061
|
targetIndex,
|
|
1736
|
-
workerIndex
|
|
2062
|
+
workerIndex,
|
|
2063
|
+
workerId,
|
|
2064
|
+
mainOnly
|
|
1737
2065
|
);
|
|
1738
2066
|
}
|
|
1739
2067
|
async function resolveTargetWithCurrentCfTarget(opts, options = {}) {
|
|
@@ -1764,13 +2092,15 @@ function parseSelectionIndex(raw, label) {
|
|
|
1764
2092
|
function targetIndexOption(targetIndex) {
|
|
1765
2093
|
return targetIndex === void 0 ? {} : { targetIndex };
|
|
1766
2094
|
}
|
|
1767
|
-
function selectionOptions(targetIndex, workerIndex) {
|
|
2095
|
+
function selectionOptions(targetIndex, workerIndex, workerId, mainOnly) {
|
|
1768
2096
|
return {
|
|
1769
2097
|
...targetIndexOption(targetIndex),
|
|
1770
|
-
...workerIndex === void 0 ? {} : { workerIndex }
|
|
2098
|
+
...workerIndex === void 0 ? {} : { workerIndex },
|
|
2099
|
+
...workerId === void 0 ? {} : { workerId },
|
|
2100
|
+
...mainOnly === true ? { mainOnly: true } : {}
|
|
1771
2101
|
};
|
|
1772
2102
|
}
|
|
1773
|
-
function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex, workerIndex) {
|
|
2103
|
+
function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex, workerIndex, workerId, mainOnly) {
|
|
1774
2104
|
return {
|
|
1775
2105
|
kind: "cf",
|
|
1776
2106
|
region,
|
|
@@ -1779,13 +2109,38 @@ function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, t
|
|
|
1779
2109
|
space,
|
|
1780
2110
|
app,
|
|
1781
2111
|
tunnelTimeoutMs: tunnelTimeoutSec * 1e3,
|
|
1782
|
-
...selectionOptions(targetIndex, workerIndex)
|
|
2112
|
+
...selectionOptions(targetIndex, workerIndex, workerId, mainOnly)
|
|
1783
2113
|
};
|
|
1784
2114
|
}
|
|
2115
|
+
function validateIsolateSelectors(targetIndex, workerIndex, workerId, mainOnly) {
|
|
2116
|
+
const workerSelectors = Number(workerIndex !== void 0) + Number(workerId !== void 0);
|
|
2117
|
+
if (workerSelectors > 1 || mainOnly && workerSelectors > 0) {
|
|
2118
|
+
throw new CfInspectorError(
|
|
2119
|
+
"INVALID_ARGUMENT",
|
|
2120
|
+
"Use only one of --worker, --worker-id, or --main-only."
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
if (targetIndex !== void 0 && workerSelectors === 0 && !mainOnly) {
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1785
2127
|
function optionalText(value) {
|
|
1786
2128
|
const trimmed = value?.trim();
|
|
1787
2129
|
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
1788
2130
|
}
|
|
2131
|
+
function parseWorkerId(value) {
|
|
2132
|
+
if (value === void 0) {
|
|
2133
|
+
return void 0;
|
|
2134
|
+
}
|
|
2135
|
+
const trimmed = value.trim();
|
|
2136
|
+
if (trimmed.length === 0) {
|
|
2137
|
+
throw new CfInspectorError(
|
|
2138
|
+
"INVALID_ARGUMENT",
|
|
2139
|
+
"Invalid --worker-id: expected a non-empty workerId from list-targets"
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
return trimmed;
|
|
2143
|
+
}
|
|
1789
2144
|
async function withSession(target, fn, reportProgress, signal) {
|
|
1790
2145
|
const tunnel = await openTarget(target, reportProgress, signal);
|
|
1791
2146
|
let session;
|
|
@@ -1796,12 +2151,13 @@ async function withSession(target, fn, reportProgress, signal) {
|
|
|
1796
2151
|
session = await connectInspector({
|
|
1797
2152
|
port: tunnel.port,
|
|
1798
2153
|
host: tunnel.host,
|
|
1799
|
-
...selectionOptions(target.targetIndex, target.workerIndex)
|
|
2154
|
+
...selectionOptions(target.targetIndex, target.workerIndex),
|
|
2155
|
+
...target.workerId === void 0 ? {} : { workerId: target.workerId }
|
|
1800
2156
|
});
|
|
1801
2157
|
warnOnImplicitInspectorSelection(
|
|
1802
2158
|
session,
|
|
1803
2159
|
target.targetIndex !== void 0,
|
|
1804
|
-
target.workerIndex !== void 0
|
|
2160
|
+
target.workerIndex !== void 0 || target.workerId !== void 0
|
|
1805
2161
|
);
|
|
1806
2162
|
reportProgress?.("Inspector session is ready.");
|
|
1807
2163
|
return await fn(session, tunnel.port);
|
|
@@ -1814,6 +2170,56 @@ async function withSession(target, fn, reportProgress, signal) {
|
|
|
1814
2170
|
await tunnel.dispose();
|
|
1815
2171
|
}
|
|
1816
2172
|
}
|
|
2173
|
+
async function withSessions(target, fn, reportProgress, signal) {
|
|
2174
|
+
const tunnel = await openTarget(target, reportProgress, signal);
|
|
2175
|
+
let group;
|
|
2176
|
+
try {
|
|
2177
|
+
reportProgress?.(
|
|
2178
|
+
`Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
|
|
2179
|
+
);
|
|
2180
|
+
const autoAttach = target.targetIndex === void 0 && target.workerIndex === void 0 && target.workerId === void 0 && target.mainOnly !== true;
|
|
2181
|
+
if (autoAttach) {
|
|
2182
|
+
group = await connectInspectorGroup({ port: tunnel.port, host: tunnel.host });
|
|
2183
|
+
} else {
|
|
2184
|
+
const session = await connectInspector({
|
|
2185
|
+
port: tunnel.port,
|
|
2186
|
+
host: tunnel.host,
|
|
2187
|
+
...selectionOptions(target.targetIndex, target.workerIndex, target.workerId)
|
|
2188
|
+
});
|
|
2189
|
+
group = singleSessionGroup(session);
|
|
2190
|
+
}
|
|
2191
|
+
reportProgress?.("Inspector session is ready.");
|
|
2192
|
+
return await fn(group, tunnel.port);
|
|
2193
|
+
} finally {
|
|
2194
|
+
try {
|
|
2195
|
+
if (group !== void 0) {
|
|
2196
|
+
const sessionCount = group.list().length;
|
|
2197
|
+
reportProgress?.(sessionCount === 1 ? "Closing the inspector session..." : `Closing ${sessionCount.toString()} inspector sessions...`);
|
|
2198
|
+
await group.dispose();
|
|
2199
|
+
reportProgress?.(sessionCount === 1 ? "Inspector session closed." : "Inspector sessions closed.");
|
|
2200
|
+
}
|
|
2201
|
+
} finally {
|
|
2202
|
+
await tunnel.dispose();
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
function singleSessionGroup(session) {
|
|
2207
|
+
return {
|
|
2208
|
+
targetIndex: session.targetIndex ?? 0,
|
|
2209
|
+
targetCount: session.targetCount ?? 1,
|
|
2210
|
+
workerDiscoverySupported: session.workerDiscoverySupported ?? false,
|
|
2211
|
+
list: () => [session],
|
|
2212
|
+
onSession: (listener) => {
|
|
2213
|
+
listener(session);
|
|
2214
|
+
return () => void 0;
|
|
2215
|
+
},
|
|
2216
|
+
onSessionRemoved: () => () => void 0,
|
|
2217
|
+
onError: () => () => void 0,
|
|
2218
|
+
dispose: async () => {
|
|
2219
|
+
await session.dispose();
|
|
2220
|
+
}
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
1817
2223
|
async function openTarget(target, reportProgress, signal) {
|
|
1818
2224
|
if (target.kind === "port") {
|
|
1819
2225
|
return {
|
|
@@ -1867,185 +2273,55 @@ async function handleAttach(opts) {
|
|
|
1867
2273
|
}
|
|
1868
2274
|
}
|
|
1869
2275
|
|
|
1870
|
-
// src/cli/commands/
|
|
2276
|
+
// src/cli/commands/checkBreakpoint.ts
|
|
1871
2277
|
import process4 from "process";
|
|
1872
2278
|
|
|
1873
|
-
// src/
|
|
2279
|
+
// src/pathMapper.ts
|
|
1874
2280
|
init_types();
|
|
1875
|
-
|
|
1876
|
-
|
|
2281
|
+
var REGEX_PREFIX = "regex:";
|
|
2282
|
+
var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
|
|
2283
|
+
var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
|
|
2284
|
+
function parseBreakpointSpec(input) {
|
|
2285
|
+
const idx = input.lastIndexOf(":");
|
|
2286
|
+
if (idx <= 0 || idx === input.length - 1) {
|
|
2287
|
+
throw new CfInspectorError(
|
|
2288
|
+
"INVALID_BREAKPOINT",
|
|
2289
|
+
`Breakpoint must be in 'file:line' form, received: "${input}"`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
const file = input.slice(0, idx).trim();
|
|
2293
|
+
const lineRaw = input.slice(idx + 1).trim();
|
|
2294
|
+
const line = Number.parseInt(lineRaw, 10);
|
|
2295
|
+
if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
|
|
2296
|
+
throw new CfInspectorError(
|
|
2297
|
+
"INVALID_BREAKPOINT",
|
|
2298
|
+
`Breakpoint line must be a positive integer, received: "${lineRaw}"`
|
|
2299
|
+
);
|
|
2300
|
+
}
|
|
2301
|
+
if (file.length === 0) {
|
|
2302
|
+
throw new CfInspectorError(
|
|
2303
|
+
"INVALID_BREAKPOINT",
|
|
2304
|
+
`Breakpoint file path is empty in "${input}"`
|
|
2305
|
+
);
|
|
2306
|
+
}
|
|
2307
|
+
return { file, line };
|
|
1877
2308
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
2309
|
+
function parseRemoteRoot(value) {
|
|
2310
|
+
const trimmed = value?.trim();
|
|
2311
|
+
if (trimmed === void 0 || trimmed.length === 0) {
|
|
2312
|
+
return { kind: "none" };
|
|
2313
|
+
}
|
|
2314
|
+
if (trimmed.startsWith(REGEX_PREFIX)) {
|
|
2315
|
+
return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
|
|
2316
|
+
}
|
|
2317
|
+
const slashRegex = parseSlashDelimited(trimmed);
|
|
2318
|
+
if (slashRegex !== void 0) {
|
|
2319
|
+
return toRegex(slashRegex.pattern, slashRegex.flags);
|
|
2320
|
+
}
|
|
2321
|
+
return { kind: "literal", value: stripTrailingSlash(trimmed) };
|
|
1880
2322
|
}
|
|
1881
|
-
|
|
1882
|
-
return
|
|
1883
|
-
callFrameId,
|
|
1884
|
-
expression,
|
|
1885
|
-
returnByValue: false,
|
|
1886
|
-
generatePreview: true,
|
|
1887
|
-
silent: true,
|
|
1888
|
-
...options.throwOnSideEffect === void 0 ? {} : { throwOnSideEffect: options.throwOnSideEffect },
|
|
1889
|
-
...options.objectGroup === void 0 ? {} : { objectGroup: options.objectGroup }
|
|
1890
|
-
});
|
|
1891
|
-
}
|
|
1892
|
-
function isSideEffectRefusal(result) {
|
|
1893
|
-
const classNames = [
|
|
1894
|
-
result.result?.className,
|
|
1895
|
-
result.exceptionDetails?.exception?.className
|
|
1896
|
-
];
|
|
1897
|
-
const descriptions = [
|
|
1898
|
-
result.result?.description,
|
|
1899
|
-
result.exceptionDetails?.exception?.description
|
|
1900
|
-
];
|
|
1901
|
-
const isEvalError = classNames.includes("EvalError");
|
|
1902
|
-
return isEvalError && descriptions.some(
|
|
1903
|
-
(description) => typeof description === "string" && description.toLowerCase().includes("possible side-effect in debug-evaluate")
|
|
1904
|
-
);
|
|
1905
|
-
}
|
|
1906
|
-
async function evaluateGlobal(session, expression) {
|
|
1907
|
-
return await session.client.send("Runtime.evaluate", {
|
|
1908
|
-
expression,
|
|
1909
|
-
returnByValue: false,
|
|
1910
|
-
generatePreview: true,
|
|
1911
|
-
silent: true
|
|
1912
|
-
});
|
|
1913
|
-
}
|
|
1914
|
-
async function runSetupEvals(session, expressions) {
|
|
1915
|
-
for (const expression of expressions) {
|
|
1916
|
-
const result = await evaluateGlobal(session, expression);
|
|
1917
|
-
if (result.exceptionDetails !== void 0) {
|
|
1918
|
-
throw new CfInspectorError(
|
|
1919
|
-
"SETUP_EVAL_FAILED",
|
|
1920
|
-
exceptionDetailsMessage(result, "setup evaluation failed")
|
|
1921
|
-
);
|
|
1922
|
-
}
|
|
1923
|
-
}
|
|
1924
|
-
}
|
|
1925
|
-
function exceptionDetailsMessage(result, fallback) {
|
|
1926
|
-
return typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : fallback;
|
|
1927
|
-
}
|
|
1928
|
-
function listScripts(session) {
|
|
1929
|
-
return [...session.scripts.values()];
|
|
1930
|
-
}
|
|
1931
|
-
async function validateExpression(session, expression) {
|
|
1932
|
-
const result = await session.client.send("Runtime.compileScript", {
|
|
1933
|
-
expression,
|
|
1934
|
-
sourceURL: "<cf-inspector-validate>",
|
|
1935
|
-
persistScript: false
|
|
1936
|
-
});
|
|
1937
|
-
if (result.exceptionDetails === void 0) {
|
|
1938
|
-
return;
|
|
1939
|
-
}
|
|
1940
|
-
const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
|
|
1941
|
-
throw new CfInspectorError("INVALID_EXPRESSION", description);
|
|
1942
|
-
}
|
|
1943
|
-
async function getProperties(session, objectId) {
|
|
1944
|
-
const result = await session.client.send("Runtime.getProperties", {
|
|
1945
|
-
objectId,
|
|
1946
|
-
ownProperties: true,
|
|
1947
|
-
accessorPropertiesOnly: false,
|
|
1948
|
-
generatePreview: true
|
|
1949
|
-
});
|
|
1950
|
-
if (!Array.isArray(result.result)) {
|
|
1951
|
-
return [];
|
|
1952
|
-
}
|
|
1953
|
-
return result.result;
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
// src/cli/commands/eval.ts
|
|
1957
|
-
async function handleEval(opts) {
|
|
1958
|
-
warnOnMutationRisk(opts.expr, "eval --expr");
|
|
1959
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
1960
|
-
const result = await withSession(target, async (session) => {
|
|
1961
|
-
return await evaluateGlobal(session, opts.expr);
|
|
1962
|
-
});
|
|
1963
|
-
if (opts.json) {
|
|
1964
|
-
writeJson(result);
|
|
1965
|
-
if (result.exceptionDetails !== void 0) {
|
|
1966
|
-
process4.exitCode = 1;
|
|
1967
|
-
}
|
|
1968
|
-
return;
|
|
1969
|
-
}
|
|
1970
|
-
writeHumanEvalResult(result);
|
|
1971
|
-
}
|
|
1972
|
-
function writeHumanEvalResult(result) {
|
|
1973
|
-
if (result.exceptionDetails !== void 0) {
|
|
1974
|
-
const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
1975
|
-
process4.stderr.write(`${detail}
|
|
1976
|
-
`);
|
|
1977
|
-
process4.exitCode = 1;
|
|
1978
|
-
return;
|
|
1979
|
-
}
|
|
1980
|
-
const inner = result.result;
|
|
1981
|
-
if (inner === void 0) {
|
|
1982
|
-
process4.stdout.write("\n");
|
|
1983
|
-
return;
|
|
1984
|
-
}
|
|
1985
|
-
if (typeof inner.value === "string") {
|
|
1986
|
-
process4.stdout.write(`${inner.value}
|
|
1987
|
-
`);
|
|
1988
|
-
return;
|
|
1989
|
-
}
|
|
1990
|
-
if (typeof inner.description === "string") {
|
|
1991
|
-
process4.stdout.write(`${inner.description}
|
|
1992
|
-
`);
|
|
1993
|
-
return;
|
|
1994
|
-
}
|
|
1995
|
-
process4.stdout.write(`${JSON.stringify(inner.value)}
|
|
1996
|
-
`);
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
// src/cli/commands/exception.ts
|
|
2000
|
-
import { performance as performance4 } from "perf_hooks";
|
|
2001
|
-
import process5 from "process";
|
|
2002
|
-
|
|
2003
|
-
// src/pathMapper.ts
|
|
2004
|
-
init_types();
|
|
2005
|
-
var REGEX_PREFIX = "regex:";
|
|
2006
|
-
var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
|
|
2007
|
-
var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
|
|
2008
|
-
function parseBreakpointSpec(input) {
|
|
2009
|
-
const idx = input.lastIndexOf(":");
|
|
2010
|
-
if (idx <= 0 || idx === input.length - 1) {
|
|
2011
|
-
throw new CfInspectorError(
|
|
2012
|
-
"INVALID_BREAKPOINT",
|
|
2013
|
-
`Breakpoint must be in 'file:line' form, received: "${input}"`
|
|
2014
|
-
);
|
|
2015
|
-
}
|
|
2016
|
-
const file = input.slice(0, idx).trim();
|
|
2017
|
-
const lineRaw = input.slice(idx + 1).trim();
|
|
2018
|
-
const line = Number.parseInt(lineRaw, 10);
|
|
2019
|
-
if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
|
|
2020
|
-
throw new CfInspectorError(
|
|
2021
|
-
"INVALID_BREAKPOINT",
|
|
2022
|
-
`Breakpoint line must be a positive integer, received: "${lineRaw}"`
|
|
2023
|
-
);
|
|
2024
|
-
}
|
|
2025
|
-
if (file.length === 0) {
|
|
2026
|
-
throw new CfInspectorError(
|
|
2027
|
-
"INVALID_BREAKPOINT",
|
|
2028
|
-
`Breakpoint file path is empty in "${input}"`
|
|
2029
|
-
);
|
|
2030
|
-
}
|
|
2031
|
-
return { file, line };
|
|
2032
|
-
}
|
|
2033
|
-
function parseRemoteRoot(value) {
|
|
2034
|
-
const trimmed = value?.trim();
|
|
2035
|
-
if (trimmed === void 0 || trimmed.length === 0) {
|
|
2036
|
-
return { kind: "none" };
|
|
2037
|
-
}
|
|
2038
|
-
if (trimmed.startsWith(REGEX_PREFIX)) {
|
|
2039
|
-
return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
|
|
2040
|
-
}
|
|
2041
|
-
const slashRegex = parseSlashDelimited(trimmed);
|
|
2042
|
-
if (slashRegex !== void 0) {
|
|
2043
|
-
return toRegex(slashRegex.pattern, slashRegex.flags);
|
|
2044
|
-
}
|
|
2045
|
-
return { kind: "literal", value: stripTrailingSlash(trimmed) };
|
|
2046
|
-
}
|
|
2047
|
-
function toRegex(pattern, flags) {
|
|
2048
|
-
return { kind: "regex", pattern, flags };
|
|
2323
|
+
function toRegex(pattern, flags) {
|
|
2324
|
+
return { kind: "regex", pattern, flags };
|
|
2049
2325
|
}
|
|
2050
2326
|
function parseSlashDelimited(value) {
|
|
2051
2327
|
if (!value.startsWith("/")) {
|
|
@@ -2207,6 +2483,244 @@ async function setBreakpoint(session, input) {
|
|
|
2207
2483
|
async function removeBreakpoint(session, breakpointId) {
|
|
2208
2484
|
await session.client.send("Debugger.removeBreakpoint", { breakpointId });
|
|
2209
2485
|
}
|
|
2486
|
+
function validateCoordinate(value, label) {
|
|
2487
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
2488
|
+
throw new CfInspectorError(
|
|
2489
|
+
"INVALID_ARGUMENT",
|
|
2490
|
+
`${label} must be a non-negative integer, received: ${value.toString()}`
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
function validateScriptLocation(location, label) {
|
|
2495
|
+
if (location.scriptId.trim().length === 0) {
|
|
2496
|
+
throw new CfInspectorError("INVALID_ARGUMENT", `${label}.scriptId must not be empty`);
|
|
2497
|
+
}
|
|
2498
|
+
validateCoordinate(location.lineNumber, `${label}.lineNumber`);
|
|
2499
|
+
if (location.columnNumber !== void 0) {
|
|
2500
|
+
validateCoordinate(location.columnNumber, `${label}.columnNumber`);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
async function getPossibleBreakpoints(session, options) {
|
|
2504
|
+
validateScriptLocation(options.start, "start");
|
|
2505
|
+
if (options.end !== void 0) {
|
|
2506
|
+
validateScriptLocation(options.end, "end");
|
|
2507
|
+
if (options.end.scriptId !== options.start.scriptId) {
|
|
2508
|
+
throw new CfInspectorError("INVALID_ARGUMENT", "start and end must refer to the same scriptId");
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
const result = await session.client.send(
|
|
2512
|
+
"Debugger.getPossibleBreakpoints",
|
|
2513
|
+
{
|
|
2514
|
+
start: options.start,
|
|
2515
|
+
...options.end === void 0 ? {} : { end: options.end },
|
|
2516
|
+
...options.restrictToFunction === void 0 ? {} : { restrictToFunction: options.restrictToFunction }
|
|
2517
|
+
}
|
|
2518
|
+
);
|
|
2519
|
+
if (!Array.isArray(result.locations)) {
|
|
2520
|
+
throw new CfInspectorError(
|
|
2521
|
+
"CDP_REQUEST_FAILED",
|
|
2522
|
+
"Debugger.getPossibleBreakpoints did not return a locations array"
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
return toBreakLocations(result.locations);
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// src/cli/commands/checkBreakpoint.ts
|
|
2529
|
+
async function handleCheckBreakpoint(opts) {
|
|
2530
|
+
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2531
|
+
const location = parseBreakpointSpec(opts.bp);
|
|
2532
|
+
const remoteRoot = parseRemoteRoot(opts.remoteRoot);
|
|
2533
|
+
const urlRegex = buildBreakpointUrlRegex({ file: location.file, remoteRoot });
|
|
2534
|
+
const matcher = new RegExp(urlRegex, "u");
|
|
2535
|
+
const result = await withSessions(target, async (group) => {
|
|
2536
|
+
const checks = (await Promise.all(group.list().map(async (session) => await checkSession(session, matcher, location.line)))).flat();
|
|
2537
|
+
const status = checks.length === 0 ? "script-not-loaded" : checks.some((check) => check.locations.length > 0) ? "breakable" : "unbreakable";
|
|
2538
|
+
return { file: location.file, line: location.line, status, scripts: checks };
|
|
2539
|
+
});
|
|
2540
|
+
if (opts.json) {
|
|
2541
|
+
writeJson(result);
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
writeHumanCheck(result);
|
|
2545
|
+
}
|
|
2546
|
+
async function checkSession(session, matcher, requestedLine) {
|
|
2547
|
+
const zeroBasedLine = requestedLine - 1;
|
|
2548
|
+
const scripts = [...session.scripts.values()].filter((script) => matcher.test(script.url));
|
|
2549
|
+
return await Promise.all(scripts.map(async (script) => {
|
|
2550
|
+
const locations = await getPossibleBreakpoints(session, {
|
|
2551
|
+
start: { scriptId: script.scriptId, lineNumber: zeroBasedLine, columnNumber: 0 },
|
|
2552
|
+
end: { scriptId: script.scriptId, lineNumber: zeroBasedLine + 1, columnNumber: 0 }
|
|
2553
|
+
});
|
|
2554
|
+
return {
|
|
2555
|
+
isolate: session.isolate ?? { kind: "main" },
|
|
2556
|
+
scriptId: script.scriptId,
|
|
2557
|
+
url: script.url,
|
|
2558
|
+
locations: locations.filter((candidate) => candidate.lineNumber === zeroBasedLine)
|
|
2559
|
+
};
|
|
2560
|
+
}));
|
|
2561
|
+
}
|
|
2562
|
+
function writeHumanCheck(result) {
|
|
2563
|
+
if (result.status === "script-not-loaded") {
|
|
2564
|
+
process4.stdout.write(
|
|
2565
|
+
`${result.file}:${result.line.toString()} does not match any loaded script. Run list-scripts and check --remote-root/path mapping, or trigger lazy module loading first.
|
|
2566
|
+
`
|
|
2567
|
+
);
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
if (result.status === "unbreakable") {
|
|
2571
|
+
process4.stdout.write(
|
|
2572
|
+
`${result.file}:${result.line.toString()} matches a loaded script, but this exact line has no breakable location. Try a neighboring executable line.
|
|
2573
|
+
`
|
|
2574
|
+
);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
process4.stdout.write(`${result.file}:${result.line.toString()} is breakable:
|
|
2578
|
+
`);
|
|
2579
|
+
for (const script of result.scripts) {
|
|
2580
|
+
for (const location of script.locations) {
|
|
2581
|
+
const isolate = script.isolate.kind === "main" ? "main" : `worker ${script.isolate.workerId}`;
|
|
2582
|
+
process4.stdout.write(
|
|
2583
|
+
` ${isolate} ${script.url} line ${(location.lineNumber + 1).toString()}:${((location.columnNumber ?? 0) + 1).toString()}
|
|
2584
|
+
`
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
// src/cli/commands/eval.ts
|
|
2591
|
+
import process5 from "process";
|
|
2592
|
+
|
|
2593
|
+
// src/inspector/runtime.ts
|
|
2594
|
+
init_types();
|
|
2595
|
+
async function resume(session) {
|
|
2596
|
+
await session.client.send("Debugger.resume");
|
|
2597
|
+
session.debuggerState.paused = false;
|
|
2598
|
+
delete session.debuggerState.currentPause;
|
|
2599
|
+
}
|
|
2600
|
+
async function setPauseOnExceptions(session, state) {
|
|
2601
|
+
await session.client.send("Debugger.setPauseOnExceptions", { state });
|
|
2602
|
+
}
|
|
2603
|
+
async function evaluateOnFrame(session, callFrameId, expression, options = {}) {
|
|
2604
|
+
return await session.client.send("Debugger.evaluateOnCallFrame", {
|
|
2605
|
+
callFrameId,
|
|
2606
|
+
expression,
|
|
2607
|
+
returnByValue: false,
|
|
2608
|
+
generatePreview: true,
|
|
2609
|
+
silent: true,
|
|
2610
|
+
...options.throwOnSideEffect === void 0 ? {} : { throwOnSideEffect: options.throwOnSideEffect },
|
|
2611
|
+
...options.objectGroup === void 0 ? {} : { objectGroup: options.objectGroup }
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
function isSideEffectRefusal(result) {
|
|
2615
|
+
const classNames = [
|
|
2616
|
+
result.result?.className,
|
|
2617
|
+
result.exceptionDetails?.exception?.className
|
|
2618
|
+
];
|
|
2619
|
+
const descriptions = [
|
|
2620
|
+
result.result?.description,
|
|
2621
|
+
result.exceptionDetails?.exception?.description
|
|
2622
|
+
];
|
|
2623
|
+
const isEvalError = classNames.includes("EvalError");
|
|
2624
|
+
return isEvalError && descriptions.some(
|
|
2625
|
+
(description) => typeof description === "string" && description.toLowerCase().includes("possible side-effect in debug-evaluate")
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
async function evaluateGlobal(session, expression) {
|
|
2629
|
+
return await session.client.send("Runtime.evaluate", {
|
|
2630
|
+
expression,
|
|
2631
|
+
returnByValue: false,
|
|
2632
|
+
generatePreview: true,
|
|
2633
|
+
silent: true
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
async function runSetupEvals(session, expressions) {
|
|
2637
|
+
for (const expression of expressions) {
|
|
2638
|
+
const result = await evaluateGlobal(session, expression);
|
|
2639
|
+
if (result.exceptionDetails !== void 0) {
|
|
2640
|
+
throw new CfInspectorError(
|
|
2641
|
+
"SETUP_EVAL_FAILED",
|
|
2642
|
+
exceptionDetailsMessage(result, "setup evaluation failed")
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
function exceptionDetailsMessage(result, fallback) {
|
|
2648
|
+
return typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : fallback;
|
|
2649
|
+
}
|
|
2650
|
+
function listScripts(session) {
|
|
2651
|
+
return [...session.scripts.values()];
|
|
2652
|
+
}
|
|
2653
|
+
async function validateExpression(session, expression) {
|
|
2654
|
+
const result = await session.client.send("Runtime.compileScript", {
|
|
2655
|
+
expression,
|
|
2656
|
+
sourceURL: "<cf-inspector-validate>",
|
|
2657
|
+
persistScript: false
|
|
2658
|
+
});
|
|
2659
|
+
if (result.exceptionDetails === void 0) {
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
|
|
2663
|
+
throw new CfInspectorError("INVALID_EXPRESSION", description);
|
|
2664
|
+
}
|
|
2665
|
+
async function getProperties(session, objectId) {
|
|
2666
|
+
const result = await session.client.send("Runtime.getProperties", {
|
|
2667
|
+
objectId,
|
|
2668
|
+
ownProperties: true,
|
|
2669
|
+
accessorPropertiesOnly: false,
|
|
2670
|
+
generatePreview: true
|
|
2671
|
+
});
|
|
2672
|
+
if (!Array.isArray(result.result)) {
|
|
2673
|
+
return [];
|
|
2674
|
+
}
|
|
2675
|
+
return result.result;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
// src/cli/commands/eval.ts
|
|
2679
|
+
async function handleEval(opts) {
|
|
2680
|
+
warnOnMutationRisk(opts.expr, "eval --expr");
|
|
2681
|
+
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2682
|
+
const result = await withSession(target, async (session) => {
|
|
2683
|
+
return await evaluateGlobal(session, opts.expr);
|
|
2684
|
+
});
|
|
2685
|
+
if (opts.json) {
|
|
2686
|
+
writeJson(result);
|
|
2687
|
+
if (result.exceptionDetails !== void 0) {
|
|
2688
|
+
process5.exitCode = 1;
|
|
2689
|
+
}
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
writeHumanEvalResult(result);
|
|
2693
|
+
}
|
|
2694
|
+
function writeHumanEvalResult(result) {
|
|
2695
|
+
if (result.exceptionDetails !== void 0) {
|
|
2696
|
+
const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
2697
|
+
process5.stderr.write(`${detail}
|
|
2698
|
+
`);
|
|
2699
|
+
process5.exitCode = 1;
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
const inner = result.result;
|
|
2703
|
+
if (inner === void 0) {
|
|
2704
|
+
process5.stdout.write("\n");
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
if (typeof inner.value === "string") {
|
|
2708
|
+
process5.stdout.write(`${inner.value}
|
|
2709
|
+
`);
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
if (typeof inner.description === "string") {
|
|
2713
|
+
process5.stdout.write(`${inner.description}
|
|
2714
|
+
`);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
process5.stdout.write(`${JSON.stringify(inner.value)}
|
|
2718
|
+
`);
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
// src/cli/commands/exception.ts
|
|
2722
|
+
import { performance as performance5 } from "perf_hooks";
|
|
2723
|
+
import process7 from "process";
|
|
2210
2724
|
|
|
2211
2725
|
// src/inspector/pause.ts
|
|
2212
2726
|
init_types();
|
|
@@ -2302,31 +2816,289 @@ async function waitForPause(session, options) {
|
|
|
2302
2816
|
if (pauseMatches(pause, options.breakpointIds, options.pauseReasons)) {
|
|
2303
2817
|
return pause;
|
|
2304
2818
|
}
|
|
2305
|
-
await handleUnmatchedPause(session, pause, options, deadlineMs);
|
|
2819
|
+
await handleUnmatchedPause(session, pause, options, deadlineMs);
|
|
2820
|
+
}
|
|
2821
|
+
throwBreakpointTimeout(options.timeoutMs);
|
|
2822
|
+
}
|
|
2823
|
+
async function waitForLivePause(session, options, deadlineMs) {
|
|
2824
|
+
const remainingMs = remainingUntil(deadlineMs);
|
|
2825
|
+
if (remainingMs <= 0) {
|
|
2826
|
+
throwBreakpointTimeout(options.timeoutMs);
|
|
2827
|
+
}
|
|
2828
|
+
session.pauseWaitGate.active = true;
|
|
2829
|
+
let receivedAtMs;
|
|
2830
|
+
let params;
|
|
2831
|
+
try {
|
|
2832
|
+
params = await session.client.waitFor("Debugger.paused", {
|
|
2833
|
+
timeoutMs: remainingMs,
|
|
2834
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
2835
|
+
predicate: () => {
|
|
2836
|
+
receivedAtMs = performance3.now();
|
|
2837
|
+
return true;
|
|
2838
|
+
}
|
|
2839
|
+
});
|
|
2840
|
+
} finally {
|
|
2841
|
+
session.pauseWaitGate.active = false;
|
|
2842
|
+
}
|
|
2843
|
+
return toPauseEvent(params, receivedAtMs ?? performance3.now(), session.scripts);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/inspector/fanout.ts
|
|
2847
|
+
init_types();
|
|
2848
|
+
import { performance as performance4 } from "perf_hooks";
|
|
2849
|
+
var DEFAULT_CLEANUP_TIMEOUT_MS = 2e3;
|
|
2850
|
+
var BreakpointFanout = class {
|
|
2851
|
+
records = /* @__PURE__ */ new Map();
|
|
2852
|
+
setupErrors = [];
|
|
2853
|
+
detach;
|
|
2854
|
+
detachRemoved;
|
|
2855
|
+
detachError;
|
|
2856
|
+
activeRace;
|
|
2857
|
+
pauseReasons = [];
|
|
2858
|
+
constructor(group, setupSession, pauseReasons = []) {
|
|
2859
|
+
this.pauseReasons = pauseReasons;
|
|
2860
|
+
this.detach = group.onSession((session) => {
|
|
2861
|
+
const record = { session, handles: [], setup: Promise.resolve() };
|
|
2862
|
+
this.records.set(session, record);
|
|
2863
|
+
record.setup = setupSession(session, (handle) => {
|
|
2864
|
+
this.trackHandle(session, handle);
|
|
2865
|
+
}).then((result) => {
|
|
2866
|
+
for (const handle of result.handles) {
|
|
2867
|
+
this.trackHandle(session, handle);
|
|
2868
|
+
}
|
|
2869
|
+
});
|
|
2870
|
+
const setup = record.setup;
|
|
2871
|
+
setup.catch((error) => {
|
|
2872
|
+
for (const reject of this.setupErrors) {
|
|
2873
|
+
reject(error);
|
|
2874
|
+
}
|
|
2875
|
+
});
|
|
2876
|
+
this.activeRace?.add(record);
|
|
2877
|
+
});
|
|
2878
|
+
this.detachRemoved = group.onSessionRemoved((session) => {
|
|
2879
|
+
this.records.delete(session);
|
|
2880
|
+
this.activeRace?.remove(session);
|
|
2881
|
+
});
|
|
2882
|
+
this.detachError = group.onError((error) => {
|
|
2883
|
+
for (const reject of this.setupErrors) {
|
|
2884
|
+
reject(error);
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
async ready() {
|
|
2889
|
+
await Promise.all([...this.records.values()].map((record) => record.setup));
|
|
2890
|
+
}
|
|
2891
|
+
trackHandle(session, handle) {
|
|
2892
|
+
const record = this.records.get(session);
|
|
2893
|
+
if (record !== void 0 && !record.handles.some((candidate) => candidate.breakpointId === handle.breakpointId)) {
|
|
2894
|
+
record.handles.push(handle);
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
availableOutcomes() {
|
|
2898
|
+
return [...this.records.values()].map((record) => ({
|
|
2899
|
+
session: record.session,
|
|
2900
|
+
setup: { handles: record.handles }
|
|
2901
|
+
}));
|
|
2902
|
+
}
|
|
2903
|
+
async waitForFirst(timeoutMs, options = {}, signal) {
|
|
2904
|
+
if (this.activeRace !== void 0) {
|
|
2905
|
+
throw new CfInspectorError("INVALID_ARGUMENT", "A fan-out pause race is already active");
|
|
2906
|
+
}
|
|
2907
|
+
const race = new ActivePauseRace(timeoutMs, options, signal);
|
|
2908
|
+
this.pauseReasons = options.pauseReasons ?? [];
|
|
2909
|
+
this.activeRace = race;
|
|
2910
|
+
this.setupErrors.push(race.reject);
|
|
2911
|
+
for (const record of this.records.values()) {
|
|
2912
|
+
race.add(record);
|
|
2913
|
+
}
|
|
2914
|
+
try {
|
|
2915
|
+
const winner = await race.result;
|
|
2916
|
+
await race.stopAndSettle();
|
|
2917
|
+
await this.resumePausedLosers(winner.session);
|
|
2918
|
+
return winner;
|
|
2919
|
+
} finally {
|
|
2920
|
+
this.activeRace = void 0;
|
|
2921
|
+
const index = this.setupErrors.indexOf(race.reject);
|
|
2922
|
+
if (index >= 0) {
|
|
2923
|
+
this.setupErrors.splice(index, 1);
|
|
2924
|
+
}
|
|
2925
|
+
await race.stopAndSettle();
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2928
|
+
async resumePaused(except) {
|
|
2929
|
+
return await this.resumePausedLosers(except, DEFAULT_CLEANUP_TIMEOUT_MS);
|
|
2930
|
+
}
|
|
2931
|
+
async cleanup(timeoutMs = DEFAULT_CLEANUP_TIMEOUT_MS, preservePaused) {
|
|
2932
|
+
this.detach();
|
|
2933
|
+
this.detachRemoved();
|
|
2934
|
+
this.detachError();
|
|
2935
|
+
const deadline = performance4.now() + timeoutMs;
|
|
2936
|
+
await settleWithin(Promise.allSettled([...this.records.values()].map(async (record) => {
|
|
2937
|
+
await record.setup;
|
|
2938
|
+
})), remaining(deadline));
|
|
2939
|
+
const breakpointEntries = [...this.records.values()].flatMap((record) => record.handles.map((handle) => ({
|
|
2940
|
+
session: record.session,
|
|
2941
|
+
breakpointId: handle.breakpointId
|
|
2942
|
+
})));
|
|
2943
|
+
let cleared = 0;
|
|
2944
|
+
const clearWork = Promise.allSettled(breakpointEntries.map(async (entry) => {
|
|
2945
|
+
await removeBreakpoint(entry.session, entry.breakpointId);
|
|
2946
|
+
cleared += 1;
|
|
2947
|
+
}));
|
|
2948
|
+
await settleWithin(clearWork, remaining(deadline));
|
|
2949
|
+
const resumed = await this.resumePausedLosers(preservePaused, remaining(deadline));
|
|
2950
|
+
return { attempted: breakpointEntries.length, cleared, resumed };
|
|
2951
|
+
}
|
|
2952
|
+
async resumePausedLosers(except, timeoutMs = DEFAULT_CLEANUP_TIMEOUT_MS) {
|
|
2953
|
+
let resumed = 0;
|
|
2954
|
+
await settleWithin(Promise.allSettled([...this.records.keys()].map(async (session) => {
|
|
2955
|
+
if (session === except || session.debuggerState.paused !== true || session.client.isClosed || !this.ownsCurrentPause(session)) {
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
await resume(session);
|
|
2959
|
+
session.debuggerState.paused = false;
|
|
2960
|
+
resumed += 1;
|
|
2961
|
+
})), timeoutMs);
|
|
2962
|
+
return resumed;
|
|
2963
|
+
}
|
|
2964
|
+
ownsCurrentPause(session) {
|
|
2965
|
+
const pause = session.debuggerState.currentPause;
|
|
2966
|
+
if (pause === void 0) {
|
|
2967
|
+
return false;
|
|
2968
|
+
}
|
|
2969
|
+
if (this.pauseReasons.includes(pause.reason)) {
|
|
2970
|
+
return true;
|
|
2971
|
+
}
|
|
2972
|
+
const record = this.records.get(session);
|
|
2973
|
+
const breakpointIds = new Set(record?.handles.map((handle) => handle.breakpointId) ?? []);
|
|
2974
|
+
return pause.hitBreakpoints.some((breakpointId) => breakpointIds.has(breakpointId));
|
|
2975
|
+
}
|
|
2976
|
+
};
|
|
2977
|
+
var ActivePauseRace = class {
|
|
2978
|
+
constructor(timeoutMs, options, signal) {
|
|
2979
|
+
this.options = options;
|
|
2980
|
+
this.externalSignal = signal;
|
|
2981
|
+
this.deadline = performance4.now() + timeoutMs;
|
|
2982
|
+
let resolveResult;
|
|
2983
|
+
let rejectResult;
|
|
2984
|
+
this.result = new Promise((resolve, reject) => {
|
|
2985
|
+
resolveResult = resolve;
|
|
2986
|
+
rejectResult = reject;
|
|
2987
|
+
});
|
|
2988
|
+
this.resolveResult = (winner) => {
|
|
2989
|
+
if (this.settled) {
|
|
2990
|
+
return;
|
|
2991
|
+
}
|
|
2992
|
+
this.settled = true;
|
|
2993
|
+
resolveResult?.(winner);
|
|
2994
|
+
};
|
|
2995
|
+
this.reject = (error) => {
|
|
2996
|
+
if (this.settled) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
this.settled = true;
|
|
3000
|
+
rejectResult?.(error);
|
|
3001
|
+
};
|
|
3002
|
+
this.timeout = setTimeout(() => {
|
|
3003
|
+
this.reject(this.terminalTimeoutError ?? new CfInspectorError(
|
|
3004
|
+
"BREAKPOINT_NOT_HIT",
|
|
3005
|
+
`Timed out waiting for a matching pause in any isolate after ${timeoutMs.toString()}ms`
|
|
3006
|
+
));
|
|
3007
|
+
this.controller.abort();
|
|
3008
|
+
}, timeoutMs + 25);
|
|
3009
|
+
if (signal !== void 0) {
|
|
3010
|
+
if (signal.aborted) {
|
|
3011
|
+
this.reject(new CfInspectorError("ABORTED", "Aborted while waiting for an isolate pause"));
|
|
3012
|
+
} else {
|
|
3013
|
+
signal.addEventListener("abort", this.onExternalAbort, { once: true });
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
options;
|
|
3018
|
+
controller = new AbortController();
|
|
3019
|
+
waits = /* @__PURE__ */ new Set();
|
|
3020
|
+
settled = false;
|
|
3021
|
+
deadline;
|
|
3022
|
+
timeout;
|
|
3023
|
+
externalSignal;
|
|
3024
|
+
onExternalAbort = () => {
|
|
3025
|
+
this.reject(new CfInspectorError("ABORTED", "Aborted while waiting for an isolate pause"));
|
|
3026
|
+
this.controller.abort();
|
|
3027
|
+
};
|
|
3028
|
+
resolveResult;
|
|
3029
|
+
terminalTimeoutError;
|
|
3030
|
+
removedSessions = /* @__PURE__ */ new Set();
|
|
3031
|
+
reject;
|
|
3032
|
+
result;
|
|
3033
|
+
add(record) {
|
|
3034
|
+
if (this.settled) {
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
const wait = this.wait(record).finally(() => {
|
|
3038
|
+
this.waits.delete(wait);
|
|
3039
|
+
});
|
|
3040
|
+
this.waits.add(wait);
|
|
3041
|
+
}
|
|
3042
|
+
remove(session) {
|
|
3043
|
+
this.removedSessions.add(session);
|
|
3044
|
+
}
|
|
3045
|
+
async stopAndSettle() {
|
|
3046
|
+
clearTimeout(this.timeout);
|
|
3047
|
+
this.externalSignal?.removeEventListener("abort", this.onExternalAbort);
|
|
3048
|
+
this.controller.abort();
|
|
3049
|
+
await Promise.allSettled([...this.waits]);
|
|
3050
|
+
}
|
|
3051
|
+
async wait(record) {
|
|
3052
|
+
try {
|
|
3053
|
+
await record.setup;
|
|
3054
|
+
const remainingMs = Math.max(1, this.deadline - performance4.now());
|
|
3055
|
+
const pause = await waitForPause(record.session, {
|
|
3056
|
+
...this.options,
|
|
3057
|
+
timeoutMs: remainingMs,
|
|
3058
|
+
breakpointIds: record.handles.map((handle) => handle.breakpointId),
|
|
3059
|
+
signal: this.controller.signal
|
|
3060
|
+
});
|
|
3061
|
+
record.session.debuggerState.paused = true;
|
|
3062
|
+
this.resolveResult({ session: record.session, pause });
|
|
3063
|
+
this.controller.abort();
|
|
3064
|
+
} catch (error) {
|
|
3065
|
+
if (this.removedSessions.has(record.session)) {
|
|
3066
|
+
return;
|
|
3067
|
+
}
|
|
3068
|
+
if (error instanceof CfInspectorError && error.code === "UNRELATED_PAUSE_TIMEOUT") {
|
|
3069
|
+
this.terminalTimeoutError = error;
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
if (isExpectedRaceStop(error)) {
|
|
3073
|
+
return;
|
|
3074
|
+
}
|
|
3075
|
+
this.reject(error);
|
|
3076
|
+
this.controller.abort();
|
|
3077
|
+
}
|
|
2306
3078
|
}
|
|
2307
|
-
|
|
3079
|
+
};
|
|
3080
|
+
function isExpectedRaceStop(error) {
|
|
3081
|
+
return error instanceof CfInspectorError && (error.code === "ABORTED" || error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT");
|
|
2308
3082
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
session.pauseWaitGate.active = true;
|
|
2315
|
-
let receivedAtMs;
|
|
2316
|
-
let params;
|
|
3083
|
+
function remaining(deadline) {
|
|
3084
|
+
return Math.max(0, deadline - performance4.now());
|
|
3085
|
+
}
|
|
3086
|
+
async function settleWithin(work, timeoutMs) {
|
|
3087
|
+
let timer;
|
|
2317
3088
|
try {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
}
|
|
2325
|
-
|
|
3089
|
+
return await Promise.race([
|
|
3090
|
+
work,
|
|
3091
|
+
new Promise((resolve) => {
|
|
3092
|
+
timer = setTimeout(() => {
|
|
3093
|
+
resolve(null);
|
|
3094
|
+
}, timeoutMs);
|
|
3095
|
+
})
|
|
3096
|
+
]);
|
|
2326
3097
|
} finally {
|
|
2327
|
-
|
|
3098
|
+
if (timer !== void 0) {
|
|
3099
|
+
clearTimeout(timer);
|
|
3100
|
+
}
|
|
2328
3101
|
}
|
|
2329
|
-
return toPauseEvent(params, receivedAtMs ?? performance3.now(), session.scripts);
|
|
2330
3102
|
}
|
|
2331
3103
|
|
|
2332
3104
|
// src/snapshot/evaluation.ts
|
|
@@ -3040,6 +3812,25 @@ async function captureExpression(session, callFrameId, expression, maxValueLengt
|
|
|
3040
3812
|
|
|
3041
3813
|
// src/cli/commands/exception.ts
|
|
3042
3814
|
init_types();
|
|
3815
|
+
|
|
3816
|
+
// src/cli/signals.ts
|
|
3817
|
+
import process6 from "process";
|
|
3818
|
+
async function withTerminationSignal(fn) {
|
|
3819
|
+
const abort = new AbortController();
|
|
3820
|
+
const onSignal = () => {
|
|
3821
|
+
abort.abort();
|
|
3822
|
+
};
|
|
3823
|
+
process6.once("SIGINT", onSignal);
|
|
3824
|
+
process6.once("SIGTERM", onSignal);
|
|
3825
|
+
try {
|
|
3826
|
+
return await fn(abort.signal);
|
|
3827
|
+
} finally {
|
|
3828
|
+
process6.off("SIGINT", onSignal);
|
|
3829
|
+
process6.off("SIGTERM", onSignal);
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
// src/cli/commands/exception.ts
|
|
3043
3834
|
var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
|
|
3044
3835
|
async function handleException(opts) {
|
|
3045
3836
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
@@ -3048,7 +3839,7 @@ async function handleException(opts) {
|
|
|
3048
3839
|
[...prepared.captures, ...prepared.stackCaptures],
|
|
3049
3840
|
opts.allowMutation === true
|
|
3050
3841
|
);
|
|
3051
|
-
const result = await runExceptionCommand(prepared, opts);
|
|
3842
|
+
const result = await withTerminationSignal(async (signal) => await runExceptionCommand(prepared, opts, signal));
|
|
3052
3843
|
if (opts.json) {
|
|
3053
3844
|
writeJson(result);
|
|
3054
3845
|
} else {
|
|
@@ -3078,17 +3869,24 @@ function prepareExceptionCommand(opts, target) {
|
|
|
3078
3869
|
throwOnSideEffect: opts.allowMutation !== true
|
|
3079
3870
|
};
|
|
3080
3871
|
}
|
|
3081
|
-
async function runExceptionCommand(command, opts) {
|
|
3082
|
-
return await
|
|
3083
|
-
|
|
3872
|
+
async function runExceptionCommand(command, opts, signal) {
|
|
3873
|
+
return await withSessions(command.target, async (group) => {
|
|
3874
|
+
const fanout = new BreakpointFanout(group, async (session) => {
|
|
3875
|
+
await setPauseOnExceptions(session, command.state);
|
|
3876
|
+
return { handles: [] };
|
|
3877
|
+
}, ["exception", "promiseRejection"]);
|
|
3878
|
+
let winner;
|
|
3879
|
+
let preserveWinner = false;
|
|
3084
3880
|
try {
|
|
3085
|
-
|
|
3086
|
-
|
|
3881
|
+
await fanout.ready();
|
|
3882
|
+
const hit = await fanout.waitForFirst(command.timeoutMs, {
|
|
3087
3883
|
pauseReasons: ["exception", "promiseRejection"],
|
|
3088
3884
|
unmatchedPausePolicy: "wait-for-resume"
|
|
3089
|
-
});
|
|
3090
|
-
|
|
3091
|
-
const
|
|
3885
|
+
}, signal);
|
|
3886
|
+
winner = hit.session;
|
|
3887
|
+
const pause = hit.pause;
|
|
3888
|
+
const pausedStartedAt = pause.receivedAtMs ?? performance5.now();
|
|
3889
|
+
const snapshot = await captureSnapshot(hit.session, pause, {
|
|
3092
3890
|
captures: command.captures,
|
|
3093
3891
|
includeScopes: opts.includeScopes === true,
|
|
3094
3892
|
maxValueLength: command.maxValueLength,
|
|
@@ -3097,20 +3895,25 @@ async function runExceptionCommand(command, opts) {
|
|
|
3097
3895
|
throwOnSideEffect: command.throwOnSideEffect
|
|
3098
3896
|
});
|
|
3099
3897
|
if (opts.keepPaused === true) {
|
|
3100
|
-
|
|
3898
|
+
preserveWinner = true;
|
|
3899
|
+
return { ...withPausedDuration(snapshot, null), isolate: hit.session.isolate ?? { kind: "main" } };
|
|
3101
3900
|
}
|
|
3102
|
-
|
|
3901
|
+
const result = await resumeAfterException(hit.session, snapshot, pausedStartedAt);
|
|
3902
|
+
return { ...result, isolate: hit.session.isolate ?? { kind: "main" } };
|
|
3103
3903
|
} finally {
|
|
3104
|
-
await
|
|
3904
|
+
await Promise.allSettled(group.list().map(async (session) => {
|
|
3905
|
+
await disablePauseOnExceptionsBestEffort(session);
|
|
3906
|
+
}));
|
|
3907
|
+
await fanout.cleanup(2e3, preserveWinner ? winner : void 0);
|
|
3105
3908
|
}
|
|
3106
|
-
});
|
|
3909
|
+
}, void 0, signal);
|
|
3107
3910
|
}
|
|
3108
3911
|
async function resumeAfterException(session, snapshot, pausedStartedAt) {
|
|
3109
3912
|
try {
|
|
3110
3913
|
await resume(session);
|
|
3111
|
-
return withPausedDuration(snapshot, roundDurationMs(
|
|
3914
|
+
return withPausedDuration(snapshot, roundDurationMs(performance5.now() - pausedStartedAt));
|
|
3112
3915
|
} catch {
|
|
3113
|
-
|
|
3916
|
+
process7.stderr.write(
|
|
3114
3917
|
"[cf-inspector] warning: Debugger.resume failed after exception capture; pausedDurationMs is unknown.\n"
|
|
3115
3918
|
);
|
|
3116
3919
|
return withPausedDuration(snapshot, null);
|
|
@@ -3124,7 +3927,7 @@ async function disablePauseOnExceptionsBestEffort(session) {
|
|
|
3124
3927
|
}
|
|
3125
3928
|
|
|
3126
3929
|
// src/cli/commands/listScripts.ts
|
|
3127
|
-
import
|
|
3930
|
+
import process8 from "process";
|
|
3128
3931
|
async function handleListScripts(opts) {
|
|
3129
3932
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
3130
3933
|
const filter = compileScriptUrlFilter(opts.filter);
|
|
@@ -3134,7 +3937,7 @@ async function handleListScripts(opts) {
|
|
|
3134
3937
|
return;
|
|
3135
3938
|
}
|
|
3136
3939
|
for (const script of scripts) {
|
|
3137
|
-
|
|
3940
|
+
process8.stdout.write(`${script.scriptId} ${script.url}
|
|
3138
3941
|
`);
|
|
3139
3942
|
}
|
|
3140
3943
|
}
|
|
@@ -3165,7 +3968,7 @@ async function buildListedTargets(targets) {
|
|
|
3165
3968
|
return buildListedTarget(target, index, workerResult.supported, workerResult.workers);
|
|
3166
3969
|
} catch (error) {
|
|
3167
3970
|
const message = error instanceof Error ? error.message : String(error);
|
|
3168
|
-
|
|
3971
|
+
process8.stderr.write(
|
|
3169
3972
|
`[cf-inspector] warning: worker discovery failed for raw target ${index.toString()}: ${message}
|
|
3170
3973
|
`
|
|
3171
3974
|
);
|
|
@@ -3192,7 +3995,7 @@ function looksLikeWorkerTarget(target) {
|
|
|
3192
3995
|
return `${target.type} ${target.title} ${target.url}`.toLowerCase().includes("worker");
|
|
3193
3996
|
}
|
|
3194
3997
|
function writeTargetCountSummary(targetCount, workerCount) {
|
|
3195
|
-
|
|
3998
|
+
process8.stderr.write(
|
|
3196
3999
|
`[cf-inspector] ${targetCount.toString()} raw inspector ${targetCount === 1 ? "target" : "targets"}; ${workerCount.toString()} ${workerCount === 1 ? "worker" : "workers"}.
|
|
3197
4000
|
`
|
|
3198
4001
|
);
|
|
@@ -3203,7 +4006,7 @@ function warnOnMissingWorkers(targetCount, workerCount, targets) {
|
|
|
3203
4006
|
}
|
|
3204
4007
|
const supported = targets[0]?.workerDiscoverySupported === true;
|
|
3205
4008
|
const supportHint = supported ? "NodeWorker discovery is available, but no live worker attached." : "This runtime did not expose NodeWorker discovery.";
|
|
3206
|
-
|
|
4009
|
+
process8.stderr.write(
|
|
3207
4010
|
`[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.
|
|
3208
4011
|
`
|
|
3209
4012
|
);
|
|
@@ -3211,12 +4014,12 @@ function warnOnMissingWorkers(targetCount, workerCount, targets) {
|
|
|
3211
4014
|
function writeHumanTargets(targets) {
|
|
3212
4015
|
for (const target of targets) {
|
|
3213
4016
|
const workerLabel = target.likelyWorker ? " likely-worker" : "";
|
|
3214
|
-
|
|
4017
|
+
process8.stdout.write(
|
|
3215
4018
|
`${target.index.toString()} target ${target.type} ${target.title} ${target.url}${workerLabel}
|
|
3216
4019
|
`
|
|
3217
4020
|
);
|
|
3218
4021
|
for (const worker of target.workers) {
|
|
3219
|
-
|
|
4022
|
+
process8.stdout.write(
|
|
3220
4023
|
` ${worker.index.toString()} worker ${worker.type} ${worker.title} ${worker.url}
|
|
3221
4024
|
`
|
|
3222
4025
|
);
|
|
@@ -3293,7 +4096,7 @@ function matchesFilterTokens(value, tokens) {
|
|
|
3293
4096
|
}
|
|
3294
4097
|
|
|
3295
4098
|
// src/cli/commands/log.ts
|
|
3296
|
-
import
|
|
4099
|
+
import process9 from "process";
|
|
3297
4100
|
|
|
3298
4101
|
// src/logpoint/stream.ts
|
|
3299
4102
|
init_types();
|
|
@@ -3559,25 +4362,6 @@ async function waitForStop(session, options, registerMaxEventsSignal) {
|
|
|
3559
4362
|
|
|
3560
4363
|
// src/cli/commands/log.ts
|
|
3561
4364
|
init_types();
|
|
3562
|
-
|
|
3563
|
-
// src/cli/signals.ts
|
|
3564
|
-
import process7 from "process";
|
|
3565
|
-
async function withTerminationSignal(fn) {
|
|
3566
|
-
const abort = new AbortController();
|
|
3567
|
-
const onSignal = () => {
|
|
3568
|
-
abort.abort();
|
|
3569
|
-
};
|
|
3570
|
-
process7.once("SIGINT", onSignal);
|
|
3571
|
-
process7.once("SIGTERM", onSignal);
|
|
3572
|
-
try {
|
|
3573
|
-
return await fn(abort.signal);
|
|
3574
|
-
} finally {
|
|
3575
|
-
process7.off("SIGINT", onSignal);
|
|
3576
|
-
process7.off("SIGTERM", onSignal);
|
|
3577
|
-
}
|
|
3578
|
-
}
|
|
3579
|
-
|
|
3580
|
-
// src/cli/commands/log.ts
|
|
3581
4365
|
async function handleLog(opts) {
|
|
3582
4366
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
3583
4367
|
const location = parseBreakpointSpec(opts.at);
|
|
@@ -3596,12 +4380,8 @@ async function handleLog(opts) {
|
|
|
3596
4380
|
warnOnMutationRisk(condition, "log --condition");
|
|
3597
4381
|
}
|
|
3598
4382
|
await withTerminationSignal(async (signal) => {
|
|
3599
|
-
await
|
|
3600
|
-
await
|
|
3601
|
-
if (condition !== void 0) {
|
|
3602
|
-
await validateExpression(session, condition);
|
|
3603
|
-
}
|
|
3604
|
-
const result = await streamLogpoint(session, {
|
|
4383
|
+
await withSessions(target, async (group) => {
|
|
4384
|
+
const result = await runLogGroup(group, {
|
|
3605
4385
|
location,
|
|
3606
4386
|
expression,
|
|
3607
4387
|
remoteRoot,
|
|
@@ -3610,36 +4390,132 @@ async function handleLog(opts) {
|
|
|
3610
4390
|
...hitCount === void 0 ? {} : { hitCount },
|
|
3611
4391
|
...condition === void 0 ? {} : { condition },
|
|
3612
4392
|
maxValueLength,
|
|
3613
|
-
|
|
4393
|
+
json: opts.json,
|
|
4394
|
+
signal
|
|
4395
|
+
});
|
|
4396
|
+
writeLogSummary(result.stoppedReason, result.emitted, opts.json);
|
|
4397
|
+
}, void 0, signal);
|
|
4398
|
+
});
|
|
4399
|
+
}
|
|
4400
|
+
async function runLogGroup(group, options) {
|
|
4401
|
+
const controller = new AbortController();
|
|
4402
|
+
const tasks = /* @__PURE__ */ new Set();
|
|
4403
|
+
const removedSessions = /* @__PURE__ */ new Set();
|
|
4404
|
+
const results = [];
|
|
4405
|
+
let fatalError;
|
|
4406
|
+
let emitted = 0;
|
|
4407
|
+
let reason = "signal";
|
|
4408
|
+
let resolveStop;
|
|
4409
|
+
const stopped = new Promise((resolve) => {
|
|
4410
|
+
resolveStop = resolve;
|
|
4411
|
+
});
|
|
4412
|
+
const finish = (nextReason) => {
|
|
4413
|
+
if (controller.signal.aborted) {
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
reason = nextReason;
|
|
4417
|
+
controller.abort();
|
|
4418
|
+
resolveStop?.();
|
|
4419
|
+
};
|
|
4420
|
+
const onSignal = () => {
|
|
4421
|
+
finish("signal");
|
|
4422
|
+
};
|
|
4423
|
+
options.signal.addEventListener("abort", onSignal, { once: true });
|
|
4424
|
+
if (options.signal.aborted) {
|
|
4425
|
+
finish("signal");
|
|
4426
|
+
}
|
|
4427
|
+
const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
|
|
4428
|
+
finish("duration");
|
|
4429
|
+
}, options.durationMs);
|
|
4430
|
+
const startSession = (session) => {
|
|
4431
|
+
if (controller.signal.aborted) {
|
|
4432
|
+
return;
|
|
4433
|
+
}
|
|
4434
|
+
const task = (async () => {
|
|
4435
|
+
await validateExpression(session, options.expression);
|
|
4436
|
+
if (options.condition !== void 0) {
|
|
4437
|
+
await validateExpression(session, options.condition);
|
|
4438
|
+
}
|
|
4439
|
+
return await streamLogpoint(session, {
|
|
4440
|
+
location: options.location,
|
|
4441
|
+
expression: options.expression,
|
|
4442
|
+
remoteRoot: options.remoteRoot,
|
|
4443
|
+
...options.hitCount === void 0 ? {} : { hitCount: options.hitCount },
|
|
4444
|
+
...options.condition === void 0 ? {} : { condition: options.condition },
|
|
4445
|
+
maxValueLength: options.maxValueLength,
|
|
4446
|
+
signal: controller.signal,
|
|
3614
4447
|
onEvent: (event) => {
|
|
3615
|
-
|
|
4448
|
+
if (controller.signal.aborted) {
|
|
4449
|
+
return;
|
|
4450
|
+
}
|
|
4451
|
+
emitted += 1;
|
|
4452
|
+
writeLogEvent({ ...event, isolate: session.isolate ?? { kind: "main" } }, options.json);
|
|
4453
|
+
if (options.maxEvents !== void 0 && emitted >= options.maxEvents) {
|
|
4454
|
+
finish("max-events");
|
|
4455
|
+
}
|
|
3616
4456
|
},
|
|
3617
4457
|
onBreakpointSet: (handle) => {
|
|
3618
4458
|
warnOnUnboundBreakpoints([handle]);
|
|
3619
4459
|
}
|
|
3620
4460
|
});
|
|
3621
|
-
|
|
3622
|
-
|
|
4461
|
+
})();
|
|
4462
|
+
tasks.add(task);
|
|
4463
|
+
void task.then(
|
|
4464
|
+
(result) => {
|
|
4465
|
+
results.push(result);
|
|
4466
|
+
if (result.stoppedReason === "transport-closed" && !removedSessions.has(session)) {
|
|
4467
|
+
finish("transport-closed");
|
|
4468
|
+
}
|
|
4469
|
+
},
|
|
4470
|
+
(error) => {
|
|
4471
|
+
fatalError = error;
|
|
4472
|
+
finish("transport-closed");
|
|
3623
4473
|
}
|
|
3624
|
-
|
|
3625
|
-
|
|
4474
|
+
).finally(() => {
|
|
4475
|
+
tasks.delete(task);
|
|
4476
|
+
});
|
|
4477
|
+
};
|
|
4478
|
+
const detach = group.onSession(startSession);
|
|
4479
|
+
const detachRemoved = group.onSessionRemoved((session) => {
|
|
4480
|
+
removedSessions.add(session);
|
|
3626
4481
|
});
|
|
4482
|
+
try {
|
|
4483
|
+
await stopped;
|
|
4484
|
+
await Promise.allSettled([...tasks]);
|
|
4485
|
+
} finally {
|
|
4486
|
+
detach();
|
|
4487
|
+
detachRemoved();
|
|
4488
|
+
if (timer !== void 0) {
|
|
4489
|
+
clearTimeout(timer);
|
|
4490
|
+
}
|
|
4491
|
+
options.signal.removeEventListener("abort", onSignal);
|
|
4492
|
+
}
|
|
4493
|
+
if (emitted === 0 && isZeroHitStop(reason)) {
|
|
4494
|
+
warnOnBoundBreakpointWithoutHit(results.map((result) => result.handle));
|
|
4495
|
+
}
|
|
4496
|
+
if (fatalError !== void 0) {
|
|
4497
|
+
throw fatalError instanceof Error ? fatalError : new Error("Unknown logpoint fan-out failure");
|
|
4498
|
+
}
|
|
4499
|
+
return { emitted, stoppedReason: reason };
|
|
4500
|
+
}
|
|
4501
|
+
function isZeroHitStop(reason) {
|
|
4502
|
+
return reason === "duration" || reason === "signal";
|
|
3627
4503
|
}
|
|
3628
4504
|
function writeLogSummary(stoppedReason, emitted, json) {
|
|
3629
4505
|
if (json) {
|
|
3630
|
-
|
|
4506
|
+
process9.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
|
|
3631
4507
|
`);
|
|
3632
4508
|
return;
|
|
3633
4509
|
}
|
|
3634
|
-
|
|
4510
|
+
process9.stderr.write(
|
|
3635
4511
|
`Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
|
|
3636
4512
|
`
|
|
3637
4513
|
);
|
|
3638
4514
|
}
|
|
3639
4515
|
|
|
3640
4516
|
// src/cli/commands/snapshot.ts
|
|
3641
|
-
import { performance as
|
|
3642
|
-
import
|
|
4517
|
+
import { performance as performance6 } from "perf_hooks";
|
|
4518
|
+
import process10 from "process";
|
|
3643
4519
|
init_types();
|
|
3644
4520
|
async function handleSnapshot(opts) {
|
|
3645
4521
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
@@ -3652,7 +4528,7 @@ async function handleSnapshot(opts) {
|
|
|
3652
4528
|
warnOnMutationRisk(expression, "snapshot --setup-eval");
|
|
3653
4529
|
}
|
|
3654
4530
|
const reportProgress = opts.quiet === true ? void 0 : writeProgress;
|
|
3655
|
-
const result = await runSnapshotCommand(prepared, opts, reportProgress);
|
|
4531
|
+
const result = await withTerminationSignal(async (signal) => await runSnapshotCommand(prepared, opts, reportProgress, signal));
|
|
3656
4532
|
if (opts.json) {
|
|
3657
4533
|
writeJson(result);
|
|
3658
4534
|
} else {
|
|
@@ -3693,45 +4569,95 @@ function prepareSnapshotCommand(opts, target) {
|
|
|
3693
4569
|
throwOnSideEffect: opts.allowMutation !== true
|
|
3694
4570
|
};
|
|
3695
4571
|
}
|
|
3696
|
-
async function runSnapshotCommand(command, opts, reportProgress) {
|
|
3697
|
-
return await
|
|
3698
|
-
|
|
3699
|
-
|
|
4572
|
+
async function runSnapshotCommand(command, opts, reportProgress, signal) {
|
|
4573
|
+
return await withSessions(command.target, async (group) => {
|
|
4574
|
+
if (command.setupEvals.length > 0) {
|
|
4575
|
+
const setupCount = command.setupEvals.length;
|
|
4576
|
+
reportProgress?.(
|
|
4577
|
+
`Running ${setupCount.toString()} setup ${setupCount === 1 ? "evaluation" : "evaluations"}...`
|
|
4578
|
+
);
|
|
4579
|
+
}
|
|
4580
|
+
if (command.condition !== void 0) {
|
|
4581
|
+
reportProgress?.("Validating the breakpoint condition...");
|
|
4582
|
+
}
|
|
4583
|
+
const breakpointCount = command.breakpoints.length;
|
|
4584
|
+
reportProgress?.(
|
|
4585
|
+
`Setting ${breakpointCount.toString()} ${breakpointCount === 1 ? "breakpoint" : "breakpoints"}...`
|
|
4586
|
+
);
|
|
4587
|
+
const fanout = new BreakpointFanout(group, async (session, trackHandle) => {
|
|
4588
|
+
await prepareSnapshotSession(session, command);
|
|
4589
|
+
return { handles: await setCommandBreakpoints(session, command, trackHandle) };
|
|
4590
|
+
});
|
|
4591
|
+
let winner;
|
|
4592
|
+
let preserveWinner = false;
|
|
4593
|
+
try {
|
|
4594
|
+
await fanout.ready();
|
|
4595
|
+
if (command.setupEvals.length > 0) {
|
|
4596
|
+
reportProgress?.("Setup evaluation complete.");
|
|
4597
|
+
}
|
|
4598
|
+
if (command.condition !== void 0) {
|
|
4599
|
+
reportProgress?.("Breakpoint condition is valid.");
|
|
4600
|
+
}
|
|
4601
|
+
const outcomes = fanout.availableOutcomes();
|
|
4602
|
+
reportBreakpointOutcomes(outcomes, reportProgress);
|
|
4603
|
+
reportProgress?.(
|
|
4604
|
+
`Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
|
|
4605
|
+
);
|
|
4606
|
+
const hit = await fanout.waitForFirst(command.timeoutMs, {
|
|
4607
|
+
unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
|
|
4608
|
+
...opts.failOnUnmatchedPause === true ? {} : { onUnmatchedPause: warnOnUnmatchedPause }
|
|
4609
|
+
}, signal);
|
|
4610
|
+
winner = hit.session;
|
|
4611
|
+
const captureCount = command.captures.length;
|
|
4612
|
+
reportProgress?.(
|
|
4613
|
+
`Breakpoint hit; capturing ${captureCount.toString()} ${captureCount === 1 ? "expression" : "expressions"}...`
|
|
4614
|
+
);
|
|
4615
|
+
const result = await captureSnapshotResult(hit.session, hit.pause, command, opts, reportProgress);
|
|
4616
|
+
preserveWinner = opts.keepPaused === true;
|
|
4617
|
+
return { ...result, isolate: hit.session.isolate ?? { kind: "main" } };
|
|
4618
|
+
} catch (error) {
|
|
4619
|
+
if (error instanceof CfInspectorError && (error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT")) {
|
|
4620
|
+
const outcomes = fanout.availableOutcomes();
|
|
4621
|
+
warnOnBoundBreakpointWithoutHit(outcomes.flatMap((outcome) => outcome.setup.handles));
|
|
4622
|
+
}
|
|
4623
|
+
throw error;
|
|
4624
|
+
} finally {
|
|
4625
|
+
const cleanup = await fanout.cleanup(2e3, preserveWinner ? winner : void 0);
|
|
4626
|
+
reportProgress?.(
|
|
4627
|
+
`Breakpoint cleanup: cleared ${cleanup.cleared.toString()} of ${cleanup.attempted.toString()}; resumed ${cleanup.resumed.toString()} paused losing isolates.`
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4630
|
+
}, reportProgress, signal);
|
|
3700
4631
|
}
|
|
3701
|
-
async function
|
|
4632
|
+
async function prepareSnapshotSession(session, command) {
|
|
3702
4633
|
if (command.setupEvals.length > 0) {
|
|
3703
|
-
const setupCount = command.setupEvals.length;
|
|
3704
|
-
reportProgress?.(`Running ${setupCount.toString()} setup ${setupCount === 1 ? "evaluation" : "evaluations"}...`);
|
|
3705
4634
|
await runSetupEvals(session, command.setupEvals);
|
|
3706
|
-
reportProgress?.("Setup evaluation complete.");
|
|
3707
4635
|
}
|
|
3708
4636
|
if (command.condition !== void 0) {
|
|
3709
|
-
reportProgress?.("Validating the breakpoint condition...");
|
|
3710
4637
|
await validateExpression(session, command.condition);
|
|
3711
|
-
reportProgress?.("Breakpoint condition is valid.");
|
|
3712
4638
|
}
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
const
|
|
3719
|
-
|
|
4639
|
+
}
|
|
4640
|
+
function reportBreakpointOutcomes(outcomes, reportProgress) {
|
|
4641
|
+
for (const outcome of outcomes) {
|
|
4642
|
+
warnOnUnboundBreakpoints(outcome.setup.handles);
|
|
4643
|
+
}
|
|
4644
|
+
const boundSessions = outcomes.filter((outcome) => outcome.setup.handles.some((handle) => handle.resolvedLocations.length > 0)).length;
|
|
4645
|
+
const locations = outcomes.reduce((total, outcome) => total + outcome.setup.handles.reduce(
|
|
4646
|
+
(sessionTotal, handle) => sessionTotal + handle.resolvedLocations.length,
|
|
3720
4647
|
0
|
|
3721
|
-
);
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
);
|
|
3729
|
-
const pause = await waitForCommandPause(session, opts, handles, command.timeoutMs);
|
|
3730
|
-
const captureCount = command.captures.length;
|
|
4648
|
+
), 0);
|
|
4649
|
+
if (outcomes.length === 1) {
|
|
4650
|
+
reportProgress?.(
|
|
4651
|
+
`Breakpoint setup complete: ${locations.toString()} resolved ${locations === 1 ? "location" : "locations"}.`
|
|
4652
|
+
);
|
|
4653
|
+
return;
|
|
4654
|
+
}
|
|
3731
4655
|
reportProgress?.(
|
|
3732
|
-
`Breakpoint
|
|
4656
|
+
`Breakpoint setup complete: sessions=${outcomes.length.toString()} boundSessions=${boundSessions.toString()} resolvedLocations=${locations.toString()}.`
|
|
3733
4657
|
);
|
|
3734
|
-
|
|
4658
|
+
}
|
|
4659
|
+
async function captureSnapshotResult(session, pause, command, opts, reportProgress) {
|
|
4660
|
+
const pausedStartedAt = pause.receivedAtMs ?? performance6.now();
|
|
3735
4661
|
const snapshot = await captureSnapshot(session, pause, {
|
|
3736
4662
|
captures: command.captures,
|
|
3737
4663
|
includeScopes: opts.includeScopes === true,
|
|
@@ -3741,13 +4667,12 @@ async function runSnapshotOnSession(session, command, opts, reportProgress) {
|
|
|
3741
4667
|
throwOnSideEffect: command.throwOnSideEffect
|
|
3742
4668
|
});
|
|
3743
4669
|
if (opts.keepPaused === true) {
|
|
3744
|
-
reportProgress?.("Snapshot captured; leaving the target paused as requested.");
|
|
3745
4670
|
return withPausedDuration(snapshot, null);
|
|
3746
4671
|
}
|
|
3747
4672
|
reportProgress?.("Snapshot captured; resuming the target...");
|
|
3748
4673
|
return await resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress);
|
|
3749
4674
|
}
|
|
3750
|
-
async function setCommandBreakpoints(session, command) {
|
|
4675
|
+
async function setCommandBreakpoints(session, command, onSet) {
|
|
3751
4676
|
return await Promise.all(
|
|
3752
4677
|
command.breakpoints.map(
|
|
3753
4678
|
(bp) => setBreakpoint(session, {
|
|
@@ -3756,39 +4681,20 @@ async function setCommandBreakpoints(session, command) {
|
|
|
3756
4681
|
remoteRoot: command.remoteRoot,
|
|
3757
4682
|
...command.condition === void 0 ? {} : { condition: command.condition },
|
|
3758
4683
|
...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
|
|
4684
|
+
}).then((handle) => {
|
|
4685
|
+
onSet?.(handle);
|
|
4686
|
+
return handle;
|
|
3759
4687
|
})
|
|
3760
4688
|
)
|
|
3761
4689
|
);
|
|
3762
4690
|
}
|
|
3763
|
-
async function waitForCommandPause(session, opts, handles, timeoutMs) {
|
|
3764
|
-
let warnedUnmatchedPause = false;
|
|
3765
|
-
try {
|
|
3766
|
-
return await waitForPause(session, {
|
|
3767
|
-
timeoutMs,
|
|
3768
|
-
breakpointIds: handles.map((h) => h.breakpointId),
|
|
3769
|
-
unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
|
|
3770
|
-
onUnmatchedPause: (unmatchedPause) => {
|
|
3771
|
-
if (warnedUnmatchedPause || opts.failOnUnmatchedPause === true) {
|
|
3772
|
-
return;
|
|
3773
|
-
}
|
|
3774
|
-
warnedUnmatchedPause = true;
|
|
3775
|
-
warnOnUnmatchedPause(unmatchedPause);
|
|
3776
|
-
}
|
|
3777
|
-
});
|
|
3778
|
-
} catch (error) {
|
|
3779
|
-
if (error instanceof CfInspectorError && (error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT")) {
|
|
3780
|
-
warnOnBoundBreakpointWithoutHit(handles);
|
|
3781
|
-
}
|
|
3782
|
-
throw error;
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
4691
|
async function resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress) {
|
|
3786
4692
|
try {
|
|
3787
4693
|
await resume(session);
|
|
3788
4694
|
reportProgress?.("Target resumed.");
|
|
3789
|
-
return withPausedDuration(snapshot, roundDurationMs(
|
|
4695
|
+
return withPausedDuration(snapshot, roundDurationMs(performance6.now() - pausedStartedAt));
|
|
3790
4696
|
} catch {
|
|
3791
|
-
|
|
4697
|
+
process10.stderr.write(
|
|
3792
4698
|
"[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
|
|
3793
4699
|
);
|
|
3794
4700
|
return withPausedDuration(snapshot, null);
|
|
@@ -3800,8 +4706,8 @@ function parseSetupEvals(raw) {
|
|
|
3800
4706
|
}
|
|
3801
4707
|
|
|
3802
4708
|
// src/cli/commands/watch.ts
|
|
3803
|
-
import { performance as
|
|
3804
|
-
import
|
|
4709
|
+
import { performance as performance7 } from "perf_hooks";
|
|
4710
|
+
import process11 from "process";
|
|
3805
4711
|
init_types();
|
|
3806
4712
|
async function handleWatch(opts) {
|
|
3807
4713
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
@@ -3816,14 +4722,115 @@ async function handleWatch(opts) {
|
|
|
3816
4722
|
let stoppedReason = "signal";
|
|
3817
4723
|
let emitted = 0;
|
|
3818
4724
|
await withTerminationSignal(async (signal) => {
|
|
3819
|
-
await
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3822
|
-
|
|
4725
|
+
await withSessions(prepared.target, async (group, port) => {
|
|
4726
|
+
const host = prepared.target.kind === "port" ? prepared.target.host : "127.0.0.1";
|
|
4727
|
+
const keepalive = startInspectorKeepalive(host, port);
|
|
4728
|
+
const commandAbort = new AbortController();
|
|
4729
|
+
const onSignal = () => {
|
|
4730
|
+
commandAbort.abort();
|
|
4731
|
+
};
|
|
4732
|
+
signal.addEventListener("abort", onSignal, { once: true });
|
|
4733
|
+
let keepaliveError;
|
|
4734
|
+
void keepalive.failure.catch((error) => {
|
|
4735
|
+
keepaliveError = error;
|
|
4736
|
+
commandAbort.abort();
|
|
4737
|
+
});
|
|
4738
|
+
try {
|
|
4739
|
+
const result = await runWatchGroup(group, prepared, opts, commandAbort.signal);
|
|
4740
|
+
stoppedReason = result.stoppedReason;
|
|
4741
|
+
emitted = result.emitted;
|
|
4742
|
+
if (keepaliveError !== void 0) {
|
|
4743
|
+
throw keepaliveError instanceof Error ? keepaliveError : new Error("Unknown inspector keepalive failure");
|
|
4744
|
+
}
|
|
4745
|
+
} finally {
|
|
4746
|
+
keepalive.cancel();
|
|
4747
|
+
signal.removeEventListener("abort", onSignal);
|
|
4748
|
+
}
|
|
3823
4749
|
}, void 0, signal);
|
|
3824
4750
|
});
|
|
3825
4751
|
writeWatchSummary(stoppedReason, emitted, opts.json);
|
|
3826
4752
|
}
|
|
4753
|
+
async function runWatchGroup(group, command, opts, signal) {
|
|
4754
|
+
const fanout = new BreakpointFanout(group, async (session, trackHandle) => {
|
|
4755
|
+
if (command.setupEvals.length > 0) {
|
|
4756
|
+
await runSetupEvals(session, command.setupEvals);
|
|
4757
|
+
}
|
|
4758
|
+
if (command.condition !== void 0) {
|
|
4759
|
+
await validateExpression(session, command.condition);
|
|
4760
|
+
}
|
|
4761
|
+
const handles = await Promise.all(command.breakpoints.map((bp) => setBreakpoint(session, {
|
|
4762
|
+
file: bp.file,
|
|
4763
|
+
line: bp.line,
|
|
4764
|
+
remoteRoot: command.remoteRoot,
|
|
4765
|
+
...command.condition === void 0 ? {} : { condition: command.condition },
|
|
4766
|
+
...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
|
|
4767
|
+
}).then((handle) => {
|
|
4768
|
+
trackHandle(handle);
|
|
4769
|
+
return handle;
|
|
4770
|
+
})));
|
|
4771
|
+
warnOnUnboundBreakpoints(handles);
|
|
4772
|
+
return { handles };
|
|
4773
|
+
});
|
|
4774
|
+
let emitted = 0;
|
|
4775
|
+
let stoppedReason = "signal";
|
|
4776
|
+
const deadline = computeDeadline(command.durationMs);
|
|
4777
|
+
try {
|
|
4778
|
+
await fanout.ready();
|
|
4779
|
+
while (!signal.aborted) {
|
|
4780
|
+
const remainingMs = remainingForLoop(deadline, command.perHitTimeoutMs);
|
|
4781
|
+
if (remainingMs <= 0) {
|
|
4782
|
+
stoppedReason = "duration";
|
|
4783
|
+
break;
|
|
4784
|
+
}
|
|
4785
|
+
let hit;
|
|
4786
|
+
try {
|
|
4787
|
+
hit = await fanout.waitForFirst(remainingMs, { unmatchedPausePolicy: "wait-for-resume" }, signal);
|
|
4788
|
+
} catch (error) {
|
|
4789
|
+
if (error instanceof CfInspectorError && error.code === "ABORTED") {
|
|
4790
|
+
stoppedReason = "signal";
|
|
4791
|
+
break;
|
|
4792
|
+
}
|
|
4793
|
+
if (error instanceof CfInspectorError && (error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT")) {
|
|
4794
|
+
if (deadline !== void 0 && performance7.now() >= deadline) {
|
|
4795
|
+
stoppedReason = "duration";
|
|
4796
|
+
break;
|
|
4797
|
+
}
|
|
4798
|
+
continue;
|
|
4799
|
+
}
|
|
4800
|
+
throw error;
|
|
4801
|
+
}
|
|
4802
|
+
const event = await captureWatchEvent(hit.session, command, hit.pause, emitted + 1, opts);
|
|
4803
|
+
emitted += 1;
|
|
4804
|
+
writeWatchEvent({ ...event, isolate: hit.session.isolate ?? { kind: "main" } }, opts.json);
|
|
4805
|
+
try {
|
|
4806
|
+
await resume(hit.session);
|
|
4807
|
+
hit.session.debuggerState.paused = false;
|
|
4808
|
+
} catch {
|
|
4809
|
+
process11.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
|
|
4810
|
+
stoppedReason = "transport-closed";
|
|
4811
|
+
break;
|
|
4812
|
+
}
|
|
4813
|
+
if (command.maxEvents !== void 0 && emitted >= command.maxEvents) {
|
|
4814
|
+
stoppedReason = "max-events";
|
|
4815
|
+
break;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
if (signal.aborted) {
|
|
4819
|
+
stoppedReason = "signal";
|
|
4820
|
+
}
|
|
4821
|
+
} finally {
|
|
4822
|
+
const cleanup = await fanout.cleanup();
|
|
4823
|
+
process11.stderr.write(
|
|
4824
|
+
`[cf-inspector] breakpoint cleanup: cleared ${cleanup.cleared.toString()} of ${cleanup.attempted.toString()}; resumed ${cleanup.resumed.toString()} paused isolates.
|
|
4825
|
+
`
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4828
|
+
if (emitted === 0 && (stoppedReason === "duration" || stoppedReason === "signal")) {
|
|
4829
|
+
const outcomes = fanout.availableOutcomes();
|
|
4830
|
+
warnOnBoundBreakpointWithoutHit(outcomes.flatMap((outcome) => outcome.setup.handles));
|
|
4831
|
+
}
|
|
4832
|
+
return { emitted, stoppedReason };
|
|
4833
|
+
}
|
|
3827
4834
|
function prepareWatchCommand(opts, target) {
|
|
3828
4835
|
if (opts.bp.length === 0) {
|
|
3829
4836
|
throw new CfInspectorError(
|
|
@@ -3861,147 +4868,21 @@ function prepareWatchCommand(opts, target) {
|
|
|
3861
4868
|
throwOnSideEffect: opts.allowMutation !== true
|
|
3862
4869
|
};
|
|
3863
4870
|
}
|
|
3864
|
-
async function runWatchLoop(session, command, opts, signal) {
|
|
3865
|
-
if (command.setupEvals.length > 0) {
|
|
3866
|
-
await runSetupEvals(session, command.setupEvals);
|
|
3867
|
-
}
|
|
3868
|
-
if (command.condition !== void 0) {
|
|
3869
|
-
await validateExpression(session, command.condition);
|
|
3870
|
-
}
|
|
3871
|
-
const handles = await Promise.all(
|
|
3872
|
-
command.breakpoints.map(
|
|
3873
|
-
(bp) => setBreakpoint(session, {
|
|
3874
|
-
file: bp.file,
|
|
3875
|
-
line: bp.line,
|
|
3876
|
-
remoteRoot: command.remoteRoot,
|
|
3877
|
-
...command.condition === void 0 ? {} : { condition: command.condition },
|
|
3878
|
-
...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
|
|
3879
|
-
})
|
|
3880
|
-
)
|
|
3881
|
-
);
|
|
3882
|
-
warnOnUnboundBreakpoints(handles);
|
|
3883
|
-
const deadline = computeDeadline(command.durationMs);
|
|
3884
|
-
let emitted = 0;
|
|
3885
|
-
const state = { stopped: false, reason: "signal" };
|
|
3886
|
-
const setStop = (reason) => {
|
|
3887
|
-
if (state.stopped) {
|
|
3888
|
-
return;
|
|
3889
|
-
}
|
|
3890
|
-
state.reason = reason;
|
|
3891
|
-
state.stopped = true;
|
|
3892
|
-
};
|
|
3893
|
-
const transportClosed = waitForTransportClose(session);
|
|
3894
|
-
transportClosed.promise.then(() => {
|
|
3895
|
-
setStop("transport-closed");
|
|
3896
|
-
}).catch(() => {
|
|
3897
|
-
});
|
|
3898
|
-
try {
|
|
3899
|
-
while (!state.stopped) {
|
|
3900
|
-
if (signal.aborted) {
|
|
3901
|
-
setStop("signal");
|
|
3902
|
-
break;
|
|
3903
|
-
}
|
|
3904
|
-
const remainingMs = remainingForLoop(deadline, command.perHitTimeoutMs);
|
|
3905
|
-
if (remainingMs <= 0) {
|
|
3906
|
-
setStop("duration");
|
|
3907
|
-
break;
|
|
3908
|
-
}
|
|
3909
|
-
const pause = await waitForNextWatchPause(session, handles, remainingMs, signal);
|
|
3910
|
-
if (pause === "signal") {
|
|
3911
|
-
setStop("signal");
|
|
3912
|
-
break;
|
|
3913
|
-
}
|
|
3914
|
-
if (pause === "timeout") {
|
|
3915
|
-
if (deadline !== void 0 && performance6.now() >= deadline) {
|
|
3916
|
-
setStop("duration");
|
|
3917
|
-
break;
|
|
3918
|
-
}
|
|
3919
|
-
continue;
|
|
3920
|
-
}
|
|
3921
|
-
const event = await captureWatchEvent(session, command, pause, emitted + 1, opts);
|
|
3922
|
-
emitted += 1;
|
|
3923
|
-
writeWatchEvent(event, opts.json);
|
|
3924
|
-
try {
|
|
3925
|
-
await resume(session);
|
|
3926
|
-
} catch {
|
|
3927
|
-
process10.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
|
|
3928
|
-
setStop("transport-closed");
|
|
3929
|
-
break;
|
|
3930
|
-
}
|
|
3931
|
-
if (command.maxEvents !== void 0 && emitted >= command.maxEvents) {
|
|
3932
|
-
setStop("max-events");
|
|
3933
|
-
break;
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
} finally {
|
|
3937
|
-
transportClosed.cancel();
|
|
3938
|
-
}
|
|
3939
|
-
if (emitted === 0 && (state.reason === "duration" || state.reason === "signal")) {
|
|
3940
|
-
warnOnBoundBreakpointWithoutHit(handles);
|
|
3941
|
-
}
|
|
3942
|
-
return { emitted, stoppedReason: state.reason };
|
|
3943
|
-
}
|
|
3944
4871
|
function computeDeadline(durationMs) {
|
|
3945
4872
|
if (durationMs === void 0) {
|
|
3946
4873
|
return void 0;
|
|
3947
4874
|
}
|
|
3948
|
-
return
|
|
4875
|
+
return performance7.now() + durationMs;
|
|
3949
4876
|
}
|
|
3950
4877
|
function remainingForLoop(deadline, perHitTimeoutMs) {
|
|
3951
4878
|
if (deadline === void 0) {
|
|
3952
4879
|
return perHitTimeoutMs;
|
|
3953
4880
|
}
|
|
3954
|
-
const
|
|
3955
|
-
if (
|
|
4881
|
+
const remaining2 = deadline - performance7.now();
|
|
4882
|
+
if (remaining2 <= 0) {
|
|
3956
4883
|
return 0;
|
|
3957
4884
|
}
|
|
3958
|
-
return Math.min(
|
|
3959
|
-
}
|
|
3960
|
-
function waitForTransportClose(session) {
|
|
3961
|
-
let cancelled = false;
|
|
3962
|
-
let resolve;
|
|
3963
|
-
const promise = new Promise((res) => {
|
|
3964
|
-
resolve = res;
|
|
3965
|
-
});
|
|
3966
|
-
const off = session.client.onClose(() => {
|
|
3967
|
-
if (!cancelled) {
|
|
3968
|
-
resolve?.();
|
|
3969
|
-
}
|
|
3970
|
-
});
|
|
3971
|
-
return {
|
|
3972
|
-
promise,
|
|
3973
|
-
cancel: () => {
|
|
3974
|
-
cancelled = true;
|
|
3975
|
-
off();
|
|
3976
|
-
resolve?.();
|
|
3977
|
-
}
|
|
3978
|
-
};
|
|
3979
|
-
}
|
|
3980
|
-
async function waitForNextWatchPause(session, handles, timeoutMs, signal) {
|
|
3981
|
-
if (signal.aborted) {
|
|
3982
|
-
return "signal";
|
|
3983
|
-
}
|
|
3984
|
-
try {
|
|
3985
|
-
return await waitForPause(session, {
|
|
3986
|
-
timeoutMs,
|
|
3987
|
-
breakpointIds: handles.map((h) => h.breakpointId),
|
|
3988
|
-
unmatchedPausePolicy: "wait-for-resume",
|
|
3989
|
-
signal
|
|
3990
|
-
});
|
|
3991
|
-
} catch (err) {
|
|
3992
|
-
if (err instanceof CfInspectorError) {
|
|
3993
|
-
if (err.code === "ABORTED") {
|
|
3994
|
-
return "signal";
|
|
3995
|
-
}
|
|
3996
|
-
if (err.code === "BREAKPOINT_NOT_HIT") {
|
|
3997
|
-
return "timeout";
|
|
3998
|
-
}
|
|
3999
|
-
if (err.code === "UNRELATED_PAUSE_TIMEOUT") {
|
|
4000
|
-
return "timeout";
|
|
4001
|
-
}
|
|
4002
|
-
}
|
|
4003
|
-
throw err;
|
|
4004
|
-
}
|
|
4885
|
+
return Math.min(remaining2, perHitTimeoutMs);
|
|
4005
4886
|
}
|
|
4006
4887
|
async function captureWatchEvent(session, command, pause, hit, opts) {
|
|
4007
4888
|
const snapshot = await captureSnapshot(session, pause, {
|
|
@@ -4037,11 +4918,11 @@ function formatLocation(command, topFrame) {
|
|
|
4037
4918
|
}
|
|
4038
4919
|
function writeWatchSummary(reason, emitted, json) {
|
|
4039
4920
|
if (json) {
|
|
4040
|
-
|
|
4921
|
+
process11.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
|
|
4041
4922
|
`);
|
|
4042
4923
|
return;
|
|
4043
4924
|
}
|
|
4044
|
-
|
|
4925
|
+
process11.stderr.write(
|
|
4045
4926
|
`Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
|
|
4046
4927
|
`
|
|
4047
4928
|
);
|
|
@@ -4061,7 +4942,7 @@ function applyTargetOptions(cmd, options = {}) {
|
|
|
4061
4942
|
"--target <index>",
|
|
4062
4943
|
"Inspector target index from /json/list (default: 0)"
|
|
4063
4944
|
);
|
|
4064
|
-
const withWorkerOption = options.includeWorker === false ? withTargetOption : withTargetOption.option("--worker <index>", "NodeWorker sub-session index listed by list-targets");
|
|
4945
|
+
const withWorkerOption = options.includeWorker === false ? withTargetOption : withTargetOption.option("--worker <index>", "NodeWorker sub-session index listed by list-targets").option("--worker-id <id>", "Stable NodeWorker workerId listed by list-targets").option("--main-only", "Attach only to the main isolate and ignore workers");
|
|
4065
4946
|
return options.includeTimeout === false ? withWorkerOption : withWorkerOption.option("--timeout <seconds>", "Timeout for CF tunnel readiness in seconds (default: 180)");
|
|
4066
4947
|
}
|
|
4067
4948
|
var collectStrings = (value, prev = []) => [
|
|
@@ -4093,12 +4974,22 @@ async function main(argv) {
|
|
|
4093
4974
|
registerLog(program);
|
|
4094
4975
|
registerWatch(program);
|
|
4095
4976
|
registerException(program);
|
|
4977
|
+
registerCheckBreakpoint(program);
|
|
4096
4978
|
registerEval(program);
|
|
4097
4979
|
registerListScripts(program);
|
|
4098
4980
|
registerListTargets(program);
|
|
4099
4981
|
registerAttach(program);
|
|
4100
4982
|
await program.parseAsync([...argv]);
|
|
4101
4983
|
}
|
|
4984
|
+
function registerCheckBreakpoint(program) {
|
|
4985
|
+
applyTargetOptions(
|
|
4986
|
+
program.command("check-breakpoint").description(
|
|
4987
|
+
"Report whether a loaded script can break at a file:line location"
|
|
4988
|
+
)
|
|
4989
|
+
).requiredOption("--bp <file:line>", "Source location to check").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--no-json", "Print a human-readable result instead of JSON").action(async (opts) => {
|
|
4990
|
+
await handleCheckBreakpoint(opts);
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4102
4993
|
function registerSnapshot(program) {
|
|
4103
4994
|
applyTargetOptions(
|
|
4104
4995
|
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume"),
|
|
@@ -4169,20 +5060,20 @@ function registerAttach(program) {
|
|
|
4169
5060
|
// src/cli.ts
|
|
4170
5061
|
init_types();
|
|
4171
5062
|
try {
|
|
4172
|
-
await main(
|
|
5063
|
+
await main(process12.argv);
|
|
4173
5064
|
} catch (err) {
|
|
4174
5065
|
if (err instanceof CfInspectorError) {
|
|
4175
|
-
|
|
5066
|
+
process12.stderr.write(`Error [${err.code}]: ${err.message}
|
|
4176
5067
|
`);
|
|
4177
5068
|
if (err.detail !== void 0) {
|
|
4178
|
-
|
|
5069
|
+
process12.stderr.write(` detail: ${err.detail}
|
|
4179
5070
|
`);
|
|
4180
5071
|
}
|
|
4181
|
-
|
|
5072
|
+
process12.exit(1);
|
|
4182
5073
|
}
|
|
4183
5074
|
const message = err instanceof Error ? err.message : String(err);
|
|
4184
|
-
|
|
5075
|
+
process12.stderr.write(`Error: ${message}
|
|
4185
5076
|
`);
|
|
4186
|
-
|
|
5077
|
+
process12.exit(1);
|
|
4187
5078
|
}
|
|
4188
5079
|
//# sourceMappingURL=cli.js.map
|