@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/dist/cli.js CHANGED
@@ -152,16 +152,16 @@ var init_wsTransport = __esm({
152
152
  });
153
153
 
154
154
  // src/cli.ts
155
- import process11 from "process";
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 process3 from "process";
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 process from "process";
389
+ import process2 from "process";
340
390
  function writeProgress(message) {
341
- process.stderr.write(`[cf-inspector] ${message}
391
+ process2.stderr.write(`[cf-inspector] ${message}
342
392
  `);
343
393
  }
344
394
  function writeJson(value) {
345
- process.stdout.write(`${JSON.stringify(value, null, 2)}
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
- process.stdout.write(`${lines.join("\n")}
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
- process.stdout.write(`${JSON.stringify(event)}
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
- process.stdout.write(`[${event.ts}] ${event.at} !err ${renderTruncated(event.error, event)}
475
+ process2.stdout.write(`[${event.ts}] ${event.at}${isolateSuffix} !err ${renderTruncated(event.error, event)}
424
476
  `);
425
477
  return;
426
478
  }
427
- process.stdout.write(`[${event.ts}] ${event.at} ${renderTruncated(event.value ?? "", event)}
479
+ 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
- process.stdout.write(`${JSON.stringify(event)}
484
+ process2.stdout.write(`${JSON.stringify(event)}
433
485
  `);
434
486
  return;
435
487
  }
436
- process.stdout.write(`[${event.ts}] hit#${event.hit.toString()} ${event.at}
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
- process.stdout.write(` exception: ${renderExceptionDetail(event.exception)}
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
- process.stdout.write(` ${capture.expression} = ${renderTruncated(detail, capture)}
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 at index ${workerIndex.toString()} (available: ${workers.length.toString()}). Ensure the worker is alive, then rerun list-targets.`
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 process2 from "process";
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
- process2.stderr.write(
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
- process2.stderr.write(
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
- process2.stderr.write(
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
- process2.stderr.write(
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
- process2.stderr.write(
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
- process2.stderr.write(
1625
- `[cf-inspector] notice: attached to the main isolate; ${workerCount.toString()} Node ${workerCount === 1 ? "worker is" : "workers are"} available. Run list-targets and pass --worker <index> to inspect one.
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
- process2.stderr.write(
1638
- `[cf-inspector] warning: ${boundCount.toString()} breakpoint ${boundCount === 1 ? "location bound" : "locations bound"}, but no hit was observed. The code may be running in another worker isolate. Run list-targets and retry with --worker <index> for a NodeWorker sub-session or --target <index> for a raw target.
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
- process2.stderr.write(
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
- async function withSession(target, fn, reportProgress, signal) {
1790
- const tunnel = await openTarget(target, reportProgress, signal);
1791
- let session;
1792
- try {
1793
- reportProgress?.(
1794
- `Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
1795
- );
1796
- session = await connectInspector({
1797
- port: tunnel.port,
1798
- host: tunnel.host,
1799
- ...selectionOptions(target.targetIndex, target.workerIndex)
1800
- });
1801
- warnOnImplicitInspectorSelection(
1802
- session,
1803
- target.targetIndex !== void 0,
1804
- target.workerIndex !== void 0
1805
- );
1806
- reportProgress?.("Inspector session is ready.");
1807
- return await fn(session, tunnel.port);
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
- if (session) {
1810
- reportProgress?.("Closing the inspector session...");
1811
- await session.dispose();
1812
- reportProgress?.("Inspector session closed.");
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
- await tunnel.dispose();
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
- process3.stdout.write(
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/eval.ts
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
- params = await session.client.waitFor("Debugger.paused", {
2319
- timeoutMs: remainingMs,
2320
- ...options.signal === void 0 ? {} : { signal: options.signal },
2321
- predicate: () => {
2322
- receivedAtMs = performance3.now();
2323
- return true;
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
- session.pauseWaitGate.active = false;
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 withSession(command.target, async (session) => {
3083
- await setPauseOnExceptions(session, command.state);
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
- const pause = await waitForPause(session, {
3086
- timeoutMs: command.timeoutMs,
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
- const pausedStartedAt = pause.receivedAtMs ?? performance4.now();
3091
- const snapshot = await captureSnapshot(session, pause, {
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
- return withPausedDuration(snapshot, null);
4146
+ preserveWinner = true;
4147
+ return { ...withPausedDuration(snapshot, null), isolate: hit.session.isolate ?? { kind: "main" } };
3101
4148
  }
3102
- return await resumeAfterException(session, snapshot, pausedStartedAt);
4149
+ const result = await resumeAfterException(hit.session, snapshot, pausedStartedAt);
4150
+ return { ...result, isolate: hit.session.isolate ?? { kind: "main" } };
3103
4151
  } finally {
3104
- await disablePauseOnExceptionsBestEffort(session);
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(performance4.now() - pausedStartedAt));
4162
+ return withPausedDuration(snapshot, roundDurationMs(performance5.now() - pausedStartedAt));
3112
4163
  } catch {
3113
- process5.stderr.write(
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 process6 from "process";
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
- process6.stdout.write(`${script.scriptId} ${script.url}
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
- process6.stderr.write(
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
- process6.stderr.write(
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
- process6.stderr.write(
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
- process6.stdout.write(
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
- process6.stdout.write(
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 process8 from "process";
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 withSession(target, async (session) => {
3600
- await validateExpression(session, expression);
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
- signal,
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
- writeLogEvent(event, opts.json);
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
- if (result.emitted === 0 && (result.stoppedReason === "duration" || result.stoppedReason === "signal")) {
3622
- warnOnBoundBreakpointWithoutHit([result.handle]);
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
- writeLogSummary(result.stoppedReason, result.emitted, opts.json);
3625
- }, void 0, signal);
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
- process8.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
4754
+ process10.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
3631
4755
  `);
3632
4756
  return;
3633
4757
  }
3634
- process8.stderr.write(
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 performance5 } from "perf_hooks";
3642
- import process9 from "process";
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 withSession(command.target, async (session) => {
3698
- return await runSnapshotOnSession(session, command, opts, reportProgress);
3699
- }, reportProgress);
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 runSnapshotOnSession(session, command, opts, reportProgress) {
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
- const breakpointCount = command.breakpoints.length;
3714
- reportProgress?.(
3715
- `Setting ${breakpointCount.toString()} ${breakpointCount === 1 ? "breakpoint" : "breakpoints"}...`
3716
- );
3717
- const handles = await setCommandBreakpoints(session, command);
3718
- const resolvedCount = handles.reduce(
3719
- (total, handle) => total + handle.resolvedLocations.length,
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
- reportProgress?.(
3723
- `Breakpoint setup complete: ${resolvedCount.toString()} resolved ${resolvedCount === 1 ? "location" : "locations"}.`
3724
- );
3725
- warnOnUnboundBreakpoints(handles);
3726
- reportProgress?.(
3727
- `Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
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 hit; capturing ${captureCount.toString()} ${captureCount === 1 ? "expression" : "expressions"}...`
4904
+ `Breakpoint setup complete: sessions=${outcomes.length.toString()} boundSessions=${boundSessions.toString()} resolvedLocations=${locations.toString()}.`
3733
4905
  );
3734
- const pausedStartedAt = pause.receivedAtMs ?? performance5.now();
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(performance5.now() - pausedStartedAt));
4943
+ return withPausedDuration(snapshot, roundDurationMs(performance6.now() - pausedStartedAt));
3790
4944
  } catch {
3791
- process9.stderr.write(
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 performance6 } from "perf_hooks";
3804
- import process10 from "process";
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 withSession(prepared.target, async (session) => {
3820
- const result = await runWatchLoop(session, prepared, opts, signal);
3821
- stoppedReason = result.stoppedReason;
3822
- emitted = result.emitted;
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 performance6.now() + durationMs;
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 remaining = deadline - performance6.now();
3955
- if (remaining <= 0) {
5129
+ const remaining2 = deadline - performance7.now();
5130
+ if (remaining2 <= 0) {
3956
5131
  return 0;
3957
5132
  }
3958
- return Math.min(remaining, perHitTimeoutMs);
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
- process10.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
5169
+ process12.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
4041
5170
  `);
4042
5171
  return;
4043
5172
  }
4044
- process10.stderr.write(
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 = join(current, "package.json");
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(process11.argv);
5311
+ await main(process13.argv);
4173
5312
  } catch (err) {
4174
5313
  if (err instanceof CfInspectorError) {
4175
- process11.stderr.write(`Error [${err.code}]: ${err.message}
5314
+ process13.stderr.write(`Error [${err.code}]: ${err.message}
4176
5315
  `);
4177
5316
  if (err.detail !== void 0) {
4178
- process11.stderr.write(` detail: ${err.detail}
5317
+ process13.stderr.write(` detail: ${err.detail}
4179
5318
  `);
4180
5319
  }
4181
- process11.exit(1);
5320
+ process13.exit(1);
4182
5321
  }
4183
5322
  const message = err instanceof Error ? err.message : String(err);
4184
- process11.stderr.write(`Error: ${message}
5323
+ process13.stderr.write(`Error: ${message}
4185
5324
  `);
4186
- process11.exit(1);
5325
+ process13.exit(1);
4187
5326
  }
4188
5327
  //# sourceMappingURL=cli.js.map