@saptools/cf-inspector 0.6.2 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -14
- package/dist/cli.js +1612 -473
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +71 -2
- package/dist/index.js +579 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -152,16 +152,16 @@ var init_wsTransport = __esm({
|
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
// src/cli.ts
|
|
155
|
-
import
|
|
155
|
+
import process13 from "process";
|
|
156
156
|
|
|
157
157
|
// src/cli/program.ts
|
|
158
158
|
import { readFileSync } from "fs";
|
|
159
|
-
import { dirname, join } from "path";
|
|
159
|
+
import { dirname, join as join2 } from "path";
|
|
160
160
|
import { fileURLToPath } from "url";
|
|
161
161
|
import { Command } from "commander";
|
|
162
162
|
|
|
163
163
|
// src/cli/commands/attach.ts
|
|
164
|
-
import
|
|
164
|
+
import process4 from "process";
|
|
165
165
|
|
|
166
166
|
// src/inspector/discovery.ts
|
|
167
167
|
init_types();
|
|
@@ -334,15 +334,65 @@ 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
|
-
import
|
|
389
|
+
import process2 from "process";
|
|
340
390
|
function writeProgress(message) {
|
|
341
|
-
|
|
391
|
+
process2.stderr.write(`[cf-inspector] ${message}
|
|
342
392
|
`);
|
|
343
393
|
}
|
|
344
394
|
function writeJson(value) {
|
|
345
|
-
|
|
395
|
+
process2.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
346
396
|
`);
|
|
347
397
|
}
|
|
348
398
|
function writeHumanSnapshot(snapshot) {
|
|
@@ -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) {
|
|
@@ -372,7 +423,7 @@ function writeHumanSnapshot(snapshot) {
|
|
|
372
423
|
appendStackFrameLine(lines, frame);
|
|
373
424
|
}
|
|
374
425
|
}
|
|
375
|
-
|
|
426
|
+
process2.stdout.write(`${lines.join("\n")}
|
|
376
427
|
`);
|
|
377
428
|
}
|
|
378
429
|
function appendFrameLines(lines, frame) {
|
|
@@ -415,36 +466,45 @@ function appendExceptionLines(lines, exception) {
|
|
|
415
466
|
}
|
|
416
467
|
function writeLogEvent(event, json) {
|
|
417
468
|
if (json) {
|
|
418
|
-
|
|
469
|
+
process2.stdout.write(`${JSON.stringify(event)}
|
|
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
|
-
|
|
475
|
+
process2.stdout.write(`[${event.ts}] ${event.at}${isolateSuffix} !err ${renderTruncated(event.error, event)}
|
|
424
476
|
`);
|
|
425
477
|
return;
|
|
426
478
|
}
|
|
427
|
-
|
|
479
|
+
process2.stdout.write(`[${event.ts}] ${event.at}${isolateSuffix} ${renderTruncated(event.value ?? "", event)}
|
|
428
480
|
`);
|
|
429
481
|
}
|
|
430
482
|
function writeWatchEvent(event, json) {
|
|
431
483
|
if (json) {
|
|
432
|
-
|
|
484
|
+
process2.stdout.write(`${JSON.stringify(event)}
|
|
433
485
|
`);
|
|
434
486
|
return;
|
|
435
487
|
}
|
|
436
|
-
|
|
437
|
-
`)
|
|
488
|
+
process2.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
|
+
process2.stdout.write(` exception: ${renderExceptionDetail(event.exception)}
|
|
440
494
|
`);
|
|
441
495
|
}
|
|
442
496
|
for (const capture of event.captures) {
|
|
443
497
|
const detail = capture.error ?? capture.value ?? "undefined";
|
|
444
|
-
|
|
498
|
+
process2.stdout.write(` ${capture.expression} = ${renderTruncated(detail, capture)}
|
|
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,
|
|
@@ -1453,9 +1776,247 @@ var DEFAULT_BREAKPOINT_TIMEOUT_SEC = 30;
|
|
|
1453
1776
|
var DEFAULT_CF_TIMEOUT_SEC = 180;
|
|
1454
1777
|
var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
|
|
1455
1778
|
|
|
1779
|
+
// src/cli/sessionLock.ts
|
|
1780
|
+
init_types();
|
|
1781
|
+
import { execFileSync } from "child_process";
|
|
1782
|
+
import { createHash, randomUUID } from "crypto";
|
|
1783
|
+
import { constants } from "fs";
|
|
1784
|
+
import { mkdir, open, readFile, readdir, stat, unlink, writeFile } from "fs/promises";
|
|
1785
|
+
import { homedir } from "os";
|
|
1786
|
+
import { join } from "path";
|
|
1787
|
+
var ELECTION_WINDOW_MS = 25;
|
|
1788
|
+
var LOCK_FILE_SUFFIX = ".lock";
|
|
1789
|
+
async function acquireDebugSessionLock(target, options = {}) {
|
|
1790
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1791
|
+
const pid = options.pid ?? process.pid;
|
|
1792
|
+
const getProcessStart = options.getProcessStart ?? processStart;
|
|
1793
|
+
const ownerProcessStart = getProcessStart(pid);
|
|
1794
|
+
const token = options.token?.() ?? randomUUID();
|
|
1795
|
+
const targetIdentity = debugTargetIdentity(target);
|
|
1796
|
+
const key = createHash("sha256").update(targetIdentity).digest("hex");
|
|
1797
|
+
const lockRoot = options.stateRoot ?? defaultStateRoot();
|
|
1798
|
+
const lockDirectory = join(lockRoot, "cf-inspector", "locks");
|
|
1799
|
+
const ownPath = join(lockDirectory, `${key}.${pid.toString()}.${token}${LOCK_FILE_SUFFIX}`);
|
|
1800
|
+
const metadata = {
|
|
1801
|
+
pid,
|
|
1802
|
+
...ownerProcessStart === void 0 ? {} : { processStart: ownerProcessStart },
|
|
1803
|
+
state: "pending",
|
|
1804
|
+
startedAt: now().toISOString(),
|
|
1805
|
+
token,
|
|
1806
|
+
target: targetIdentity
|
|
1807
|
+
};
|
|
1808
|
+
await mkdir(lockDirectory, { recursive: true, mode: 448 });
|
|
1809
|
+
const handle = await open(ownPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 384);
|
|
1810
|
+
try {
|
|
1811
|
+
await handle.writeFile(`${JSON.stringify(metadata)}
|
|
1812
|
+
`, "utf8");
|
|
1813
|
+
} finally {
|
|
1814
|
+
await handle.close();
|
|
1815
|
+
}
|
|
1816
|
+
try {
|
|
1817
|
+
await new Promise((resolve) => {
|
|
1818
|
+
setTimeout(resolve, ELECTION_WINDOW_MS);
|
|
1819
|
+
});
|
|
1820
|
+
const contenders = await findLiveContenders(
|
|
1821
|
+
lockDirectory,
|
|
1822
|
+
key,
|
|
1823
|
+
ownPath,
|
|
1824
|
+
options.isProcessAlive ?? processIsAlive,
|
|
1825
|
+
getProcessStart
|
|
1826
|
+
);
|
|
1827
|
+
const owner = contenders.find((candidate) => candidate.state === "owned") ?? contenders.filter((candidate) => candidate.state === "pending" && candidate.token < token).sort((left, right) => left.token.localeCompare(right.token))[0];
|
|
1828
|
+
if (owner !== void 0) {
|
|
1829
|
+
throw alreadyDebuggedError(owner);
|
|
1830
|
+
}
|
|
1831
|
+
await writeLockMetadata(ownPath, { ...metadata, state: "owned" });
|
|
1832
|
+
} catch (error) {
|
|
1833
|
+
await unlink(ownPath).catch(() => {
|
|
1834
|
+
});
|
|
1835
|
+
throw error;
|
|
1836
|
+
}
|
|
1837
|
+
let released = false;
|
|
1838
|
+
return {
|
|
1839
|
+
path: ownPath,
|
|
1840
|
+
release: async () => {
|
|
1841
|
+
if (released) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
released = true;
|
|
1845
|
+
const current = await readLockMetadata(ownPath);
|
|
1846
|
+
if (current?.token !== token || current.pid !== pid) {
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
await unlink(ownPath).catch((error) => {
|
|
1850
|
+
if (!isNodeError(error, "ENOENT")) {
|
|
1851
|
+
throw error;
|
|
1852
|
+
}
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
function debugTargetIdentity(target) {
|
|
1858
|
+
const targetIndex = target.targetIndex ?? 0;
|
|
1859
|
+
if (target.kind === "port") {
|
|
1860
|
+
return JSON.stringify({
|
|
1861
|
+
kind: "port",
|
|
1862
|
+
host: normalizeHost(target.host),
|
|
1863
|
+
port: target.port,
|
|
1864
|
+
targetIndex
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
return JSON.stringify({
|
|
1868
|
+
kind: "cf",
|
|
1869
|
+
region: target.region,
|
|
1870
|
+
org: target.org,
|
|
1871
|
+
space: target.space,
|
|
1872
|
+
app: target.app,
|
|
1873
|
+
targetIndex
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
function defaultStateRoot() {
|
|
1877
|
+
const configured = process.env["CF_INSPECTOR_STATE_DIR"]?.trim();
|
|
1878
|
+
return configured === void 0 || configured.length === 0 ? join(homedir(), ".saptools") : configured;
|
|
1879
|
+
}
|
|
1880
|
+
async function findLiveContenders(lockDirectory, key, ownPath, isProcessAlive, getProcessStart) {
|
|
1881
|
+
const prefix = `${key}.`;
|
|
1882
|
+
const names = await readdir(lockDirectory);
|
|
1883
|
+
const contenders = [];
|
|
1884
|
+
for (const name of names) {
|
|
1885
|
+
if (!name.startsWith(prefix) || !name.endsWith(LOCK_FILE_SUFFIX)) {
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
const path = join(lockDirectory, name);
|
|
1889
|
+
if (path === ownPath) {
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
const info = await stat(path).catch(() => {
|
|
1893
|
+
});
|
|
1894
|
+
const metadata = await readLockMetadata(path) ?? (info === void 0 ? void 0 : metadataFromFilename(name, key, info.mtimeMs));
|
|
1895
|
+
if (metadata !== void 0) {
|
|
1896
|
+
if (ownerIsAlive(metadata, isProcessAlive, getProcessStart)) {
|
|
1897
|
+
contenders.push(metadata);
|
|
1898
|
+
} else {
|
|
1899
|
+
await unlink(path).catch(() => {
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
continue;
|
|
1903
|
+
}
|
|
1904
|
+
if (info !== void 0) {
|
|
1905
|
+
contenders.push({
|
|
1906
|
+
pid: 0,
|
|
1907
|
+
state: "owned",
|
|
1908
|
+
startedAt: new Date(info.mtimeMs).toISOString(),
|
|
1909
|
+
token: "unknown",
|
|
1910
|
+
target: "unknown"
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
return contenders;
|
|
1915
|
+
}
|
|
1916
|
+
async function readLockMetadata(path) {
|
|
1917
|
+
try {
|
|
1918
|
+
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
1919
|
+
if (!isRecord2(parsed)) {
|
|
1920
|
+
return void 0;
|
|
1921
|
+
}
|
|
1922
|
+
const pid = parsed["pid"];
|
|
1923
|
+
const processStart2 = parsed["processStart"];
|
|
1924
|
+
const state = parsed["state"];
|
|
1925
|
+
const startedAt = parsed["startedAt"];
|
|
1926
|
+
const token = parsed["token"];
|
|
1927
|
+
const target = parsed["target"];
|
|
1928
|
+
if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0 || processStart2 !== void 0 && typeof processStart2 !== "string" || state !== "pending" && state !== "owned" || typeof startedAt !== "string" || Number.isNaN(Date.parse(startedAt)) || typeof token !== "string" || token.length === 0 || typeof target !== "string" || target.length === 0) {
|
|
1929
|
+
return void 0;
|
|
1930
|
+
}
|
|
1931
|
+
return {
|
|
1932
|
+
pid,
|
|
1933
|
+
...typeof processStart2 === "string" ? { processStart: processStart2 } : {},
|
|
1934
|
+
state,
|
|
1935
|
+
startedAt,
|
|
1936
|
+
token,
|
|
1937
|
+
target
|
|
1938
|
+
};
|
|
1939
|
+
} catch {
|
|
1940
|
+
return void 0;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
async function writeLockMetadata(path, metadata) {
|
|
1944
|
+
await writeFile(path, `${JSON.stringify(metadata)}
|
|
1945
|
+
`, { encoding: "utf8", mode: 384 });
|
|
1946
|
+
}
|
|
1947
|
+
function metadataFromFilename(name, key, mtimeMs) {
|
|
1948
|
+
const match = new RegExp(`^${key}\\.(\\d+)\\.(.+)\\${LOCK_FILE_SUFFIX}$`, "u").exec(name);
|
|
1949
|
+
const rawPid = match?.[1];
|
|
1950
|
+
const token = match?.[2];
|
|
1951
|
+
if (rawPid === void 0 || token === void 0 || token.length === 0) {
|
|
1952
|
+
return void 0;
|
|
1953
|
+
}
|
|
1954
|
+
const pid = Number.parseInt(rawPid, 10);
|
|
1955
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
1956
|
+
return void 0;
|
|
1957
|
+
}
|
|
1958
|
+
return {
|
|
1959
|
+
pid,
|
|
1960
|
+
state: "owned",
|
|
1961
|
+
startedAt: new Date(mtimeMs).toISOString(),
|
|
1962
|
+
token,
|
|
1963
|
+
target: "unknown"
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
function ownerIsAlive(metadata, isProcessAlive, getProcessStart) {
|
|
1967
|
+
if (!isProcessAlive(metadata.pid)) {
|
|
1968
|
+
return false;
|
|
1969
|
+
}
|
|
1970
|
+
const currentStart = getProcessStart(metadata.pid);
|
|
1971
|
+
return metadata.processStart === void 0 || currentStart === void 0 || metadata.processStart === currentStart;
|
|
1972
|
+
}
|
|
1973
|
+
function processIsAlive(pid) {
|
|
1974
|
+
try {
|
|
1975
|
+
process.kill(pid, 0);
|
|
1976
|
+
const status = processStatus(pid);
|
|
1977
|
+
return !status?.startsWith("Z");
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
return isNodeError(error, "EPERM");
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
function processStatus(pid) {
|
|
1983
|
+
return runPs(pid, "stat=");
|
|
1984
|
+
}
|
|
1985
|
+
function processStart(pid) {
|
|
1986
|
+
return runPs(pid, "lstart=");
|
|
1987
|
+
}
|
|
1988
|
+
function runPs(pid, field) {
|
|
1989
|
+
try {
|
|
1990
|
+
const value = execFileSync("ps", ["-o", field, "-p", pid.toString()], {
|
|
1991
|
+
encoding: "utf8",
|
|
1992
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1993
|
+
}).trim();
|
|
1994
|
+
return value.length === 0 ? void 0 : value;
|
|
1995
|
+
} catch {
|
|
1996
|
+
return void 0;
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
function alreadyDebuggedError(owner) {
|
|
2000
|
+
const ownerLabel = owner.pid > 0 ? `PID ${owner.pid.toString()}` : "an unknown process";
|
|
2001
|
+
return new CfInspectorError(
|
|
2002
|
+
"TARGET_ALREADY_DEBUGGED",
|
|
2003
|
+
`Another cf-inspector session (${ownerLabel}, started ${owner.startedAt}) is already actively debugging this target. Concurrent debugging sessions on the same isolate(s) can corrupt each other and disrupt real application traffic, so this attempt was refused rather than queued. Wait for the other session to finish, or confirm that it is gone before retrying; locks from dead processes are reclaimed automatically.`
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
function normalizeHost(host) {
|
|
2007
|
+
const normalized = host.trim().toLowerCase();
|
|
2008
|
+
return normalized === "localhost" || normalized === "::1" ? "127.0.0.1" : normalized;
|
|
2009
|
+
}
|
|
2010
|
+
function isRecord2(value) {
|
|
2011
|
+
return typeof value === "object" && value !== null;
|
|
2012
|
+
}
|
|
2013
|
+
function isNodeError(error, code) {
|
|
2014
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
1456
2017
|
// src/cli/warnings.ts
|
|
1457
2018
|
init_types();
|
|
1458
|
-
import
|
|
2019
|
+
import process3 from "process";
|
|
1459
2020
|
|
|
1460
2021
|
// src/cli/captureParser.ts
|
|
1461
2022
|
function parseCaptureList(raw) {
|
|
@@ -1570,7 +2131,7 @@ function warnOnCaptureMutationRisk(expressions, allowMutation) {
|
|
|
1570
2131
|
return;
|
|
1571
2132
|
}
|
|
1572
2133
|
const suffix = allowMutation ? "will run without the V8 side-effect guard because --allow-mutation was passed." : "will be checked by the V8 side-effect guard and blocked unless V8 proves them safe; pass --allow-mutation to run them unrestricted.";
|
|
1573
|
-
|
|
2134
|
+
process3.stderr.write(
|
|
1574
2135
|
`[cf-inspector] warning: ${riskyCount.toString()} capture ${riskyCount === 1 ? "expression looks" : "expressions look"} mutation-capable and ${suffix}
|
|
1575
2136
|
`
|
|
1576
2137
|
);
|
|
@@ -1585,7 +2146,7 @@ function enforceNativeConditionMutationPolicy(expression, allowMutation, context
|
|
|
1585
2146
|
`${context} looks mutation-capable. Native breakpoint conditions cannot be protected by V8's side-effect guard; pass --allow-mutation to arm it explicitly.`
|
|
1586
2147
|
);
|
|
1587
2148
|
}
|
|
1588
|
-
|
|
2149
|
+
process3.stderr.write(
|
|
1589
2150
|
`[cf-inspector] warning: ${context} looks mutation-capable and will run as a native breakpoint condition; native conditions cannot be side-effect-gated.
|
|
1590
2151
|
`
|
|
1591
2152
|
);
|
|
@@ -1594,7 +2155,7 @@ function warnOnMutationRisk(expression, context) {
|
|
|
1594
2155
|
if (!looksLikeMutation(expression)) {
|
|
1595
2156
|
return;
|
|
1596
2157
|
}
|
|
1597
|
-
|
|
2158
|
+
process3.stderr.write(
|
|
1598
2159
|
`[cf-inspector] warning: ${context} looks mutation-capable and will execute against the live inspectee without a side-effect guard.
|
|
1599
2160
|
`
|
|
1600
2161
|
);
|
|
@@ -1603,7 +2164,7 @@ function warnOnUnboundBreakpoints(handles) {
|
|
|
1603
2164
|
for (const handle of handles) {
|
|
1604
2165
|
if (handle.resolvedLocations.length === 0) {
|
|
1605
2166
|
const tsHint = handle.file.endsWith(".ts") ? " Hint: Source TS breakpoints may not bind. Try inspecting loaded scripts with list-scripts and target the compiled .js file instead." : "";
|
|
1606
|
-
|
|
2167
|
+
process3.stderr.write(
|
|
1607
2168
|
`[cf-inspector] warning: breakpoint ${handle.file}:${handle.line.toString()} did not bind to any loaded script. Check the path or pass --remote-root. Use 'list-scripts' to inspect what V8 currently has loaded.${tsHint}
|
|
1608
2169
|
`
|
|
1609
2170
|
);
|
|
@@ -1614,15 +2175,15 @@ function warnOnImplicitInspectorSelection(session, targetWasExplicit, workerWasE
|
|
|
1614
2175
|
const targetCount = session.targetCount ?? 1;
|
|
1615
2176
|
const targetIndex = session.targetIndex ?? 0;
|
|
1616
2177
|
if (!targetWasExplicit && targetCount > 1) {
|
|
1617
|
-
|
|
2178
|
+
process3.stderr.write(
|
|
1618
2179
|
`[cf-inspector] notice: attached to inspector target ${targetIndex.toString()} of ${targetCount.toString()}; pass --target <index> to pick another.
|
|
1619
2180
|
`
|
|
1620
2181
|
);
|
|
1621
2182
|
}
|
|
1622
2183
|
const workerCount = session.workerTargets?.length ?? 0;
|
|
1623
2184
|
if (!workerWasExplicit && workerCount > 0) {
|
|
1624
|
-
|
|
1625
|
-
`[cf-inspector] notice: attached to the main isolate; ${workerCount.toString()} Node ${workerCount === 1 ? "worker is" : "workers are"} available.
|
|
2185
|
+
process3.stderr.write(
|
|
2186
|
+
`[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
2187
|
`
|
|
1627
2188
|
);
|
|
1628
2189
|
}
|
|
@@ -1634,8 +2195,8 @@ function warnOnBoundBreakpointWithoutHit(handles) {
|
|
|
1634
2195
|
if (boundCount === 0) {
|
|
1635
2196
|
return;
|
|
1636
2197
|
}
|
|
1637
|
-
|
|
1638
|
-
`[cf-inspector] warning: ${boundCount.toString()} breakpoint ${boundCount === 1 ? "location bound" : "locations bound"}, but no hit was observed.
|
|
2198
|
+
process3.stderr.write(
|
|
2199
|
+
`[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
2200
|
`
|
|
1640
2201
|
);
|
|
1641
2202
|
}
|
|
@@ -1644,7 +2205,7 @@ function roundDurationMs(durationMs) {
|
|
|
1644
2205
|
}
|
|
1645
2206
|
function warnOnUnmatchedPause(pause) {
|
|
1646
2207
|
const reason = pause.reason.length > 0 ? pause.reason : "unknown";
|
|
1647
|
-
|
|
2208
|
+
process3.stderr.write(
|
|
1648
2209
|
`[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
|
|
1649
2210
|
`
|
|
1650
2211
|
);
|
|
@@ -1701,12 +2262,15 @@ function resolveTarget(opts, options = {}) {
|
|
|
1701
2262
|
const port = parsePositiveInt(opts.port, "--port");
|
|
1702
2263
|
const targetIndex = parseTargetIndex(opts.target);
|
|
1703
2264
|
const workerIndex = parseSelectionIndex(opts.worker, "--worker");
|
|
2265
|
+
const workerId = parseWorkerId(opts.workerId);
|
|
2266
|
+
const mainOnly = opts.mainOnly === true;
|
|
2267
|
+
validateIsolateSelectors(targetIndex, workerIndex, workerId, mainOnly);
|
|
1704
2268
|
if (port !== void 0) {
|
|
1705
2269
|
return {
|
|
1706
2270
|
kind: "port",
|
|
1707
2271
|
port,
|
|
1708
2272
|
host: opts.host ?? "127.0.0.1",
|
|
1709
|
-
...selectionOptions(targetIndex, workerIndex)
|
|
2273
|
+
...selectionOptions(targetIndex, workerIndex, workerId, mainOnly)
|
|
1710
2274
|
};
|
|
1711
2275
|
}
|
|
1712
2276
|
const region = optionalText(opts.region);
|
|
@@ -1733,7 +2297,9 @@ function resolveTarget(opts, options = {}) {
|
|
|
1733
2297
|
app,
|
|
1734
2298
|
parseTunnelTimeout(opts, options),
|
|
1735
2299
|
targetIndex,
|
|
1736
|
-
workerIndex
|
|
2300
|
+
workerIndex,
|
|
2301
|
+
workerId,
|
|
2302
|
+
mainOnly
|
|
1737
2303
|
);
|
|
1738
2304
|
}
|
|
1739
2305
|
async function resolveTargetWithCurrentCfTarget(opts, options = {}) {
|
|
@@ -1764,13 +2330,15 @@ function parseSelectionIndex(raw, label) {
|
|
|
1764
2330
|
function targetIndexOption(targetIndex) {
|
|
1765
2331
|
return targetIndex === void 0 ? {} : { targetIndex };
|
|
1766
2332
|
}
|
|
1767
|
-
function selectionOptions(targetIndex, workerIndex) {
|
|
2333
|
+
function selectionOptions(targetIndex, workerIndex, workerId, mainOnly) {
|
|
1768
2334
|
return {
|
|
1769
2335
|
...targetIndexOption(targetIndex),
|
|
1770
|
-
...workerIndex === void 0 ? {} : { workerIndex }
|
|
2336
|
+
...workerIndex === void 0 ? {} : { workerIndex },
|
|
2337
|
+
...workerId === void 0 ? {} : { workerId },
|
|
2338
|
+
...mainOnly === true ? { mainOnly: true } : {}
|
|
1771
2339
|
};
|
|
1772
2340
|
}
|
|
1773
|
-
function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex, workerIndex) {
|
|
2341
|
+
function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex, workerIndex, workerId, mainOnly) {
|
|
1774
2342
|
return {
|
|
1775
2343
|
kind: "cf",
|
|
1776
2344
|
region,
|
|
@@ -1779,41 +2347,127 @@ function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, t
|
|
|
1779
2347
|
space,
|
|
1780
2348
|
app,
|
|
1781
2349
|
tunnelTimeoutMs: tunnelTimeoutSec * 1e3,
|
|
1782
|
-
...selectionOptions(targetIndex, workerIndex)
|
|
2350
|
+
...selectionOptions(targetIndex, workerIndex, workerId, mainOnly)
|
|
1783
2351
|
};
|
|
1784
2352
|
}
|
|
2353
|
+
function validateIsolateSelectors(targetIndex, workerIndex, workerId, mainOnly) {
|
|
2354
|
+
const workerSelectors = Number(workerIndex !== void 0) + Number(workerId !== void 0);
|
|
2355
|
+
if (workerSelectors > 1 || mainOnly && workerSelectors > 0) {
|
|
2356
|
+
throw new CfInspectorError(
|
|
2357
|
+
"INVALID_ARGUMENT",
|
|
2358
|
+
"Use only one of --worker, --worker-id, or --main-only."
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
if (targetIndex !== void 0 && workerSelectors === 0 && !mainOnly) {
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
1785
2365
|
function optionalText(value) {
|
|
1786
2366
|
const trimmed = value?.trim();
|
|
1787
2367
|
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
1788
2368
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
);
|
|
1806
|
-
|
|
1807
|
-
|
|
2369
|
+
function parseWorkerId(value) {
|
|
2370
|
+
if (value === void 0) {
|
|
2371
|
+
return void 0;
|
|
2372
|
+
}
|
|
2373
|
+
const trimmed = value.trim();
|
|
2374
|
+
if (trimmed.length === 0) {
|
|
2375
|
+
throw new CfInspectorError(
|
|
2376
|
+
"INVALID_ARGUMENT",
|
|
2377
|
+
"Invalid --worker-id: expected a non-empty workerId from list-targets"
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
return trimmed;
|
|
2381
|
+
}
|
|
2382
|
+
async function withSession(target, fn, reportProgress, signal) {
|
|
2383
|
+
const lock = await acquireDebugSessionLock(target);
|
|
2384
|
+
try {
|
|
2385
|
+
const tunnel = await openTarget(target, reportProgress, signal);
|
|
2386
|
+
let session;
|
|
2387
|
+
try {
|
|
2388
|
+
reportProgress?.(
|
|
2389
|
+
`Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
|
|
2390
|
+
);
|
|
2391
|
+
session = await connectInspector({
|
|
2392
|
+
port: tunnel.port,
|
|
2393
|
+
host: tunnel.host,
|
|
2394
|
+
...selectionOptions(target.targetIndex, target.workerIndex),
|
|
2395
|
+
...target.workerId === void 0 ? {} : { workerId: target.workerId }
|
|
2396
|
+
});
|
|
2397
|
+
warnOnImplicitInspectorSelection(
|
|
2398
|
+
session,
|
|
2399
|
+
target.targetIndex !== void 0,
|
|
2400
|
+
target.workerIndex !== void 0 || target.workerId !== void 0
|
|
2401
|
+
);
|
|
2402
|
+
reportProgress?.("Inspector session is ready.");
|
|
2403
|
+
return await fn(session, tunnel.port);
|
|
2404
|
+
} finally {
|
|
2405
|
+
if (session) {
|
|
2406
|
+
reportProgress?.("Closing the inspector session...");
|
|
2407
|
+
await session.dispose();
|
|
2408
|
+
reportProgress?.("Inspector session closed.");
|
|
2409
|
+
}
|
|
2410
|
+
await tunnel.dispose();
|
|
2411
|
+
}
|
|
1808
2412
|
} finally {
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2413
|
+
await lock.release();
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
async function withSessions(target, fn, reportProgress, signal) {
|
|
2417
|
+
const lock = await acquireDebugSessionLock(target);
|
|
2418
|
+
try {
|
|
2419
|
+
const tunnel = await openTarget(target, reportProgress, signal);
|
|
2420
|
+
let group;
|
|
2421
|
+
try {
|
|
2422
|
+
reportProgress?.(
|
|
2423
|
+
`Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
|
|
2424
|
+
);
|
|
2425
|
+
const autoAttach = target.targetIndex === void 0 && target.workerIndex === void 0 && target.workerId === void 0 && target.mainOnly !== true;
|
|
2426
|
+
if (autoAttach) {
|
|
2427
|
+
group = await connectInspectorGroup({ port: tunnel.port, host: tunnel.host });
|
|
2428
|
+
} else {
|
|
2429
|
+
const session = await connectInspector({
|
|
2430
|
+
port: tunnel.port,
|
|
2431
|
+
host: tunnel.host,
|
|
2432
|
+
...selectionOptions(target.targetIndex, target.workerIndex, target.workerId)
|
|
2433
|
+
});
|
|
2434
|
+
group = singleSessionGroup(session);
|
|
2435
|
+
}
|
|
2436
|
+
reportProgress?.("Inspector session is ready.");
|
|
2437
|
+
return await fn(group, tunnel.port);
|
|
2438
|
+
} finally {
|
|
2439
|
+
try {
|
|
2440
|
+
if (group !== void 0) {
|
|
2441
|
+
const sessionCount = group.list().length;
|
|
2442
|
+
reportProgress?.(sessionCount === 1 ? "Closing the inspector session..." : `Closing ${sessionCount.toString()} inspector sessions...`);
|
|
2443
|
+
await group.dispose();
|
|
2444
|
+
reportProgress?.(sessionCount === 1 ? "Inspector session closed." : "Inspector sessions closed.");
|
|
2445
|
+
}
|
|
2446
|
+
} finally {
|
|
2447
|
+
await tunnel.dispose();
|
|
2448
|
+
}
|
|
1813
2449
|
}
|
|
1814
|
-
|
|
2450
|
+
} finally {
|
|
2451
|
+
await lock.release();
|
|
1815
2452
|
}
|
|
1816
2453
|
}
|
|
2454
|
+
function singleSessionGroup(session) {
|
|
2455
|
+
return {
|
|
2456
|
+
targetIndex: session.targetIndex ?? 0,
|
|
2457
|
+
targetCount: session.targetCount ?? 1,
|
|
2458
|
+
workerDiscoverySupported: session.workerDiscoverySupported ?? false,
|
|
2459
|
+
list: () => [session],
|
|
2460
|
+
onSession: (listener) => {
|
|
2461
|
+
listener(session);
|
|
2462
|
+
return () => void 0;
|
|
2463
|
+
},
|
|
2464
|
+
onSessionRemoved: () => () => void 0,
|
|
2465
|
+
onError: () => () => void 0,
|
|
2466
|
+
dispose: async () => {
|
|
2467
|
+
await session.dispose();
|
|
2468
|
+
}
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
1817
2471
|
async function openTarget(target, reportProgress, signal) {
|
|
1818
2472
|
if (target.kind === "port") {
|
|
1819
2473
|
return {
|
|
@@ -1856,7 +2510,7 @@ async function handleAttach(opts) {
|
|
|
1856
2510
|
writeJson({ host: tunnel.host, port: tunnel.port, ...version });
|
|
1857
2511
|
return;
|
|
1858
2512
|
}
|
|
1859
|
-
|
|
2513
|
+
process4.stdout.write(
|
|
1860
2514
|
`Connected to ${tunnel.host}:${tunnel.port.toString()}
|
|
1861
2515
|
Browser: ${version.browser}
|
|
1862
2516
|
Protocol: ${version.protocolVersion}
|
|
@@ -1867,137 +2521,7 @@ async function handleAttach(opts) {
|
|
|
1867
2521
|
}
|
|
1868
2522
|
}
|
|
1869
2523
|
|
|
1870
|
-
// src/cli/commands/
|
|
1871
|
-
import process4 from "process";
|
|
1872
|
-
|
|
1873
|
-
// src/inspector/runtime.ts
|
|
1874
|
-
init_types();
|
|
1875
|
-
async function resume(session) {
|
|
1876
|
-
await session.client.send("Debugger.resume");
|
|
1877
|
-
}
|
|
1878
|
-
async function setPauseOnExceptions(session, state) {
|
|
1879
|
-
await session.client.send("Debugger.setPauseOnExceptions", { state });
|
|
1880
|
-
}
|
|
1881
|
-
async function evaluateOnFrame(session, callFrameId, expression, options = {}) {
|
|
1882
|
-
return await session.client.send("Debugger.evaluateOnCallFrame", {
|
|
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";
|
|
2524
|
+
// src/cli/commands/checkBreakpoint.ts
|
|
2001
2525
|
import process5 from "process";
|
|
2002
2526
|
|
|
2003
2527
|
// src/pathMapper.ts
|
|
@@ -2207,6 +2731,244 @@ async function setBreakpoint(session, input) {
|
|
|
2207
2731
|
async function removeBreakpoint(session, breakpointId) {
|
|
2208
2732
|
await session.client.send("Debugger.removeBreakpoint", { breakpointId });
|
|
2209
2733
|
}
|
|
2734
|
+
function validateCoordinate(value, label) {
|
|
2735
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
2736
|
+
throw new CfInspectorError(
|
|
2737
|
+
"INVALID_ARGUMENT",
|
|
2738
|
+
`${label} must be a non-negative integer, received: ${value.toString()}`
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
function validateScriptLocation(location, label) {
|
|
2743
|
+
if (location.scriptId.trim().length === 0) {
|
|
2744
|
+
throw new CfInspectorError("INVALID_ARGUMENT", `${label}.scriptId must not be empty`);
|
|
2745
|
+
}
|
|
2746
|
+
validateCoordinate(location.lineNumber, `${label}.lineNumber`);
|
|
2747
|
+
if (location.columnNumber !== void 0) {
|
|
2748
|
+
validateCoordinate(location.columnNumber, `${label}.columnNumber`);
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
async function getPossibleBreakpoints(session, options) {
|
|
2752
|
+
validateScriptLocation(options.start, "start");
|
|
2753
|
+
if (options.end !== void 0) {
|
|
2754
|
+
validateScriptLocation(options.end, "end");
|
|
2755
|
+
if (options.end.scriptId !== options.start.scriptId) {
|
|
2756
|
+
throw new CfInspectorError("INVALID_ARGUMENT", "start and end must refer to the same scriptId");
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
const result = await session.client.send(
|
|
2760
|
+
"Debugger.getPossibleBreakpoints",
|
|
2761
|
+
{
|
|
2762
|
+
start: options.start,
|
|
2763
|
+
...options.end === void 0 ? {} : { end: options.end },
|
|
2764
|
+
...options.restrictToFunction === void 0 ? {} : { restrictToFunction: options.restrictToFunction }
|
|
2765
|
+
}
|
|
2766
|
+
);
|
|
2767
|
+
if (!Array.isArray(result.locations)) {
|
|
2768
|
+
throw new CfInspectorError(
|
|
2769
|
+
"CDP_REQUEST_FAILED",
|
|
2770
|
+
"Debugger.getPossibleBreakpoints did not return a locations array"
|
|
2771
|
+
);
|
|
2772
|
+
}
|
|
2773
|
+
return toBreakLocations(result.locations);
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// src/cli/commands/checkBreakpoint.ts
|
|
2777
|
+
async function handleCheckBreakpoint(opts) {
|
|
2778
|
+
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2779
|
+
const location = parseBreakpointSpec(opts.bp);
|
|
2780
|
+
const remoteRoot = parseRemoteRoot(opts.remoteRoot);
|
|
2781
|
+
const urlRegex = buildBreakpointUrlRegex({ file: location.file, remoteRoot });
|
|
2782
|
+
const matcher = new RegExp(urlRegex, "u");
|
|
2783
|
+
const result = await withSessions(target, async (group) => {
|
|
2784
|
+
const checks = (await Promise.all(group.list().map(async (session) => await checkSession(session, matcher, location.line)))).flat();
|
|
2785
|
+
const status = checks.length === 0 ? "script-not-loaded" : checks.some((check) => check.locations.length > 0) ? "breakable" : "unbreakable";
|
|
2786
|
+
return { file: location.file, line: location.line, status, scripts: checks };
|
|
2787
|
+
});
|
|
2788
|
+
if (opts.json) {
|
|
2789
|
+
writeJson(result);
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
writeHumanCheck(result);
|
|
2793
|
+
}
|
|
2794
|
+
async function checkSession(session, matcher, requestedLine) {
|
|
2795
|
+
const zeroBasedLine = requestedLine - 1;
|
|
2796
|
+
const scripts = [...session.scripts.values()].filter((script) => matcher.test(script.url));
|
|
2797
|
+
return await Promise.all(scripts.map(async (script) => {
|
|
2798
|
+
const locations = await getPossibleBreakpoints(session, {
|
|
2799
|
+
start: { scriptId: script.scriptId, lineNumber: zeroBasedLine, columnNumber: 0 },
|
|
2800
|
+
end: { scriptId: script.scriptId, lineNumber: zeroBasedLine + 1, columnNumber: 0 }
|
|
2801
|
+
});
|
|
2802
|
+
return {
|
|
2803
|
+
isolate: session.isolate ?? { kind: "main" },
|
|
2804
|
+
scriptId: script.scriptId,
|
|
2805
|
+
url: script.url,
|
|
2806
|
+
locations: locations.filter((candidate) => candidate.lineNumber === zeroBasedLine)
|
|
2807
|
+
};
|
|
2808
|
+
}));
|
|
2809
|
+
}
|
|
2810
|
+
function writeHumanCheck(result) {
|
|
2811
|
+
if (result.status === "script-not-loaded") {
|
|
2812
|
+
process5.stdout.write(
|
|
2813
|
+
`${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.
|
|
2814
|
+
`
|
|
2815
|
+
);
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
if (result.status === "unbreakable") {
|
|
2819
|
+
process5.stdout.write(
|
|
2820
|
+
`${result.file}:${result.line.toString()} matches a loaded script, but this exact line has no breakable location. Try a neighboring executable line.
|
|
2821
|
+
`
|
|
2822
|
+
);
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
process5.stdout.write(`${result.file}:${result.line.toString()} is breakable:
|
|
2826
|
+
`);
|
|
2827
|
+
for (const script of result.scripts) {
|
|
2828
|
+
for (const location of script.locations) {
|
|
2829
|
+
const isolate = script.isolate.kind === "main" ? "main" : `worker ${script.isolate.workerId}`;
|
|
2830
|
+
process5.stdout.write(
|
|
2831
|
+
` ${isolate} ${script.url} line ${(location.lineNumber + 1).toString()}:${((location.columnNumber ?? 0) + 1).toString()}
|
|
2832
|
+
`
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
// src/cli/commands/eval.ts
|
|
2839
|
+
import process6 from "process";
|
|
2840
|
+
|
|
2841
|
+
// src/inspector/runtime.ts
|
|
2842
|
+
init_types();
|
|
2843
|
+
async function resume(session) {
|
|
2844
|
+
await session.client.send("Debugger.resume");
|
|
2845
|
+
session.debuggerState.paused = false;
|
|
2846
|
+
delete session.debuggerState.currentPause;
|
|
2847
|
+
}
|
|
2848
|
+
async function setPauseOnExceptions(session, state) {
|
|
2849
|
+
await session.client.send("Debugger.setPauseOnExceptions", { state });
|
|
2850
|
+
}
|
|
2851
|
+
async function evaluateOnFrame(session, callFrameId, expression, options = {}) {
|
|
2852
|
+
return await session.client.send("Debugger.evaluateOnCallFrame", {
|
|
2853
|
+
callFrameId,
|
|
2854
|
+
expression,
|
|
2855
|
+
returnByValue: false,
|
|
2856
|
+
generatePreview: true,
|
|
2857
|
+
silent: true,
|
|
2858
|
+
...options.throwOnSideEffect === void 0 ? {} : { throwOnSideEffect: options.throwOnSideEffect },
|
|
2859
|
+
...options.objectGroup === void 0 ? {} : { objectGroup: options.objectGroup }
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
function isSideEffectRefusal(result) {
|
|
2863
|
+
const classNames = [
|
|
2864
|
+
result.result?.className,
|
|
2865
|
+
result.exceptionDetails?.exception?.className
|
|
2866
|
+
];
|
|
2867
|
+
const descriptions = [
|
|
2868
|
+
result.result?.description,
|
|
2869
|
+
result.exceptionDetails?.exception?.description
|
|
2870
|
+
];
|
|
2871
|
+
const isEvalError = classNames.includes("EvalError");
|
|
2872
|
+
return isEvalError && descriptions.some(
|
|
2873
|
+
(description) => typeof description === "string" && description.toLowerCase().includes("possible side-effect in debug-evaluate")
|
|
2874
|
+
);
|
|
2875
|
+
}
|
|
2876
|
+
async function evaluateGlobal(session, expression) {
|
|
2877
|
+
return await session.client.send("Runtime.evaluate", {
|
|
2878
|
+
expression,
|
|
2879
|
+
returnByValue: false,
|
|
2880
|
+
generatePreview: true,
|
|
2881
|
+
silent: true
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
async function runSetupEvals(session, expressions) {
|
|
2885
|
+
for (const expression of expressions) {
|
|
2886
|
+
const result = await evaluateGlobal(session, expression);
|
|
2887
|
+
if (result.exceptionDetails !== void 0) {
|
|
2888
|
+
throw new CfInspectorError(
|
|
2889
|
+
"SETUP_EVAL_FAILED",
|
|
2890
|
+
exceptionDetailsMessage(result, "setup evaluation failed")
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
function exceptionDetailsMessage(result, fallback) {
|
|
2896
|
+
return typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : fallback;
|
|
2897
|
+
}
|
|
2898
|
+
function listScripts(session) {
|
|
2899
|
+
return [...session.scripts.values()];
|
|
2900
|
+
}
|
|
2901
|
+
async function validateExpression(session, expression) {
|
|
2902
|
+
const result = await session.client.send("Runtime.compileScript", {
|
|
2903
|
+
expression,
|
|
2904
|
+
sourceURL: "<cf-inspector-validate>",
|
|
2905
|
+
persistScript: false
|
|
2906
|
+
});
|
|
2907
|
+
if (result.exceptionDetails === void 0) {
|
|
2908
|
+
return;
|
|
2909
|
+
}
|
|
2910
|
+
const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
|
|
2911
|
+
throw new CfInspectorError("INVALID_EXPRESSION", description);
|
|
2912
|
+
}
|
|
2913
|
+
async function getProperties(session, objectId) {
|
|
2914
|
+
const result = await session.client.send("Runtime.getProperties", {
|
|
2915
|
+
objectId,
|
|
2916
|
+
ownProperties: true,
|
|
2917
|
+
accessorPropertiesOnly: false,
|
|
2918
|
+
generatePreview: true
|
|
2919
|
+
});
|
|
2920
|
+
if (!Array.isArray(result.result)) {
|
|
2921
|
+
return [];
|
|
2922
|
+
}
|
|
2923
|
+
return result.result;
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// src/cli/commands/eval.ts
|
|
2927
|
+
async function handleEval(opts) {
|
|
2928
|
+
warnOnMutationRisk(opts.expr, "eval --expr");
|
|
2929
|
+
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2930
|
+
const result = await withSession(target, async (session) => {
|
|
2931
|
+
return await evaluateGlobal(session, opts.expr);
|
|
2932
|
+
});
|
|
2933
|
+
if (opts.json) {
|
|
2934
|
+
writeJson(result);
|
|
2935
|
+
if (result.exceptionDetails !== void 0) {
|
|
2936
|
+
process6.exitCode = 1;
|
|
2937
|
+
}
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
writeHumanEvalResult(result);
|
|
2941
|
+
}
|
|
2942
|
+
function writeHumanEvalResult(result) {
|
|
2943
|
+
if (result.exceptionDetails !== void 0) {
|
|
2944
|
+
const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
2945
|
+
process6.stderr.write(`${detail}
|
|
2946
|
+
`);
|
|
2947
|
+
process6.exitCode = 1;
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
const inner = result.result;
|
|
2951
|
+
if (inner === void 0) {
|
|
2952
|
+
process6.stdout.write("\n");
|
|
2953
|
+
return;
|
|
2954
|
+
}
|
|
2955
|
+
if (typeof inner.value === "string") {
|
|
2956
|
+
process6.stdout.write(`${inner.value}
|
|
2957
|
+
`);
|
|
2958
|
+
return;
|
|
2959
|
+
}
|
|
2960
|
+
if (typeof inner.description === "string") {
|
|
2961
|
+
process6.stdout.write(`${inner.description}
|
|
2962
|
+
`);
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
process6.stdout.write(`${JSON.stringify(inner.value)}
|
|
2966
|
+
`);
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
// src/cli/commands/exception.ts
|
|
2970
|
+
import { performance as performance5 } from "perf_hooks";
|
|
2971
|
+
import process8 from "process";
|
|
2210
2972
|
|
|
2211
2973
|
// src/inspector/pause.ts
|
|
2212
2974
|
init_types();
|
|
@@ -2311,22 +3073,280 @@ async function waitForLivePause(session, options, deadlineMs) {
|
|
|
2311
3073
|
if (remainingMs <= 0) {
|
|
2312
3074
|
throwBreakpointTimeout(options.timeoutMs);
|
|
2313
3075
|
}
|
|
2314
|
-
session.pauseWaitGate.active = true;
|
|
2315
|
-
let receivedAtMs;
|
|
2316
|
-
let params;
|
|
3076
|
+
session.pauseWaitGate.active = true;
|
|
3077
|
+
let receivedAtMs;
|
|
3078
|
+
let params;
|
|
3079
|
+
try {
|
|
3080
|
+
params = await session.client.waitFor("Debugger.paused", {
|
|
3081
|
+
timeoutMs: remainingMs,
|
|
3082
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
3083
|
+
predicate: () => {
|
|
3084
|
+
receivedAtMs = performance3.now();
|
|
3085
|
+
return true;
|
|
3086
|
+
}
|
|
3087
|
+
});
|
|
3088
|
+
} finally {
|
|
3089
|
+
session.pauseWaitGate.active = false;
|
|
3090
|
+
}
|
|
3091
|
+
return toPauseEvent(params, receivedAtMs ?? performance3.now(), session.scripts);
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
// src/inspector/fanout.ts
|
|
3095
|
+
init_types();
|
|
3096
|
+
import { performance as performance4 } from "perf_hooks";
|
|
3097
|
+
var DEFAULT_CLEANUP_TIMEOUT_MS = 2e3;
|
|
3098
|
+
var BreakpointFanout = class {
|
|
3099
|
+
records = /* @__PURE__ */ new Map();
|
|
3100
|
+
setupErrors = [];
|
|
3101
|
+
detach;
|
|
3102
|
+
detachRemoved;
|
|
3103
|
+
detachError;
|
|
3104
|
+
activeRace;
|
|
3105
|
+
pauseReasons = [];
|
|
3106
|
+
constructor(group, setupSession, pauseReasons = []) {
|
|
3107
|
+
this.pauseReasons = pauseReasons;
|
|
3108
|
+
this.detach = group.onSession((session) => {
|
|
3109
|
+
const record = { session, handles: [], setup: Promise.resolve() };
|
|
3110
|
+
this.records.set(session, record);
|
|
3111
|
+
record.setup = setupSession(session, (handle) => {
|
|
3112
|
+
this.trackHandle(session, handle);
|
|
3113
|
+
}).then((result) => {
|
|
3114
|
+
for (const handle of result.handles) {
|
|
3115
|
+
this.trackHandle(session, handle);
|
|
3116
|
+
}
|
|
3117
|
+
});
|
|
3118
|
+
const setup = record.setup;
|
|
3119
|
+
setup.catch((error) => {
|
|
3120
|
+
for (const reject of this.setupErrors) {
|
|
3121
|
+
reject(error);
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
this.activeRace?.add(record);
|
|
3125
|
+
});
|
|
3126
|
+
this.detachRemoved = group.onSessionRemoved((session) => {
|
|
3127
|
+
this.records.delete(session);
|
|
3128
|
+
this.activeRace?.remove(session);
|
|
3129
|
+
});
|
|
3130
|
+
this.detachError = group.onError((error) => {
|
|
3131
|
+
for (const reject of this.setupErrors) {
|
|
3132
|
+
reject(error);
|
|
3133
|
+
}
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
async ready() {
|
|
3137
|
+
await Promise.all([...this.records.values()].map((record) => record.setup));
|
|
3138
|
+
}
|
|
3139
|
+
trackHandle(session, handle) {
|
|
3140
|
+
const record = this.records.get(session);
|
|
3141
|
+
if (record !== void 0 && !record.handles.some((candidate) => candidate.breakpointId === handle.breakpointId)) {
|
|
3142
|
+
record.handles.push(handle);
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
availableOutcomes() {
|
|
3146
|
+
return [...this.records.values()].map((record) => ({
|
|
3147
|
+
session: record.session,
|
|
3148
|
+
setup: { handles: record.handles }
|
|
3149
|
+
}));
|
|
3150
|
+
}
|
|
3151
|
+
async waitForFirst(timeoutMs, options = {}, signal) {
|
|
3152
|
+
if (this.activeRace !== void 0) {
|
|
3153
|
+
throw new CfInspectorError("INVALID_ARGUMENT", "A fan-out pause race is already active");
|
|
3154
|
+
}
|
|
3155
|
+
const race = new ActivePauseRace(timeoutMs, options, signal);
|
|
3156
|
+
this.pauseReasons = options.pauseReasons ?? [];
|
|
3157
|
+
this.activeRace = race;
|
|
3158
|
+
this.setupErrors.push(race.reject);
|
|
3159
|
+
for (const record of this.records.values()) {
|
|
3160
|
+
race.add(record);
|
|
3161
|
+
}
|
|
3162
|
+
try {
|
|
3163
|
+
const winner = await race.result;
|
|
3164
|
+
await race.stopAndSettle();
|
|
3165
|
+
await this.resumePausedLosers(winner.session);
|
|
3166
|
+
return winner;
|
|
3167
|
+
} finally {
|
|
3168
|
+
this.activeRace = void 0;
|
|
3169
|
+
const index = this.setupErrors.indexOf(race.reject);
|
|
3170
|
+
if (index >= 0) {
|
|
3171
|
+
this.setupErrors.splice(index, 1);
|
|
3172
|
+
}
|
|
3173
|
+
await race.stopAndSettle();
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
3176
|
+
async resumePaused(except) {
|
|
3177
|
+
return await this.resumePausedLosers(except, DEFAULT_CLEANUP_TIMEOUT_MS);
|
|
3178
|
+
}
|
|
3179
|
+
async cleanup(timeoutMs = DEFAULT_CLEANUP_TIMEOUT_MS, preservePaused) {
|
|
3180
|
+
this.detach();
|
|
3181
|
+
this.detachRemoved();
|
|
3182
|
+
this.detachError();
|
|
3183
|
+
const deadline = performance4.now() + timeoutMs;
|
|
3184
|
+
await settleWithin(Promise.allSettled([...this.records.values()].map(async (record) => {
|
|
3185
|
+
await record.setup;
|
|
3186
|
+
})), remaining(deadline));
|
|
3187
|
+
const breakpointEntries = [...this.records.values()].flatMap((record) => record.handles.map((handle) => ({
|
|
3188
|
+
session: record.session,
|
|
3189
|
+
breakpointId: handle.breakpointId
|
|
3190
|
+
})));
|
|
3191
|
+
let cleared = 0;
|
|
3192
|
+
const clearWork = Promise.allSettled(breakpointEntries.map(async (entry) => {
|
|
3193
|
+
await removeBreakpoint(entry.session, entry.breakpointId);
|
|
3194
|
+
cleared += 1;
|
|
3195
|
+
}));
|
|
3196
|
+
await settleWithin(clearWork, remaining(deadline));
|
|
3197
|
+
const resumed = await this.resumePausedLosers(preservePaused, remaining(deadline));
|
|
3198
|
+
return { attempted: breakpointEntries.length, cleared, resumed };
|
|
3199
|
+
}
|
|
3200
|
+
async resumePausedLosers(except, timeoutMs = DEFAULT_CLEANUP_TIMEOUT_MS) {
|
|
3201
|
+
let resumed = 0;
|
|
3202
|
+
await settleWithin(Promise.allSettled([...this.records.keys()].map(async (session) => {
|
|
3203
|
+
if (session === except || session.debuggerState.paused !== true || session.client.isClosed || !this.ownsCurrentPause(session)) {
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
await resume(session);
|
|
3207
|
+
session.debuggerState.paused = false;
|
|
3208
|
+
resumed += 1;
|
|
3209
|
+
})), timeoutMs);
|
|
3210
|
+
return resumed;
|
|
3211
|
+
}
|
|
3212
|
+
ownsCurrentPause(session) {
|
|
3213
|
+
const pause = session.debuggerState.currentPause;
|
|
3214
|
+
if (pause === void 0) {
|
|
3215
|
+
return false;
|
|
3216
|
+
}
|
|
3217
|
+
if (this.pauseReasons.includes(pause.reason)) {
|
|
3218
|
+
return true;
|
|
3219
|
+
}
|
|
3220
|
+
const record = this.records.get(session);
|
|
3221
|
+
const breakpointIds = new Set(record?.handles.map((handle) => handle.breakpointId) ?? []);
|
|
3222
|
+
return pause.hitBreakpoints.some((breakpointId) => breakpointIds.has(breakpointId));
|
|
3223
|
+
}
|
|
3224
|
+
};
|
|
3225
|
+
var ActivePauseRace = class {
|
|
3226
|
+
constructor(timeoutMs, options, signal) {
|
|
3227
|
+
this.options = options;
|
|
3228
|
+
this.externalSignal = signal;
|
|
3229
|
+
this.deadline = performance4.now() + timeoutMs;
|
|
3230
|
+
let resolveResult;
|
|
3231
|
+
let rejectResult;
|
|
3232
|
+
this.result = new Promise((resolve, reject) => {
|
|
3233
|
+
resolveResult = resolve;
|
|
3234
|
+
rejectResult = reject;
|
|
3235
|
+
});
|
|
3236
|
+
this.resolveResult = (winner) => {
|
|
3237
|
+
if (this.settled) {
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
this.settled = true;
|
|
3241
|
+
resolveResult?.(winner);
|
|
3242
|
+
};
|
|
3243
|
+
this.reject = (error) => {
|
|
3244
|
+
if (this.settled) {
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
this.settled = true;
|
|
3248
|
+
rejectResult?.(error);
|
|
3249
|
+
};
|
|
3250
|
+
this.timeout = setTimeout(() => {
|
|
3251
|
+
this.reject(this.terminalTimeoutError ?? new CfInspectorError(
|
|
3252
|
+
"BREAKPOINT_NOT_HIT",
|
|
3253
|
+
`Timed out waiting for a matching pause in any isolate after ${timeoutMs.toString()}ms`
|
|
3254
|
+
));
|
|
3255
|
+
this.controller.abort();
|
|
3256
|
+
}, timeoutMs + 25);
|
|
3257
|
+
if (signal !== void 0) {
|
|
3258
|
+
if (signal.aborted) {
|
|
3259
|
+
this.reject(new CfInspectorError("ABORTED", "Aborted while waiting for an isolate pause"));
|
|
3260
|
+
} else {
|
|
3261
|
+
signal.addEventListener("abort", this.onExternalAbort, { once: true });
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
options;
|
|
3266
|
+
controller = new AbortController();
|
|
3267
|
+
waits = /* @__PURE__ */ new Set();
|
|
3268
|
+
settled = false;
|
|
3269
|
+
deadline;
|
|
3270
|
+
timeout;
|
|
3271
|
+
externalSignal;
|
|
3272
|
+
onExternalAbort = () => {
|
|
3273
|
+
this.reject(new CfInspectorError("ABORTED", "Aborted while waiting for an isolate pause"));
|
|
3274
|
+
this.controller.abort();
|
|
3275
|
+
};
|
|
3276
|
+
resolveResult;
|
|
3277
|
+
terminalTimeoutError;
|
|
3278
|
+
removedSessions = /* @__PURE__ */ new Set();
|
|
3279
|
+
reject;
|
|
3280
|
+
result;
|
|
3281
|
+
add(record) {
|
|
3282
|
+
if (this.settled) {
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const wait = this.wait(record).finally(() => {
|
|
3286
|
+
this.waits.delete(wait);
|
|
3287
|
+
});
|
|
3288
|
+
this.waits.add(wait);
|
|
3289
|
+
}
|
|
3290
|
+
remove(session) {
|
|
3291
|
+
this.removedSessions.add(session);
|
|
3292
|
+
}
|
|
3293
|
+
async stopAndSettle() {
|
|
3294
|
+
clearTimeout(this.timeout);
|
|
3295
|
+
this.externalSignal?.removeEventListener("abort", this.onExternalAbort);
|
|
3296
|
+
this.controller.abort();
|
|
3297
|
+
await Promise.allSettled([...this.waits]);
|
|
3298
|
+
}
|
|
3299
|
+
async wait(record) {
|
|
3300
|
+
try {
|
|
3301
|
+
await record.setup;
|
|
3302
|
+
const remainingMs = Math.max(1, this.deadline - performance4.now());
|
|
3303
|
+
const pause = await waitForPause(record.session, {
|
|
3304
|
+
...this.options,
|
|
3305
|
+
timeoutMs: remainingMs,
|
|
3306
|
+
breakpointIds: record.handles.map((handle) => handle.breakpointId),
|
|
3307
|
+
signal: this.controller.signal
|
|
3308
|
+
});
|
|
3309
|
+
record.session.debuggerState.paused = true;
|
|
3310
|
+
this.resolveResult({ session: record.session, pause });
|
|
3311
|
+
this.controller.abort();
|
|
3312
|
+
} catch (error) {
|
|
3313
|
+
if (this.removedSessions.has(record.session)) {
|
|
3314
|
+
return;
|
|
3315
|
+
}
|
|
3316
|
+
if (error instanceof CfInspectorError && error.code === "UNRELATED_PAUSE_TIMEOUT") {
|
|
3317
|
+
this.terminalTimeoutError = error;
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
if (isExpectedRaceStop(error)) {
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
this.reject(error);
|
|
3324
|
+
this.controller.abort();
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
function isExpectedRaceStop(error) {
|
|
3329
|
+
return error instanceof CfInspectorError && (error.code === "ABORTED" || error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT");
|
|
3330
|
+
}
|
|
3331
|
+
function remaining(deadline) {
|
|
3332
|
+
return Math.max(0, deadline - performance4.now());
|
|
3333
|
+
}
|
|
3334
|
+
async function settleWithin(work, timeoutMs) {
|
|
3335
|
+
let timer;
|
|
2317
3336
|
try {
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
}
|
|
2325
|
-
|
|
3337
|
+
return await Promise.race([
|
|
3338
|
+
work,
|
|
3339
|
+
new Promise((resolve) => {
|
|
3340
|
+
timer = setTimeout(() => {
|
|
3341
|
+
resolve(null);
|
|
3342
|
+
}, timeoutMs);
|
|
3343
|
+
})
|
|
3344
|
+
]);
|
|
2326
3345
|
} finally {
|
|
2327
|
-
|
|
3346
|
+
if (timer !== void 0) {
|
|
3347
|
+
clearTimeout(timer);
|
|
3348
|
+
}
|
|
2328
3349
|
}
|
|
2329
|
-
return toPauseEvent(params, receivedAtMs ?? performance3.now(), session.scripts);
|
|
2330
3350
|
}
|
|
2331
3351
|
|
|
2332
3352
|
// src/snapshot/evaluation.ts
|
|
@@ -3040,6 +4060,25 @@ async function captureExpression(session, callFrameId, expression, maxValueLengt
|
|
|
3040
4060
|
|
|
3041
4061
|
// src/cli/commands/exception.ts
|
|
3042
4062
|
init_types();
|
|
4063
|
+
|
|
4064
|
+
// src/cli/signals.ts
|
|
4065
|
+
import process7 from "process";
|
|
4066
|
+
async function withTerminationSignal(fn) {
|
|
4067
|
+
const abort = new AbortController();
|
|
4068
|
+
const onSignal = () => {
|
|
4069
|
+
abort.abort();
|
|
4070
|
+
};
|
|
4071
|
+
process7.once("SIGINT", onSignal);
|
|
4072
|
+
process7.once("SIGTERM", onSignal);
|
|
4073
|
+
try {
|
|
4074
|
+
return await fn(abort.signal);
|
|
4075
|
+
} finally {
|
|
4076
|
+
process7.off("SIGINT", onSignal);
|
|
4077
|
+
process7.off("SIGTERM", onSignal);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4081
|
+
// src/cli/commands/exception.ts
|
|
3043
4082
|
var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
|
|
3044
4083
|
async function handleException(opts) {
|
|
3045
4084
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
@@ -3048,7 +4087,7 @@ async function handleException(opts) {
|
|
|
3048
4087
|
[...prepared.captures, ...prepared.stackCaptures],
|
|
3049
4088
|
opts.allowMutation === true
|
|
3050
4089
|
);
|
|
3051
|
-
const result = await runExceptionCommand(prepared, opts);
|
|
4090
|
+
const result = await withTerminationSignal(async (signal) => await runExceptionCommand(prepared, opts, signal));
|
|
3052
4091
|
if (opts.json) {
|
|
3053
4092
|
writeJson(result);
|
|
3054
4093
|
} else {
|
|
@@ -3078,17 +4117,24 @@ function prepareExceptionCommand(opts, target) {
|
|
|
3078
4117
|
throwOnSideEffect: opts.allowMutation !== true
|
|
3079
4118
|
};
|
|
3080
4119
|
}
|
|
3081
|
-
async function runExceptionCommand(command, opts) {
|
|
3082
|
-
return await
|
|
3083
|
-
|
|
4120
|
+
async function runExceptionCommand(command, opts, signal) {
|
|
4121
|
+
return await withSessions(command.target, async (group) => {
|
|
4122
|
+
const fanout = new BreakpointFanout(group, async (session) => {
|
|
4123
|
+
await setPauseOnExceptions(session, command.state);
|
|
4124
|
+
return { handles: [] };
|
|
4125
|
+
}, ["exception", "promiseRejection"]);
|
|
4126
|
+
let winner;
|
|
4127
|
+
let preserveWinner = false;
|
|
3084
4128
|
try {
|
|
3085
|
-
|
|
3086
|
-
|
|
4129
|
+
await fanout.ready();
|
|
4130
|
+
const hit = await fanout.waitForFirst(command.timeoutMs, {
|
|
3087
4131
|
pauseReasons: ["exception", "promiseRejection"],
|
|
3088
4132
|
unmatchedPausePolicy: "wait-for-resume"
|
|
3089
|
-
});
|
|
3090
|
-
|
|
3091
|
-
const
|
|
4133
|
+
}, signal);
|
|
4134
|
+
winner = hit.session;
|
|
4135
|
+
const pause = hit.pause;
|
|
4136
|
+
const pausedStartedAt = pause.receivedAtMs ?? performance5.now();
|
|
4137
|
+
const snapshot = await captureSnapshot(hit.session, pause, {
|
|
3092
4138
|
captures: command.captures,
|
|
3093
4139
|
includeScopes: opts.includeScopes === true,
|
|
3094
4140
|
maxValueLength: command.maxValueLength,
|
|
@@ -3097,20 +4143,25 @@ async function runExceptionCommand(command, opts) {
|
|
|
3097
4143
|
throwOnSideEffect: command.throwOnSideEffect
|
|
3098
4144
|
});
|
|
3099
4145
|
if (opts.keepPaused === true) {
|
|
3100
|
-
|
|
4146
|
+
preserveWinner = true;
|
|
4147
|
+
return { ...withPausedDuration(snapshot, null), isolate: hit.session.isolate ?? { kind: "main" } };
|
|
3101
4148
|
}
|
|
3102
|
-
|
|
4149
|
+
const result = await resumeAfterException(hit.session, snapshot, pausedStartedAt);
|
|
4150
|
+
return { ...result, isolate: hit.session.isolate ?? { kind: "main" } };
|
|
3103
4151
|
} finally {
|
|
3104
|
-
await
|
|
4152
|
+
await Promise.allSettled(group.list().map(async (session) => {
|
|
4153
|
+
await disablePauseOnExceptionsBestEffort(session);
|
|
4154
|
+
}));
|
|
4155
|
+
await fanout.cleanup(2e3, preserveWinner ? winner : void 0);
|
|
3105
4156
|
}
|
|
3106
|
-
});
|
|
4157
|
+
}, void 0, signal);
|
|
3107
4158
|
}
|
|
3108
4159
|
async function resumeAfterException(session, snapshot, pausedStartedAt) {
|
|
3109
4160
|
try {
|
|
3110
4161
|
await resume(session);
|
|
3111
|
-
return withPausedDuration(snapshot, roundDurationMs(
|
|
4162
|
+
return withPausedDuration(snapshot, roundDurationMs(performance5.now() - pausedStartedAt));
|
|
3112
4163
|
} catch {
|
|
3113
|
-
|
|
4164
|
+
process8.stderr.write(
|
|
3114
4165
|
"[cf-inspector] warning: Debugger.resume failed after exception capture; pausedDurationMs is unknown.\n"
|
|
3115
4166
|
);
|
|
3116
4167
|
return withPausedDuration(snapshot, null);
|
|
@@ -3124,7 +4175,7 @@ async function disablePauseOnExceptionsBestEffort(session) {
|
|
|
3124
4175
|
}
|
|
3125
4176
|
|
|
3126
4177
|
// src/cli/commands/listScripts.ts
|
|
3127
|
-
import
|
|
4178
|
+
import process9 from "process";
|
|
3128
4179
|
async function handleListScripts(opts) {
|
|
3129
4180
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
3130
4181
|
const filter = compileScriptUrlFilter(opts.filter);
|
|
@@ -3134,7 +4185,7 @@ async function handleListScripts(opts) {
|
|
|
3134
4185
|
return;
|
|
3135
4186
|
}
|
|
3136
4187
|
for (const script of scripts) {
|
|
3137
|
-
|
|
4188
|
+
process9.stdout.write(`${script.scriptId} ${script.url}
|
|
3138
4189
|
`);
|
|
3139
4190
|
}
|
|
3140
4191
|
}
|
|
@@ -3165,7 +4216,7 @@ async function buildListedTargets(targets) {
|
|
|
3165
4216
|
return buildListedTarget(target, index, workerResult.supported, workerResult.workers);
|
|
3166
4217
|
} catch (error) {
|
|
3167
4218
|
const message = error instanceof Error ? error.message : String(error);
|
|
3168
|
-
|
|
4219
|
+
process9.stderr.write(
|
|
3169
4220
|
`[cf-inspector] warning: worker discovery failed for raw target ${index.toString()}: ${message}
|
|
3170
4221
|
`
|
|
3171
4222
|
);
|
|
@@ -3192,7 +4243,7 @@ function looksLikeWorkerTarget(target) {
|
|
|
3192
4243
|
return `${target.type} ${target.title} ${target.url}`.toLowerCase().includes("worker");
|
|
3193
4244
|
}
|
|
3194
4245
|
function writeTargetCountSummary(targetCount, workerCount) {
|
|
3195
|
-
|
|
4246
|
+
process9.stderr.write(
|
|
3196
4247
|
`[cf-inspector] ${targetCount.toString()} raw inspector ${targetCount === 1 ? "target" : "targets"}; ${workerCount.toString()} ${workerCount === 1 ? "worker" : "workers"}.
|
|
3197
4248
|
`
|
|
3198
4249
|
);
|
|
@@ -3203,7 +4254,7 @@ function warnOnMissingWorkers(targetCount, workerCount, targets) {
|
|
|
3203
4254
|
}
|
|
3204
4255
|
const supported = targets[0]?.workerDiscoverySupported === true;
|
|
3205
4256
|
const supportHint = supported ? "NodeWorker discovery is available, but no live worker attached." : "This runtime did not expose NodeWorker discovery.";
|
|
3206
|
-
|
|
4257
|
+
process9.stderr.write(
|
|
3207
4258
|
`[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
4259
|
`
|
|
3209
4260
|
);
|
|
@@ -3211,12 +4262,12 @@ function warnOnMissingWorkers(targetCount, workerCount, targets) {
|
|
|
3211
4262
|
function writeHumanTargets(targets) {
|
|
3212
4263
|
for (const target of targets) {
|
|
3213
4264
|
const workerLabel = target.likelyWorker ? " likely-worker" : "";
|
|
3214
|
-
|
|
4265
|
+
process9.stdout.write(
|
|
3215
4266
|
`${target.index.toString()} target ${target.type} ${target.title} ${target.url}${workerLabel}
|
|
3216
4267
|
`
|
|
3217
4268
|
);
|
|
3218
4269
|
for (const worker of target.workers) {
|
|
3219
|
-
|
|
4270
|
+
process9.stdout.write(
|
|
3220
4271
|
` ${worker.index.toString()} worker ${worker.type} ${worker.title} ${worker.url}
|
|
3221
4272
|
`
|
|
3222
4273
|
);
|
|
@@ -3293,7 +4344,7 @@ function matchesFilterTokens(value, tokens) {
|
|
|
3293
4344
|
}
|
|
3294
4345
|
|
|
3295
4346
|
// src/cli/commands/log.ts
|
|
3296
|
-
import
|
|
4347
|
+
import process10 from "process";
|
|
3297
4348
|
|
|
3298
4349
|
// src/logpoint/stream.ts
|
|
3299
4350
|
init_types();
|
|
@@ -3559,25 +4610,6 @@ async function waitForStop(session, options, registerMaxEventsSignal) {
|
|
|
3559
4610
|
|
|
3560
4611
|
// src/cli/commands/log.ts
|
|
3561
4612
|
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
4613
|
async function handleLog(opts) {
|
|
3582
4614
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
3583
4615
|
const location = parseBreakpointSpec(opts.at);
|
|
@@ -3596,12 +4628,8 @@ async function handleLog(opts) {
|
|
|
3596
4628
|
warnOnMutationRisk(condition, "log --condition");
|
|
3597
4629
|
}
|
|
3598
4630
|
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, {
|
|
4631
|
+
await withSessions(target, async (group) => {
|
|
4632
|
+
const result = await runLogGroup(group, {
|
|
3605
4633
|
location,
|
|
3606
4634
|
expression,
|
|
3607
4635
|
remoteRoot,
|
|
@@ -3610,36 +4638,132 @@ async function handleLog(opts) {
|
|
|
3610
4638
|
...hitCount === void 0 ? {} : { hitCount },
|
|
3611
4639
|
...condition === void 0 ? {} : { condition },
|
|
3612
4640
|
maxValueLength,
|
|
3613
|
-
|
|
4641
|
+
json: opts.json,
|
|
4642
|
+
signal
|
|
4643
|
+
});
|
|
4644
|
+
writeLogSummary(result.stoppedReason, result.emitted, opts.json);
|
|
4645
|
+
}, void 0, signal);
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
async function runLogGroup(group, options) {
|
|
4649
|
+
const controller = new AbortController();
|
|
4650
|
+
const tasks = /* @__PURE__ */ new Set();
|
|
4651
|
+
const removedSessions = /* @__PURE__ */ new Set();
|
|
4652
|
+
const results = [];
|
|
4653
|
+
let fatalError;
|
|
4654
|
+
let emitted = 0;
|
|
4655
|
+
let reason = "signal";
|
|
4656
|
+
let resolveStop;
|
|
4657
|
+
const stopped = new Promise((resolve) => {
|
|
4658
|
+
resolveStop = resolve;
|
|
4659
|
+
});
|
|
4660
|
+
const finish = (nextReason) => {
|
|
4661
|
+
if (controller.signal.aborted) {
|
|
4662
|
+
return;
|
|
4663
|
+
}
|
|
4664
|
+
reason = nextReason;
|
|
4665
|
+
controller.abort();
|
|
4666
|
+
resolveStop?.();
|
|
4667
|
+
};
|
|
4668
|
+
const onSignal = () => {
|
|
4669
|
+
finish("signal");
|
|
4670
|
+
};
|
|
4671
|
+
options.signal.addEventListener("abort", onSignal, { once: true });
|
|
4672
|
+
if (options.signal.aborted) {
|
|
4673
|
+
finish("signal");
|
|
4674
|
+
}
|
|
4675
|
+
const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
|
|
4676
|
+
finish("duration");
|
|
4677
|
+
}, options.durationMs);
|
|
4678
|
+
const startSession = (session) => {
|
|
4679
|
+
if (controller.signal.aborted) {
|
|
4680
|
+
return;
|
|
4681
|
+
}
|
|
4682
|
+
const task = (async () => {
|
|
4683
|
+
await validateExpression(session, options.expression);
|
|
4684
|
+
if (options.condition !== void 0) {
|
|
4685
|
+
await validateExpression(session, options.condition);
|
|
4686
|
+
}
|
|
4687
|
+
return await streamLogpoint(session, {
|
|
4688
|
+
location: options.location,
|
|
4689
|
+
expression: options.expression,
|
|
4690
|
+
remoteRoot: options.remoteRoot,
|
|
4691
|
+
...options.hitCount === void 0 ? {} : { hitCount: options.hitCount },
|
|
4692
|
+
...options.condition === void 0 ? {} : { condition: options.condition },
|
|
4693
|
+
maxValueLength: options.maxValueLength,
|
|
4694
|
+
signal: controller.signal,
|
|
3614
4695
|
onEvent: (event) => {
|
|
3615
|
-
|
|
4696
|
+
if (controller.signal.aborted) {
|
|
4697
|
+
return;
|
|
4698
|
+
}
|
|
4699
|
+
emitted += 1;
|
|
4700
|
+
writeLogEvent({ ...event, isolate: session.isolate ?? { kind: "main" } }, options.json);
|
|
4701
|
+
if (options.maxEvents !== void 0 && emitted >= options.maxEvents) {
|
|
4702
|
+
finish("max-events");
|
|
4703
|
+
}
|
|
3616
4704
|
},
|
|
3617
4705
|
onBreakpointSet: (handle) => {
|
|
3618
4706
|
warnOnUnboundBreakpoints([handle]);
|
|
3619
4707
|
}
|
|
3620
4708
|
});
|
|
3621
|
-
|
|
3622
|
-
|
|
4709
|
+
})();
|
|
4710
|
+
tasks.add(task);
|
|
4711
|
+
void task.then(
|
|
4712
|
+
(result) => {
|
|
4713
|
+
results.push(result);
|
|
4714
|
+
if (result.stoppedReason === "transport-closed" && !removedSessions.has(session)) {
|
|
4715
|
+
finish("transport-closed");
|
|
4716
|
+
}
|
|
4717
|
+
},
|
|
4718
|
+
(error) => {
|
|
4719
|
+
fatalError = error;
|
|
4720
|
+
finish("transport-closed");
|
|
3623
4721
|
}
|
|
3624
|
-
|
|
3625
|
-
|
|
4722
|
+
).finally(() => {
|
|
4723
|
+
tasks.delete(task);
|
|
4724
|
+
});
|
|
4725
|
+
};
|
|
4726
|
+
const detach = group.onSession(startSession);
|
|
4727
|
+
const detachRemoved = group.onSessionRemoved((session) => {
|
|
4728
|
+
removedSessions.add(session);
|
|
3626
4729
|
});
|
|
4730
|
+
try {
|
|
4731
|
+
await stopped;
|
|
4732
|
+
await Promise.allSettled([...tasks]);
|
|
4733
|
+
} finally {
|
|
4734
|
+
detach();
|
|
4735
|
+
detachRemoved();
|
|
4736
|
+
if (timer !== void 0) {
|
|
4737
|
+
clearTimeout(timer);
|
|
4738
|
+
}
|
|
4739
|
+
options.signal.removeEventListener("abort", onSignal);
|
|
4740
|
+
}
|
|
4741
|
+
if (emitted === 0 && isZeroHitStop(reason)) {
|
|
4742
|
+
warnOnBoundBreakpointWithoutHit(results.map((result) => result.handle));
|
|
4743
|
+
}
|
|
4744
|
+
if (fatalError !== void 0) {
|
|
4745
|
+
throw fatalError instanceof Error ? fatalError : new Error("Unknown logpoint fan-out failure");
|
|
4746
|
+
}
|
|
4747
|
+
return { emitted, stoppedReason: reason };
|
|
4748
|
+
}
|
|
4749
|
+
function isZeroHitStop(reason) {
|
|
4750
|
+
return reason === "duration" || reason === "signal";
|
|
3627
4751
|
}
|
|
3628
4752
|
function writeLogSummary(stoppedReason, emitted, json) {
|
|
3629
4753
|
if (json) {
|
|
3630
|
-
|
|
4754
|
+
process10.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
|
|
3631
4755
|
`);
|
|
3632
4756
|
return;
|
|
3633
4757
|
}
|
|
3634
|
-
|
|
4758
|
+
process10.stderr.write(
|
|
3635
4759
|
`Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
|
|
3636
4760
|
`
|
|
3637
4761
|
);
|
|
3638
4762
|
}
|
|
3639
4763
|
|
|
3640
4764
|
// src/cli/commands/snapshot.ts
|
|
3641
|
-
import { performance as
|
|
3642
|
-
import
|
|
4765
|
+
import { performance as performance6 } from "perf_hooks";
|
|
4766
|
+
import process11 from "process";
|
|
3643
4767
|
init_types();
|
|
3644
4768
|
async function handleSnapshot(opts) {
|
|
3645
4769
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
@@ -3652,7 +4776,7 @@ async function handleSnapshot(opts) {
|
|
|
3652
4776
|
warnOnMutationRisk(expression, "snapshot --setup-eval");
|
|
3653
4777
|
}
|
|
3654
4778
|
const reportProgress = opts.quiet === true ? void 0 : writeProgress;
|
|
3655
|
-
const result = await runSnapshotCommand(prepared, opts, reportProgress);
|
|
4779
|
+
const result = await withTerminationSignal(async (signal) => await runSnapshotCommand(prepared, opts, reportProgress, signal));
|
|
3656
4780
|
if (opts.json) {
|
|
3657
4781
|
writeJson(result);
|
|
3658
4782
|
} else {
|
|
@@ -3693,45 +4817,95 @@ function prepareSnapshotCommand(opts, target) {
|
|
|
3693
4817
|
throwOnSideEffect: opts.allowMutation !== true
|
|
3694
4818
|
};
|
|
3695
4819
|
}
|
|
3696
|
-
async function runSnapshotCommand(command, opts, reportProgress) {
|
|
3697
|
-
return await
|
|
3698
|
-
|
|
3699
|
-
|
|
4820
|
+
async function runSnapshotCommand(command, opts, reportProgress, signal) {
|
|
4821
|
+
return await withSessions(command.target, async (group) => {
|
|
4822
|
+
if (command.setupEvals.length > 0) {
|
|
4823
|
+
const setupCount = command.setupEvals.length;
|
|
4824
|
+
reportProgress?.(
|
|
4825
|
+
`Running ${setupCount.toString()} setup ${setupCount === 1 ? "evaluation" : "evaluations"}...`
|
|
4826
|
+
);
|
|
4827
|
+
}
|
|
4828
|
+
if (command.condition !== void 0) {
|
|
4829
|
+
reportProgress?.("Validating the breakpoint condition...");
|
|
4830
|
+
}
|
|
4831
|
+
const breakpointCount = command.breakpoints.length;
|
|
4832
|
+
reportProgress?.(
|
|
4833
|
+
`Setting ${breakpointCount.toString()} ${breakpointCount === 1 ? "breakpoint" : "breakpoints"}...`
|
|
4834
|
+
);
|
|
4835
|
+
const fanout = new BreakpointFanout(group, async (session, trackHandle) => {
|
|
4836
|
+
await prepareSnapshotSession(session, command);
|
|
4837
|
+
return { handles: await setCommandBreakpoints(session, command, trackHandle) };
|
|
4838
|
+
});
|
|
4839
|
+
let winner;
|
|
4840
|
+
let preserveWinner = false;
|
|
4841
|
+
try {
|
|
4842
|
+
await fanout.ready();
|
|
4843
|
+
if (command.setupEvals.length > 0) {
|
|
4844
|
+
reportProgress?.("Setup evaluation complete.");
|
|
4845
|
+
}
|
|
4846
|
+
if (command.condition !== void 0) {
|
|
4847
|
+
reportProgress?.("Breakpoint condition is valid.");
|
|
4848
|
+
}
|
|
4849
|
+
const outcomes = fanout.availableOutcomes();
|
|
4850
|
+
reportBreakpointOutcomes(outcomes, reportProgress);
|
|
4851
|
+
reportProgress?.(
|
|
4852
|
+
`Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
|
|
4853
|
+
);
|
|
4854
|
+
const hit = await fanout.waitForFirst(command.timeoutMs, {
|
|
4855
|
+
unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
|
|
4856
|
+
...opts.failOnUnmatchedPause === true ? {} : { onUnmatchedPause: warnOnUnmatchedPause }
|
|
4857
|
+
}, signal);
|
|
4858
|
+
winner = hit.session;
|
|
4859
|
+
const captureCount = command.captures.length;
|
|
4860
|
+
reportProgress?.(
|
|
4861
|
+
`Breakpoint hit; capturing ${captureCount.toString()} ${captureCount === 1 ? "expression" : "expressions"}...`
|
|
4862
|
+
);
|
|
4863
|
+
const result = await captureSnapshotResult(hit.session, hit.pause, command, opts, reportProgress);
|
|
4864
|
+
preserveWinner = opts.keepPaused === true;
|
|
4865
|
+
return { ...result, isolate: hit.session.isolate ?? { kind: "main" } };
|
|
4866
|
+
} catch (error) {
|
|
4867
|
+
if (error instanceof CfInspectorError && (error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT")) {
|
|
4868
|
+
const outcomes = fanout.availableOutcomes();
|
|
4869
|
+
warnOnBoundBreakpointWithoutHit(outcomes.flatMap((outcome) => outcome.setup.handles));
|
|
4870
|
+
}
|
|
4871
|
+
throw error;
|
|
4872
|
+
} finally {
|
|
4873
|
+
const cleanup = await fanout.cleanup(2e3, preserveWinner ? winner : void 0);
|
|
4874
|
+
reportProgress?.(
|
|
4875
|
+
`Breakpoint cleanup: cleared ${cleanup.cleared.toString()} of ${cleanup.attempted.toString()}; resumed ${cleanup.resumed.toString()} paused losing isolates.`
|
|
4876
|
+
);
|
|
4877
|
+
}
|
|
4878
|
+
}, reportProgress, signal);
|
|
3700
4879
|
}
|
|
3701
|
-
async function
|
|
4880
|
+
async function prepareSnapshotSession(session, command) {
|
|
3702
4881
|
if (command.setupEvals.length > 0) {
|
|
3703
|
-
const setupCount = command.setupEvals.length;
|
|
3704
|
-
reportProgress?.(`Running ${setupCount.toString()} setup ${setupCount === 1 ? "evaluation" : "evaluations"}...`);
|
|
3705
4882
|
await runSetupEvals(session, command.setupEvals);
|
|
3706
|
-
reportProgress?.("Setup evaluation complete.");
|
|
3707
4883
|
}
|
|
3708
4884
|
if (command.condition !== void 0) {
|
|
3709
|
-
reportProgress?.("Validating the breakpoint condition...");
|
|
3710
4885
|
await validateExpression(session, command.condition);
|
|
3711
|
-
reportProgress?.("Breakpoint condition is valid.");
|
|
3712
4886
|
}
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
const
|
|
3719
|
-
|
|
4887
|
+
}
|
|
4888
|
+
function reportBreakpointOutcomes(outcomes, reportProgress) {
|
|
4889
|
+
for (const outcome of outcomes) {
|
|
4890
|
+
warnOnUnboundBreakpoints(outcome.setup.handles);
|
|
4891
|
+
}
|
|
4892
|
+
const boundSessions = outcomes.filter((outcome) => outcome.setup.handles.some((handle) => handle.resolvedLocations.length > 0)).length;
|
|
4893
|
+
const locations = outcomes.reduce((total, outcome) => total + outcome.setup.handles.reduce(
|
|
4894
|
+
(sessionTotal, handle) => sessionTotal + handle.resolvedLocations.length,
|
|
3720
4895
|
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;
|
|
4896
|
+
), 0);
|
|
4897
|
+
if (outcomes.length === 1) {
|
|
4898
|
+
reportProgress?.(
|
|
4899
|
+
`Breakpoint setup complete: ${locations.toString()} resolved ${locations === 1 ? "location" : "locations"}.`
|
|
4900
|
+
);
|
|
4901
|
+
return;
|
|
4902
|
+
}
|
|
3731
4903
|
reportProgress?.(
|
|
3732
|
-
`Breakpoint
|
|
4904
|
+
`Breakpoint setup complete: sessions=${outcomes.length.toString()} boundSessions=${boundSessions.toString()} resolvedLocations=${locations.toString()}.`
|
|
3733
4905
|
);
|
|
3734
|
-
|
|
4906
|
+
}
|
|
4907
|
+
async function captureSnapshotResult(session, pause, command, opts, reportProgress) {
|
|
4908
|
+
const pausedStartedAt = pause.receivedAtMs ?? performance6.now();
|
|
3735
4909
|
const snapshot = await captureSnapshot(session, pause, {
|
|
3736
4910
|
captures: command.captures,
|
|
3737
4911
|
includeScopes: opts.includeScopes === true,
|
|
@@ -3741,13 +4915,12 @@ async function runSnapshotOnSession(session, command, opts, reportProgress) {
|
|
|
3741
4915
|
throwOnSideEffect: command.throwOnSideEffect
|
|
3742
4916
|
});
|
|
3743
4917
|
if (opts.keepPaused === true) {
|
|
3744
|
-
reportProgress?.("Snapshot captured; leaving the target paused as requested.");
|
|
3745
4918
|
return withPausedDuration(snapshot, null);
|
|
3746
4919
|
}
|
|
3747
4920
|
reportProgress?.("Snapshot captured; resuming the target...");
|
|
3748
4921
|
return await resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress);
|
|
3749
4922
|
}
|
|
3750
|
-
async function setCommandBreakpoints(session, command) {
|
|
4923
|
+
async function setCommandBreakpoints(session, command, onSet) {
|
|
3751
4924
|
return await Promise.all(
|
|
3752
4925
|
command.breakpoints.map(
|
|
3753
4926
|
(bp) => setBreakpoint(session, {
|
|
@@ -3756,39 +4929,20 @@ async function setCommandBreakpoints(session, command) {
|
|
|
3756
4929
|
remoteRoot: command.remoteRoot,
|
|
3757
4930
|
...command.condition === void 0 ? {} : { condition: command.condition },
|
|
3758
4931
|
...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
|
|
4932
|
+
}).then((handle) => {
|
|
4933
|
+
onSet?.(handle);
|
|
4934
|
+
return handle;
|
|
3759
4935
|
})
|
|
3760
4936
|
)
|
|
3761
4937
|
);
|
|
3762
4938
|
}
|
|
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
4939
|
async function resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress) {
|
|
3786
4940
|
try {
|
|
3787
4941
|
await resume(session);
|
|
3788
4942
|
reportProgress?.("Target resumed.");
|
|
3789
|
-
return withPausedDuration(snapshot, roundDurationMs(
|
|
4943
|
+
return withPausedDuration(snapshot, roundDurationMs(performance6.now() - pausedStartedAt));
|
|
3790
4944
|
} catch {
|
|
3791
|
-
|
|
4945
|
+
process11.stderr.write(
|
|
3792
4946
|
"[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
|
|
3793
4947
|
);
|
|
3794
4948
|
return withPausedDuration(snapshot, null);
|
|
@@ -3800,8 +4954,8 @@ function parseSetupEvals(raw) {
|
|
|
3800
4954
|
}
|
|
3801
4955
|
|
|
3802
4956
|
// src/cli/commands/watch.ts
|
|
3803
|
-
import { performance as
|
|
3804
|
-
import
|
|
4957
|
+
import { performance as performance7 } from "perf_hooks";
|
|
4958
|
+
import process12 from "process";
|
|
3805
4959
|
init_types();
|
|
3806
4960
|
async function handleWatch(opts) {
|
|
3807
4961
|
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
@@ -3816,14 +4970,115 @@ async function handleWatch(opts) {
|
|
|
3816
4970
|
let stoppedReason = "signal";
|
|
3817
4971
|
let emitted = 0;
|
|
3818
4972
|
await withTerminationSignal(async (signal) => {
|
|
3819
|
-
await
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3822
|
-
|
|
4973
|
+
await withSessions(prepared.target, async (group, port) => {
|
|
4974
|
+
const host = prepared.target.kind === "port" ? prepared.target.host : "127.0.0.1";
|
|
4975
|
+
const keepalive = startInspectorKeepalive(host, port);
|
|
4976
|
+
const commandAbort = new AbortController();
|
|
4977
|
+
const onSignal = () => {
|
|
4978
|
+
commandAbort.abort();
|
|
4979
|
+
};
|
|
4980
|
+
signal.addEventListener("abort", onSignal, { once: true });
|
|
4981
|
+
let keepaliveError;
|
|
4982
|
+
void keepalive.failure.catch((error) => {
|
|
4983
|
+
keepaliveError = error;
|
|
4984
|
+
commandAbort.abort();
|
|
4985
|
+
});
|
|
4986
|
+
try {
|
|
4987
|
+
const result = await runWatchGroup(group, prepared, opts, commandAbort.signal);
|
|
4988
|
+
stoppedReason = result.stoppedReason;
|
|
4989
|
+
emitted = result.emitted;
|
|
4990
|
+
if (keepaliveError !== void 0) {
|
|
4991
|
+
throw keepaliveError instanceof Error ? keepaliveError : new Error("Unknown inspector keepalive failure");
|
|
4992
|
+
}
|
|
4993
|
+
} finally {
|
|
4994
|
+
keepalive.cancel();
|
|
4995
|
+
signal.removeEventListener("abort", onSignal);
|
|
4996
|
+
}
|
|
3823
4997
|
}, void 0, signal);
|
|
3824
4998
|
});
|
|
3825
4999
|
writeWatchSummary(stoppedReason, emitted, opts.json);
|
|
3826
5000
|
}
|
|
5001
|
+
async function runWatchGroup(group, command, opts, signal) {
|
|
5002
|
+
const fanout = new BreakpointFanout(group, async (session, trackHandle) => {
|
|
5003
|
+
if (command.setupEvals.length > 0) {
|
|
5004
|
+
await runSetupEvals(session, command.setupEvals);
|
|
5005
|
+
}
|
|
5006
|
+
if (command.condition !== void 0) {
|
|
5007
|
+
await validateExpression(session, command.condition);
|
|
5008
|
+
}
|
|
5009
|
+
const handles = await Promise.all(command.breakpoints.map((bp) => setBreakpoint(session, {
|
|
5010
|
+
file: bp.file,
|
|
5011
|
+
line: bp.line,
|
|
5012
|
+
remoteRoot: command.remoteRoot,
|
|
5013
|
+
...command.condition === void 0 ? {} : { condition: command.condition },
|
|
5014
|
+
...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
|
|
5015
|
+
}).then((handle) => {
|
|
5016
|
+
trackHandle(handle);
|
|
5017
|
+
return handle;
|
|
5018
|
+
})));
|
|
5019
|
+
warnOnUnboundBreakpoints(handles);
|
|
5020
|
+
return { handles };
|
|
5021
|
+
});
|
|
5022
|
+
let emitted = 0;
|
|
5023
|
+
let stoppedReason = "signal";
|
|
5024
|
+
const deadline = computeDeadline(command.durationMs);
|
|
5025
|
+
try {
|
|
5026
|
+
await fanout.ready();
|
|
5027
|
+
while (!signal.aborted) {
|
|
5028
|
+
const remainingMs = remainingForLoop(deadline, command.perHitTimeoutMs);
|
|
5029
|
+
if (remainingMs <= 0) {
|
|
5030
|
+
stoppedReason = "duration";
|
|
5031
|
+
break;
|
|
5032
|
+
}
|
|
5033
|
+
let hit;
|
|
5034
|
+
try {
|
|
5035
|
+
hit = await fanout.waitForFirst(remainingMs, { unmatchedPausePolicy: "wait-for-resume" }, signal);
|
|
5036
|
+
} catch (error) {
|
|
5037
|
+
if (error instanceof CfInspectorError && error.code === "ABORTED") {
|
|
5038
|
+
stoppedReason = "signal";
|
|
5039
|
+
break;
|
|
5040
|
+
}
|
|
5041
|
+
if (error instanceof CfInspectorError && (error.code === "BREAKPOINT_NOT_HIT" || error.code === "UNRELATED_PAUSE_TIMEOUT")) {
|
|
5042
|
+
if (deadline !== void 0 && performance7.now() >= deadline) {
|
|
5043
|
+
stoppedReason = "duration";
|
|
5044
|
+
break;
|
|
5045
|
+
}
|
|
5046
|
+
continue;
|
|
5047
|
+
}
|
|
5048
|
+
throw error;
|
|
5049
|
+
}
|
|
5050
|
+
const event = await captureWatchEvent(hit.session, command, hit.pause, emitted + 1, opts);
|
|
5051
|
+
emitted += 1;
|
|
5052
|
+
writeWatchEvent({ ...event, isolate: hit.session.isolate ?? { kind: "main" } }, opts.json);
|
|
5053
|
+
try {
|
|
5054
|
+
await resume(hit.session);
|
|
5055
|
+
hit.session.debuggerState.paused = false;
|
|
5056
|
+
} catch {
|
|
5057
|
+
process12.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
|
|
5058
|
+
stoppedReason = "transport-closed";
|
|
5059
|
+
break;
|
|
5060
|
+
}
|
|
5061
|
+
if (command.maxEvents !== void 0 && emitted >= command.maxEvents) {
|
|
5062
|
+
stoppedReason = "max-events";
|
|
5063
|
+
break;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
if (signal.aborted) {
|
|
5067
|
+
stoppedReason = "signal";
|
|
5068
|
+
}
|
|
5069
|
+
} finally {
|
|
5070
|
+
const cleanup = await fanout.cleanup();
|
|
5071
|
+
process12.stderr.write(
|
|
5072
|
+
`[cf-inspector] breakpoint cleanup: cleared ${cleanup.cleared.toString()} of ${cleanup.attempted.toString()}; resumed ${cleanup.resumed.toString()} paused isolates.
|
|
5073
|
+
`
|
|
5074
|
+
);
|
|
5075
|
+
}
|
|
5076
|
+
if (emitted === 0 && (stoppedReason === "duration" || stoppedReason === "signal")) {
|
|
5077
|
+
const outcomes = fanout.availableOutcomes();
|
|
5078
|
+
warnOnBoundBreakpointWithoutHit(outcomes.flatMap((outcome) => outcome.setup.handles));
|
|
5079
|
+
}
|
|
5080
|
+
return { emitted, stoppedReason };
|
|
5081
|
+
}
|
|
3827
5082
|
function prepareWatchCommand(opts, target) {
|
|
3828
5083
|
if (opts.bp.length === 0) {
|
|
3829
5084
|
throw new CfInspectorError(
|
|
@@ -3861,147 +5116,21 @@ function prepareWatchCommand(opts, target) {
|
|
|
3861
5116
|
throwOnSideEffect: opts.allowMutation !== true
|
|
3862
5117
|
};
|
|
3863
5118
|
}
|
|
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
5119
|
function computeDeadline(durationMs) {
|
|
3945
5120
|
if (durationMs === void 0) {
|
|
3946
5121
|
return void 0;
|
|
3947
5122
|
}
|
|
3948
|
-
return
|
|
5123
|
+
return performance7.now() + durationMs;
|
|
3949
5124
|
}
|
|
3950
5125
|
function remainingForLoop(deadline, perHitTimeoutMs) {
|
|
3951
5126
|
if (deadline === void 0) {
|
|
3952
5127
|
return perHitTimeoutMs;
|
|
3953
5128
|
}
|
|
3954
|
-
const
|
|
3955
|
-
if (
|
|
5129
|
+
const remaining2 = deadline - performance7.now();
|
|
5130
|
+
if (remaining2 <= 0) {
|
|
3956
5131
|
return 0;
|
|
3957
5132
|
}
|
|
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
|
-
}
|
|
5133
|
+
return Math.min(remaining2, perHitTimeoutMs);
|
|
4005
5134
|
}
|
|
4006
5135
|
async function captureWatchEvent(session, command, pause, hit, opts) {
|
|
4007
5136
|
const snapshot = await captureSnapshot(session, pause, {
|
|
@@ -4037,11 +5166,11 @@ function formatLocation(command, topFrame) {
|
|
|
4037
5166
|
}
|
|
4038
5167
|
function writeWatchSummary(reason, emitted, json) {
|
|
4039
5168
|
if (json) {
|
|
4040
|
-
|
|
5169
|
+
process12.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
|
|
4041
5170
|
`);
|
|
4042
5171
|
return;
|
|
4043
5172
|
}
|
|
4044
|
-
|
|
5173
|
+
process12.stderr.write(
|
|
4045
5174
|
`Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
|
|
4046
5175
|
`
|
|
4047
5176
|
);
|
|
@@ -4061,7 +5190,7 @@ function applyTargetOptions(cmd, options = {}) {
|
|
|
4061
5190
|
"--target <index>",
|
|
4062
5191
|
"Inspector target index from /json/list (default: 0)"
|
|
4063
5192
|
);
|
|
4064
|
-
const withWorkerOption = options.includeWorker === false ? withTargetOption : withTargetOption.option("--worker <index>", "NodeWorker sub-session index listed by list-targets");
|
|
5193
|
+
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
5194
|
return options.includeTimeout === false ? withWorkerOption : withWorkerOption.option("--timeout <seconds>", "Timeout for CF tunnel readiness in seconds (default: 180)");
|
|
4066
5195
|
}
|
|
4067
5196
|
var collectStrings = (value, prev = []) => [
|
|
@@ -4071,7 +5200,7 @@ var collectStrings = (value, prev = []) => [
|
|
|
4071
5200
|
function readPackageVersion() {
|
|
4072
5201
|
let current = dirname(fileURLToPath(import.meta.url));
|
|
4073
5202
|
for (let depth = 0; depth < 4; depth += 1) {
|
|
4074
|
-
const candidate =
|
|
5203
|
+
const candidate = join2(current, "package.json");
|
|
4075
5204
|
try {
|
|
4076
5205
|
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
4077
5206
|
if (typeof parsed === "object" && parsed !== null) {
|
|
@@ -4093,12 +5222,22 @@ async function main(argv) {
|
|
|
4093
5222
|
registerLog(program);
|
|
4094
5223
|
registerWatch(program);
|
|
4095
5224
|
registerException(program);
|
|
5225
|
+
registerCheckBreakpoint(program);
|
|
4096
5226
|
registerEval(program);
|
|
4097
5227
|
registerListScripts(program);
|
|
4098
5228
|
registerListTargets(program);
|
|
4099
5229
|
registerAttach(program);
|
|
4100
5230
|
await program.parseAsync([...argv]);
|
|
4101
5231
|
}
|
|
5232
|
+
function registerCheckBreakpoint(program) {
|
|
5233
|
+
applyTargetOptions(
|
|
5234
|
+
program.command("check-breakpoint").description(
|
|
5235
|
+
"Report whether a loaded script can break at a file:line location"
|
|
5236
|
+
)
|
|
5237
|
+
).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) => {
|
|
5238
|
+
await handleCheckBreakpoint(opts);
|
|
5239
|
+
});
|
|
5240
|
+
}
|
|
4102
5241
|
function registerSnapshot(program) {
|
|
4103
5242
|
applyTargetOptions(
|
|
4104
5243
|
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume"),
|
|
@@ -4169,20 +5308,20 @@ function registerAttach(program) {
|
|
|
4169
5308
|
// src/cli.ts
|
|
4170
5309
|
init_types();
|
|
4171
5310
|
try {
|
|
4172
|
-
await main(
|
|
5311
|
+
await main(process13.argv);
|
|
4173
5312
|
} catch (err) {
|
|
4174
5313
|
if (err instanceof CfInspectorError) {
|
|
4175
|
-
|
|
5314
|
+
process13.stderr.write(`Error [${err.code}]: ${err.message}
|
|
4176
5315
|
`);
|
|
4177
5316
|
if (err.detail !== void 0) {
|
|
4178
|
-
|
|
5317
|
+
process13.stderr.write(` detail: ${err.detail}
|
|
4179
5318
|
`);
|
|
4180
5319
|
}
|
|
4181
|
-
|
|
5320
|
+
process13.exit(1);
|
|
4182
5321
|
}
|
|
4183
5322
|
const message = err instanceof Error ? err.message : String(err);
|
|
4184
|
-
|
|
5323
|
+
process13.stderr.write(`Error: ${message}
|
|
4185
5324
|
`);
|
|
4186
|
-
|
|
5325
|
+
process13.exit(1);
|
|
4187
5326
|
}
|
|
4188
5327
|
//# sourceMappingURL=cli.js.map
|