@saptools/cf-inspector 0.4.0 → 0.4.2

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
@@ -35,10 +35,19 @@ __export(wsTransport_exports, {
35
35
  wsTransportFactory: () => wsTransportFactory
36
36
  });
37
37
  import { WebSocket } from "ws";
38
- async function wsTransportFactory(url) {
38
+ async function wsTransportFactory(url, options = {}) {
39
39
  const socket = new WebSocket(url, { perMessageDeflate: false });
40
- await waitForOpen(socket, url);
41
- const wrappers = /* @__PURE__ */ new WeakMap();
40
+ await waitForOpen(socket, url, options.connectTimeoutMs);
41
+ const wrappers = /* @__PURE__ */ new Map();
42
+ function wrappersFor(event) {
43
+ const existing = wrappers.get(event);
44
+ if (existing !== void 0) {
45
+ return existing;
46
+ }
47
+ const created = /* @__PURE__ */ new WeakMap();
48
+ wrappers.set(event, created);
49
+ return created;
50
+ }
42
51
  return {
43
52
  send(payload) {
44
53
  socket.send(payload);
@@ -50,11 +59,12 @@ async function wsTransportFactory(url) {
50
59
  return socket.readyState;
51
60
  },
52
61
  on(event, listener) {
53
- const wrapped = wrapListener(event, listener, wrappers);
62
+ const wrapped = wrapListener(event, listener);
63
+ wrappersFor(event).set(listener, wrapped);
54
64
  socket.on(event, wrapped);
55
65
  },
56
66
  off(event, listener) {
57
- const wrapped = wrappers.get(listener);
67
+ const wrapped = wrappersFor(event).get(listener);
58
68
  if (!wrapped) {
59
69
  return;
60
70
  }
@@ -62,14 +72,30 @@ async function wsTransportFactory(url) {
62
72
  }
63
73
  };
64
74
  }
65
- async function waitForOpen(socket, url) {
75
+ async function waitForOpen(socket, url, timeoutMs) {
66
76
  await new Promise((resolve, reject) => {
67
- const onOpen = () => {
77
+ let settled = false;
78
+ const cleanup = () => {
79
+ socket.off("open", onOpen);
68
80
  socket.off("error", onError);
81
+ if (timer !== void 0) {
82
+ clearTimeout(timer);
83
+ }
84
+ };
85
+ const onOpen = () => {
86
+ if (settled) {
87
+ return;
88
+ }
89
+ settled = true;
90
+ cleanup();
69
91
  resolve();
70
92
  };
71
93
  const onError = (err) => {
72
- socket.off("open", onOpen);
94
+ if (settled) {
95
+ return;
96
+ }
97
+ settled = true;
98
+ cleanup();
73
99
  reject(
74
100
  new CfInspectorError(
75
101
  "INSPECTOR_CONNECTION_FAILED",
@@ -77,29 +103,45 @@ async function waitForOpen(socket, url) {
77
103
  )
78
104
  );
79
105
  };
106
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
107
+ if (settled) {
108
+ return;
109
+ }
110
+ settled = true;
111
+ cleanup();
112
+ socket.on("error", () => {
113
+ });
114
+ try {
115
+ socket.terminate();
116
+ } catch {
117
+ }
118
+ reject(
119
+ new CfInspectorError(
120
+ "INSPECTOR_CONNECTION_FAILED",
121
+ `WebSocket handshake to ${url} timed out after ${timeoutMs.toString()}ms`
122
+ )
123
+ );
124
+ }, timeoutMs);
80
125
  socket.once("open", onOpen);
81
126
  socket.once("error", onError);
82
127
  });
83
128
  }
84
- function wrapListener(event, listener, wrappers) {
129
+ function wrapListener(event, listener) {
85
130
  if (event === "message") {
86
131
  const wrapped2 = (data) => {
87
132
  listener(data.toString("utf8"));
88
133
  };
89
- wrappers.set(listener, wrapped2);
90
134
  return wrapped2;
91
135
  }
92
136
  if (event === "close") {
93
137
  const wrapped2 = () => {
94
138
  listener();
95
139
  };
96
- wrappers.set(listener, wrapped2);
97
140
  return wrapped2;
98
141
  }
99
142
  const wrapped = (err) => {
100
143
  listener(err);
101
144
  };
102
- wrappers.set(listener, wrapped);
103
145
  return wrapped;
104
146
  }
105
147
  var init_wsTransport = __esm({
@@ -110,7 +152,7 @@ var init_wsTransport = __esm({
110
152
  });
111
153
 
112
154
  // src/cli.ts
113
- import process10 from "process";
155
+ import process11 from "process";
114
156
 
115
157
  // src/cli/program.ts
116
158
  import { Command } from "commander";
@@ -238,6 +280,10 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
238
280
 
239
281
  // src/cli/output.ts
240
282
  import process from "process";
283
+ function writeProgress(message) {
284
+ process.stderr.write(`[cf-inspector] ${message}
285
+ `);
286
+ }
241
287
  function writeJson(value) {
242
288
  process.stdout.write(`${JSON.stringify(value, null, 2)}
243
289
  `);
@@ -352,7 +398,8 @@ async function openCfTunnel(target) {
352
398
  ...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
353
399
  ...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
354
400
  ...target.verbose === void 0 ? {} : { verbose: target.verbose },
355
- ...target.signal === void 0 ? {} : { signal: target.signal }
401
+ ...target.signal === void 0 ? {} : { signal: target.signal },
402
+ ...target.onStatus === void 0 ? {} : { onStatus: target.onStatus }
356
403
  };
357
404
  const handle = await startDebugger(opts);
358
405
  return {
@@ -404,10 +451,19 @@ var CdpClient = class _CdpClient {
404
451
  return;
405
452
  }
406
453
  if (typeof parsed.method === "string") {
407
- this.emitter.emit(parsed.method, parsed.params);
408
- this.emitter.emit("event", { method: parsed.method, params: parsed.params });
454
+ this.safeEmit(parsed.method, parsed.params);
455
+ this.safeEmit("event", { method: parsed.method, params: parsed.params });
409
456
  }
410
457
  };
458
+ safeEmit(event, payload) {
459
+ const listeners = this.emitter.listeners(event);
460
+ for (const listener of listeners) {
461
+ try {
462
+ listener(payload);
463
+ } catch {
464
+ }
465
+ }
466
+ }
411
467
  handleClose = () => {
412
468
  this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
413
469
  };
@@ -425,7 +481,8 @@ var CdpClient = class _CdpClient {
425
481
  }
426
482
  static async connect(options) {
427
483
  const factory = options.transportFactory ?? await loadDefaultFactory();
428
- const transport = await factory(options.url);
484
+ const factoryOptions = options.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs };
485
+ const transport = await factory(options.url, factoryOptions);
429
486
  return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
430
487
  }
431
488
  async send(method, params = {}) {
@@ -475,8 +532,16 @@ var CdpClient = class _CdpClient {
475
532
  return;
476
533
  }
477
534
  const params = raw;
478
- if (options.predicate && !options.predicate(params)) {
479
- return;
535
+ if (options.predicate) {
536
+ let accepted;
537
+ try {
538
+ accepted = options.predicate(params);
539
+ } catch {
540
+ return;
541
+ }
542
+ if (!accepted) {
543
+ return;
544
+ }
480
545
  }
481
546
  finish(params);
482
547
  });
@@ -716,7 +781,18 @@ async function connectInspector(options) {
716
781
  `No inspector targets available on ${host}:${options.port.toString()}`
717
782
  );
718
783
  }
719
- const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl });
784
+ const client = await CdpClient.connect({
785
+ url: target.webSocketDebuggerUrl,
786
+ connectTimeoutMs
787
+ });
788
+ try {
789
+ return await initSession(client, target);
790
+ } catch (err) {
791
+ client.dispose();
792
+ throw err;
793
+ }
794
+ }
795
+ async function initSession(client, target) {
720
796
  const scripts = /* @__PURE__ */ new Map();
721
797
  client.on("Debugger.scriptParsed", (raw) => {
722
798
  const params = raw;
@@ -772,6 +848,22 @@ var DEFAULT_CF_TIMEOUT_SEC = 60;
772
848
  var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
773
849
 
774
850
  // src/cli/target.ts
851
+ var CF_TUNNEL_STATUS_MESSAGES = {
852
+ starting: "Preparing the Cloud Foundry debugger...",
853
+ "logging-in": "Logging in to Cloud Foundry...",
854
+ targeting: "Targeting the Cloud Foundry org and space...",
855
+ "ssh-enabling": "Enabling SSH for the Cloud Foundry app...",
856
+ "ssh-restarting": "Restarting the Cloud Foundry app to activate SSH...",
857
+ signaling: "Starting the remote Node.js inspector...",
858
+ tunneling: "Opening the SSH inspector tunnel...",
859
+ ready: "Cloud Foundry inspector tunnel is ready.",
860
+ stopping: "Closing the Cloud Foundry inspector tunnel...",
861
+ stopped: "Cloud Foundry inspector tunnel closed.",
862
+ error: "Cloud Foundry inspector tunnel failed."
863
+ };
864
+ function formatCfTunnelStatus(status) {
865
+ return CF_TUNNEL_STATUS_MESSAGES[status];
866
+ }
775
867
  function parsePositiveInt(raw, label) {
776
868
  if (raw === void 0) {
777
869
  return void 0;
@@ -806,20 +898,26 @@ function resolveTarget(opts) {
806
898
  function hasCfTarget(opts) {
807
899
  return opts.region !== void 0 && opts.org !== void 0 && opts.space !== void 0 && opts.app !== void 0;
808
900
  }
809
- async function withSession(target, fn) {
810
- const tunnel = await openTarget(target);
901
+ async function withSession(target, fn, reportProgress) {
902
+ const tunnel = await openTarget(target, reportProgress);
811
903
  let session;
812
904
  try {
905
+ reportProgress?.(
906
+ `Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
907
+ );
813
908
  session = await connectInspector({ port: tunnel.port, host: tunnel.host });
909
+ reportProgress?.("Inspector session is ready.");
814
910
  return await fn(session, tunnel.port);
815
911
  } finally {
816
912
  if (session) {
913
+ reportProgress?.("Closing the inspector session...");
817
914
  await session.dispose();
915
+ reportProgress?.("Inspector session closed.");
818
916
  }
819
917
  await tunnel.dispose();
820
918
  }
821
919
  }
822
- async function openTarget(target) {
920
+ async function openTarget(target, reportProgress) {
823
921
  if (target.kind === "port") {
824
922
  return {
825
923
  port: target.port,
@@ -827,12 +925,18 @@ async function openTarget(target) {
827
925
  dispose: () => Promise.resolve()
828
926
  };
829
927
  }
928
+ reportProgress?.(formatCfTunnelStatus("starting"));
830
929
  const tunnel = await openCfTunnel({
831
930
  region: target.region,
832
931
  org: target.org,
833
932
  space: target.space,
834
933
  app: target.app,
835
- tunnelReadyTimeoutMs: target.cfTimeoutMs
934
+ tunnelReadyTimeoutMs: target.cfTimeoutMs,
935
+ ...reportProgress === void 0 ? {} : {
936
+ onStatus: (status) => {
937
+ reportProgress(formatCfTunnelStatus(status));
938
+ }
939
+ }
836
940
  });
837
941
  return {
838
942
  port: tunnel.localPort,
@@ -1066,9 +1170,11 @@ function normalizeRegexRootPattern(pattern) {
1066
1170
  const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
1067
1171
  return stripTrailingSlash(withoutEndAnchor);
1068
1172
  }
1069
- function buildFileUrlRegex(rootPattern, tail) {
1070
- const separator = rootPattern.endsWith("/") ? "" : "/";
1071
- return `^file://${rootPattern}${separator}${tail}$`;
1173
+ function buildFileUrlRegex(embeddedRoot, tail, separator) {
1174
+ return `^file://${embeddedRoot}${separator}${tail}$`;
1175
+ }
1176
+ function rootSeparator(rawPattern) {
1177
+ return rawPattern.endsWith("/") ? "" : "/";
1072
1178
  }
1073
1179
  function escapeRegExp(value) {
1074
1180
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -1095,12 +1201,12 @@ function buildBreakpointUrlRegex(input) {
1095
1201
  return `(?:^|/)${tail}$`;
1096
1202
  }
1097
1203
  case "literal": {
1098
- const escapedRoot = escapeRegExp(input.remoteRoot.value);
1099
- return buildFileUrlRegex(escapedRoot, tail);
1204
+ const root = input.remoteRoot.value;
1205
+ return buildFileUrlRegex(escapeRegExp(root), tail, rootSeparator(root));
1100
1206
  }
1101
1207
  case "regex": {
1102
- const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
1103
- return buildFileUrlRegex(rootPattern, tail);
1208
+ const root = normalizeRegexRootPattern(input.remoteRoot.pattern);
1209
+ return buildFileUrlRegex(`(?:${root})`, tail, rootSeparator(root));
1104
1210
  }
1105
1211
  }
1106
1212
  }
@@ -1364,19 +1470,32 @@ function scalarFromVariable(variable) {
1364
1470
  }
1365
1471
  return value === "null" ? null : value;
1366
1472
  }
1473
+ function isArrayLikeChildren(children) {
1474
+ let hasNumeric = false;
1475
+ for (const child of children) {
1476
+ if (child.name === "length") {
1477
+ continue;
1478
+ }
1479
+ if (parseNumericIndex(child.name) === void 0) {
1480
+ return false;
1481
+ }
1482
+ hasNumeric = true;
1483
+ }
1484
+ return hasNumeric;
1485
+ }
1367
1486
  function toStructuredValue(variable) {
1368
1487
  const children = variable.children;
1369
1488
  if (children === void 0 || children.length === 0) {
1370
1489
  return scalarFromVariable(variable);
1371
1490
  }
1372
- const indexed = children.flatMap((child) => {
1373
- const index = parseNumericIndex(child.name);
1374
- if (index === void 0) {
1375
- return [];
1376
- }
1377
- return [[index, toStructuredValue(child)]];
1378
- });
1379
- if (indexed.length > 0) {
1491
+ if (isArrayLikeChildren(children)) {
1492
+ const indexed = children.flatMap((child) => {
1493
+ const index = parseNumericIndex(child.name);
1494
+ if (index === void 0) {
1495
+ return [];
1496
+ }
1497
+ return [[index, toStructuredValue(child)]];
1498
+ });
1380
1499
  const maxIndex = Math.max(...indexed.map(([index]) => index));
1381
1500
  const out2 = Array.from({ length: maxIndex + 1 }, () => null);
1382
1501
  for (const [index, entry] of indexed) {
@@ -2041,7 +2160,7 @@ async function handleListScripts(opts) {
2041
2160
  }
2042
2161
 
2043
2162
  // src/cli/commands/log.ts
2044
- import process7 from "process";
2163
+ import process8 from "process";
2045
2164
 
2046
2165
  // src/logpoint/stream.ts
2047
2166
  init_types();
@@ -2200,7 +2319,10 @@ async function streamLogpoint(session, options) {
2200
2319
  return;
2201
2320
  }
2202
2321
  emitted += 1;
2203
- options.onEvent(event);
2322
+ try {
2323
+ options.onEvent(event);
2324
+ } catch {
2325
+ }
2204
2326
  if (maxEvents !== void 0 && emitted >= maxEvents) {
2205
2327
  maxEventsReached = true;
2206
2328
  stopMaxEvents?.();
@@ -2285,6 +2407,25 @@ async function waitForStop(session, options, registerMaxEventsSignal) {
2285
2407
 
2286
2408
  // src/cli/commands/log.ts
2287
2409
  init_types();
2410
+
2411
+ // src/cli/signals.ts
2412
+ import process7 from "process";
2413
+ async function withTerminationSignal(fn) {
2414
+ const abort = new AbortController();
2415
+ const onSignal = () => {
2416
+ abort.abort();
2417
+ };
2418
+ process7.once("SIGINT", onSignal);
2419
+ process7.once("SIGTERM", onSignal);
2420
+ try {
2421
+ return await fn(abort.signal);
2422
+ } finally {
2423
+ process7.off("SIGINT", onSignal);
2424
+ process7.off("SIGTERM", onSignal);
2425
+ }
2426
+ }
2427
+
2428
+ // src/cli/commands/log.ts
2288
2429
  async function handleLog(opts) {
2289
2430
  const target = resolveTarget(opts);
2290
2431
  const location = parseBreakpointSpec(opts.at);
@@ -2297,13 +2438,7 @@ async function handleLog(opts) {
2297
2438
  throw new CfInspectorError("INVALID_EXPRESSION", "--expr must not be empty");
2298
2439
  }
2299
2440
  const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2300
- const abort = new AbortController();
2301
- const onSig = () => {
2302
- abort.abort();
2303
- };
2304
- process7.once("SIGINT", onSig);
2305
- process7.once("SIGTERM", onSig);
2306
- try {
2441
+ await withTerminationSignal(async (signal) => {
2307
2442
  await withSession(target, async (session) => {
2308
2443
  await validateExpression(session, expression);
2309
2444
  if (condition !== void 0) {
@@ -2317,7 +2452,7 @@ async function handleLog(opts) {
2317
2452
  ...maxEvents === void 0 ? {} : { maxEvents },
2318
2453
  ...hitCount === void 0 ? {} : { hitCount },
2319
2454
  ...condition === void 0 ? {} : { condition },
2320
- signal: abort.signal,
2455
+ signal,
2321
2456
  onEvent: (event) => {
2322
2457
  writeLogEvent(event, opts.json);
2323
2458
  },
@@ -2327,18 +2462,15 @@ async function handleLog(opts) {
2327
2462
  });
2328
2463
  writeLogSummary(result.stoppedReason, result.emitted, opts.json);
2329
2464
  });
2330
- } finally {
2331
- process7.off("SIGINT", onSig);
2332
- process7.off("SIGTERM", onSig);
2333
- }
2465
+ });
2334
2466
  }
2335
2467
  function writeLogSummary(stoppedReason, emitted, json) {
2336
2468
  if (json) {
2337
- process7.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
2469
+ process8.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
2338
2470
  `);
2339
2471
  return;
2340
2472
  }
2341
- process7.stderr.write(
2473
+ process8.stderr.write(
2342
2474
  `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
2343
2475
  `
2344
2476
  );
@@ -2346,16 +2478,18 @@ function writeLogSummary(stoppedReason, emitted, json) {
2346
2478
 
2347
2479
  // src/cli/commands/snapshot.ts
2348
2480
  import { performance as performance4 } from "perf_hooks";
2349
- import process8 from "process";
2481
+ import process9 from "process";
2350
2482
  init_types();
2351
2483
  async function handleSnapshot(opts) {
2352
2484
  const prepared = prepareSnapshotCommand(opts);
2353
- const result = await runSnapshotCommand(prepared, opts);
2485
+ const reportProgress = opts.quiet === true ? void 0 : writeProgress;
2486
+ const result = await runSnapshotCommand(prepared, opts, reportProgress);
2354
2487
  if (opts.json) {
2355
2488
  writeJson(result);
2356
2489
  } else {
2357
2490
  writeHumanSnapshot(result);
2358
2491
  }
2492
+ reportProgress?.("Snapshot complete.");
2359
2493
  }
2360
2494
  function prepareSnapshotCommand(opts) {
2361
2495
  const target = resolveTarget(opts);
@@ -2383,24 +2517,34 @@ function prepareSnapshotCommand(opts) {
2383
2517
  stackCaptures: parseCaptureList(opts.stackCaptures)
2384
2518
  };
2385
2519
  }
2386
- async function runSnapshotCommand(command, opts) {
2520
+ async function runSnapshotCommand(command, opts, reportProgress) {
2387
2521
  return await withSession(command.target, async (session) => {
2388
2522
  if (command.condition !== void 0) {
2523
+ reportProgress?.("Validating the breakpoint condition...");
2389
2524
  await validateExpression(session, command.condition);
2525
+ reportProgress?.("Breakpoint condition is valid.");
2390
2526
  }
2391
- const handles = await Promise.all(
2392
- command.breakpoints.map(
2393
- (bp) => setBreakpoint(session, {
2394
- file: bp.file,
2395
- line: bp.line,
2396
- remoteRoot: command.remoteRoot,
2397
- ...command.condition === void 0 ? {} : { condition: command.condition },
2398
- ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
2399
- })
2400
- )
2527
+ const breakpointCount = command.breakpoints.length;
2528
+ reportProgress?.(
2529
+ `Setting ${breakpointCount.toString()} ${breakpointCount === 1 ? "breakpoint" : "breakpoints"}...`
2530
+ );
2531
+ const handles = await setCommandBreakpoints(session, command);
2532
+ const resolvedCount = handles.reduce(
2533
+ (total, handle) => total + handle.resolvedLocations.length,
2534
+ 0
2535
+ );
2536
+ reportProgress?.(
2537
+ `Breakpoint setup complete: ${resolvedCount.toString()} resolved ${resolvedCount === 1 ? "location" : "locations"}.`
2401
2538
  );
2402
2539
  warnOnUnboundBreakpoints(handles);
2540
+ reportProgress?.(
2541
+ `Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
2542
+ );
2403
2543
  const pause = await waitForCommandPause(session, opts, handles, command.timeoutMs);
2544
+ const captureCount = command.captures.length;
2545
+ reportProgress?.(
2546
+ `Breakpoint hit; capturing ${captureCount.toString()} ${captureCount === 1 ? "expression" : "expressions"}...`
2547
+ );
2404
2548
  const pausedStartedAt = pause.receivedAtMs ?? performance4.now();
2405
2549
  const snapshot = await captureSnapshot(session, pause, {
2406
2550
  captures: command.captures,
@@ -2410,10 +2554,25 @@ async function runSnapshotCommand(command, opts) {
2410
2554
  stackCaptures: command.stackCaptures
2411
2555
  });
2412
2556
  if (opts.keepPaused === true) {
2557
+ reportProgress?.("Snapshot captured; leaving the target paused as requested.");
2413
2558
  return withPausedDuration(snapshot, null);
2414
2559
  }
2415
- return await resumeAfterSnapshot(session, snapshot, pausedStartedAt);
2416
- });
2560
+ reportProgress?.("Snapshot captured; resuming the target...");
2561
+ return await resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress);
2562
+ }, reportProgress);
2563
+ }
2564
+ async function setCommandBreakpoints(session, command) {
2565
+ return await Promise.all(
2566
+ command.breakpoints.map(
2567
+ (bp) => setBreakpoint(session, {
2568
+ file: bp.file,
2569
+ line: bp.line,
2570
+ remoteRoot: command.remoteRoot,
2571
+ ...command.condition === void 0 ? {} : { condition: command.condition },
2572
+ ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
2573
+ })
2574
+ )
2575
+ );
2417
2576
  }
2418
2577
  async function waitForCommandPause(session, opts, handles, timeoutMs) {
2419
2578
  let warnedUnmatchedPause = false;
@@ -2430,12 +2589,13 @@ async function waitForCommandPause(session, opts, handles, timeoutMs) {
2430
2589
  }
2431
2590
  });
2432
2591
  }
2433
- async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
2592
+ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress) {
2434
2593
  try {
2435
2594
  await resume(session);
2595
+ reportProgress?.("Target resumed.");
2436
2596
  return withPausedDuration(snapshot, roundDurationMs(performance4.now() - pausedStartedAt));
2437
2597
  } catch {
2438
- process8.stderr.write(
2598
+ process9.stderr.write(
2439
2599
  "[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
2440
2600
  );
2441
2601
  return withPausedDuration(snapshot, null);
@@ -2443,28 +2603,20 @@ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
2443
2603
  }
2444
2604
 
2445
2605
  // src/cli/commands/watch.ts
2446
- import process9 from "process";
2606
+ import { performance as performance5 } from "perf_hooks";
2607
+ import process10 from "process";
2447
2608
  init_types();
2448
2609
  async function handleWatch(opts) {
2449
2610
  const prepared = prepareWatchCommand(opts);
2450
- const abort = new AbortController();
2451
- const onSig = () => {
2452
- abort.abort();
2453
- };
2454
- process9.once("SIGINT", onSig);
2455
- process9.once("SIGTERM", onSig);
2456
2611
  let stoppedReason = "signal";
2457
2612
  let emitted = 0;
2458
- try {
2613
+ await withTerminationSignal(async (signal) => {
2459
2614
  await withSession(prepared.target, async (session) => {
2460
- const result = await runWatchLoop(session, prepared, opts, abort.signal);
2615
+ const result = await runWatchLoop(session, prepared, opts, signal);
2461
2616
  stoppedReason = result.stoppedReason;
2462
2617
  emitted = result.emitted;
2463
2618
  });
2464
- } finally {
2465
- process9.off("SIGINT", onSig);
2466
- process9.off("SIGTERM", onSig);
2467
- }
2619
+ });
2468
2620
  writeWatchSummary(stoppedReason, emitted, opts.json);
2469
2621
  }
2470
2622
  function prepareWatchCommand(opts) {
@@ -2545,7 +2697,7 @@ async function runWatchLoop(session, command, opts, signal) {
2545
2697
  break;
2546
2698
  }
2547
2699
  if (pause === "timeout") {
2548
- if (deadline !== void 0 && Date.now() >= deadline) {
2700
+ if (deadline !== void 0 && performance5.now() >= deadline) {
2549
2701
  setStop("duration");
2550
2702
  break;
2551
2703
  }
@@ -2557,7 +2709,7 @@ async function runWatchLoop(session, command, opts, signal) {
2557
2709
  try {
2558
2710
  await resume(session);
2559
2711
  } catch {
2560
- process9.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
2712
+ process10.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
2561
2713
  setStop("transport-closed");
2562
2714
  break;
2563
2715
  }
@@ -2575,13 +2727,13 @@ function computeDeadline(durationMs) {
2575
2727
  if (durationMs === void 0) {
2576
2728
  return void 0;
2577
2729
  }
2578
- return Date.now() + durationMs;
2730
+ return performance5.now() + durationMs;
2579
2731
  }
2580
2732
  function remainingForLoop(deadline, perHitTimeoutMs) {
2581
2733
  if (deadline === void 0) {
2582
2734
  return perHitTimeoutMs;
2583
2735
  }
2584
- const remaining = deadline - Date.now();
2736
+ const remaining = deadline - performance5.now();
2585
2737
  if (remaining <= 0) {
2586
2738
  return 0;
2587
2739
  }
@@ -2662,11 +2814,11 @@ function formatLocation(command, topFrame) {
2662
2814
  }
2663
2815
  function writeWatchSummary(reason, emitted, json) {
2664
2816
  if (json) {
2665
- process9.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
2817
+ process10.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
2666
2818
  `);
2667
2819
  return;
2668
2820
  }
2669
- process9.stderr.write(
2821
+ process10.stderr.write(
2670
2822
  `Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
2671
2823
  `
2672
2824
  );
@@ -2695,7 +2847,7 @@ async function main(argv) {
2695
2847
  function registerSnapshot(program) {
2696
2848
  applyTargetOptions(
2697
2849
  program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
2698
- ).option("--bp <file:line>", "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--timeout <seconds>", "How long to wait for the breakpoint to hit (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--condition <expr>", "Only pause when this JS expression evaluates truthy in the paused frame").option("--hit-count <n>", "Only pause after the breakpoint has been hit N or more times").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1, only top frame)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--no-json", "Print a human-readable summary instead of JSON").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--fail-on-unmatched-pause", "Fail immediately if the target pauses somewhere else").action(async (opts) => {
2850
+ ).option("--bp <file:line>", "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--timeout <seconds>", "How long to wait for the breakpoint to hit (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--condition <expr>", "Only pause when this JS expression evaluates truthy in the paused frame").option("--hit-count <n>", "Only pause after the breakpoint has been hit N or more times").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1, only top frame)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--no-json", "Print a human-readable summary instead of JSON").option("--quiet", "Suppress progress messages on stderr").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--fail-on-unmatched-pause", "Fail immediately if the target pauses somewhere else").action(async (opts) => {
2699
2851
  await handleSnapshot(opts);
2700
2852
  });
2701
2853
  }
@@ -2745,20 +2897,20 @@ function registerAttach(program) {
2745
2897
  // src/cli.ts
2746
2898
  init_types();
2747
2899
  try {
2748
- await main(process10.argv);
2900
+ await main(process11.argv);
2749
2901
  } catch (err) {
2750
2902
  if (err instanceof CfInspectorError) {
2751
- process10.stderr.write(`Error [${err.code}]: ${err.message}
2903
+ process11.stderr.write(`Error [${err.code}]: ${err.message}
2752
2904
  `);
2753
2905
  if (err.detail !== void 0) {
2754
- process10.stderr.write(` detail: ${err.detail}
2906
+ process11.stderr.write(` detail: ${err.detail}
2755
2907
  `);
2756
2908
  }
2757
- process10.exit(1);
2909
+ process11.exit(1);
2758
2910
  }
2759
2911
  const message = err instanceof Error ? err.message : String(err);
2760
- process10.stderr.write(`Error: ${message}
2912
+ process11.stderr.write(`Error: ${message}
2761
2913
  `);
2762
- process10.exit(1);
2914
+ process11.exit(1);
2763
2915
  }
2764
2916
  //# sourceMappingURL=cli.js.map