@saptools/cf-inspector 0.4.0 → 0.4.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
@@ -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";
@@ -404,10 +446,19 @@ var CdpClient = class _CdpClient {
404
446
  return;
405
447
  }
406
448
  if (typeof parsed.method === "string") {
407
- this.emitter.emit(parsed.method, parsed.params);
408
- this.emitter.emit("event", { method: parsed.method, params: parsed.params });
449
+ this.safeEmit(parsed.method, parsed.params);
450
+ this.safeEmit("event", { method: parsed.method, params: parsed.params });
409
451
  }
410
452
  };
453
+ safeEmit(event, payload) {
454
+ const listeners = this.emitter.listeners(event);
455
+ for (const listener of listeners) {
456
+ try {
457
+ listener(payload);
458
+ } catch {
459
+ }
460
+ }
461
+ }
411
462
  handleClose = () => {
412
463
  this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
413
464
  };
@@ -425,7 +476,8 @@ var CdpClient = class _CdpClient {
425
476
  }
426
477
  static async connect(options) {
427
478
  const factory = options.transportFactory ?? await loadDefaultFactory();
428
- const transport = await factory(options.url);
479
+ const factoryOptions = options.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs };
480
+ const transport = await factory(options.url, factoryOptions);
429
481
  return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
430
482
  }
431
483
  async send(method, params = {}) {
@@ -475,8 +527,16 @@ var CdpClient = class _CdpClient {
475
527
  return;
476
528
  }
477
529
  const params = raw;
478
- if (options.predicate && !options.predicate(params)) {
479
- return;
530
+ if (options.predicate) {
531
+ let accepted;
532
+ try {
533
+ accepted = options.predicate(params);
534
+ } catch {
535
+ return;
536
+ }
537
+ if (!accepted) {
538
+ return;
539
+ }
480
540
  }
481
541
  finish(params);
482
542
  });
@@ -716,7 +776,18 @@ async function connectInspector(options) {
716
776
  `No inspector targets available on ${host}:${options.port.toString()}`
717
777
  );
718
778
  }
719
- const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl });
779
+ const client = await CdpClient.connect({
780
+ url: target.webSocketDebuggerUrl,
781
+ connectTimeoutMs
782
+ });
783
+ try {
784
+ return await initSession(client, target);
785
+ } catch (err) {
786
+ client.dispose();
787
+ throw err;
788
+ }
789
+ }
790
+ async function initSession(client, target) {
720
791
  const scripts = /* @__PURE__ */ new Map();
721
792
  client.on("Debugger.scriptParsed", (raw) => {
722
793
  const params = raw;
@@ -1066,9 +1137,11 @@ function normalizeRegexRootPattern(pattern) {
1066
1137
  const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
1067
1138
  return stripTrailingSlash(withoutEndAnchor);
1068
1139
  }
1069
- function buildFileUrlRegex(rootPattern, tail) {
1070
- const separator = rootPattern.endsWith("/") ? "" : "/";
1071
- return `^file://${rootPattern}${separator}${tail}$`;
1140
+ function buildFileUrlRegex(embeddedRoot, tail, separator) {
1141
+ return `^file://${embeddedRoot}${separator}${tail}$`;
1142
+ }
1143
+ function rootSeparator(rawPattern) {
1144
+ return rawPattern.endsWith("/") ? "" : "/";
1072
1145
  }
1073
1146
  function escapeRegExp(value) {
1074
1147
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -1095,12 +1168,12 @@ function buildBreakpointUrlRegex(input) {
1095
1168
  return `(?:^|/)${tail}$`;
1096
1169
  }
1097
1170
  case "literal": {
1098
- const escapedRoot = escapeRegExp(input.remoteRoot.value);
1099
- return buildFileUrlRegex(escapedRoot, tail);
1171
+ const root = input.remoteRoot.value;
1172
+ return buildFileUrlRegex(escapeRegExp(root), tail, rootSeparator(root));
1100
1173
  }
1101
1174
  case "regex": {
1102
- const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
1103
- return buildFileUrlRegex(rootPattern, tail);
1175
+ const root = normalizeRegexRootPattern(input.remoteRoot.pattern);
1176
+ return buildFileUrlRegex(`(?:${root})`, tail, rootSeparator(root));
1104
1177
  }
1105
1178
  }
1106
1179
  }
@@ -1364,19 +1437,32 @@ function scalarFromVariable(variable) {
1364
1437
  }
1365
1438
  return value === "null" ? null : value;
1366
1439
  }
1440
+ function isArrayLikeChildren(children) {
1441
+ let hasNumeric = false;
1442
+ for (const child of children) {
1443
+ if (child.name === "length") {
1444
+ continue;
1445
+ }
1446
+ if (parseNumericIndex(child.name) === void 0) {
1447
+ return false;
1448
+ }
1449
+ hasNumeric = true;
1450
+ }
1451
+ return hasNumeric;
1452
+ }
1367
1453
  function toStructuredValue(variable) {
1368
1454
  const children = variable.children;
1369
1455
  if (children === void 0 || children.length === 0) {
1370
1456
  return scalarFromVariable(variable);
1371
1457
  }
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) {
1458
+ if (isArrayLikeChildren(children)) {
1459
+ const indexed = children.flatMap((child) => {
1460
+ const index = parseNumericIndex(child.name);
1461
+ if (index === void 0) {
1462
+ return [];
1463
+ }
1464
+ return [[index, toStructuredValue(child)]];
1465
+ });
1380
1466
  const maxIndex = Math.max(...indexed.map(([index]) => index));
1381
1467
  const out2 = Array.from({ length: maxIndex + 1 }, () => null);
1382
1468
  for (const [index, entry] of indexed) {
@@ -2041,7 +2127,7 @@ async function handleListScripts(opts) {
2041
2127
  }
2042
2128
 
2043
2129
  // src/cli/commands/log.ts
2044
- import process7 from "process";
2130
+ import process8 from "process";
2045
2131
 
2046
2132
  // src/logpoint/stream.ts
2047
2133
  init_types();
@@ -2200,7 +2286,10 @@ async function streamLogpoint(session, options) {
2200
2286
  return;
2201
2287
  }
2202
2288
  emitted += 1;
2203
- options.onEvent(event);
2289
+ try {
2290
+ options.onEvent(event);
2291
+ } catch {
2292
+ }
2204
2293
  if (maxEvents !== void 0 && emitted >= maxEvents) {
2205
2294
  maxEventsReached = true;
2206
2295
  stopMaxEvents?.();
@@ -2285,6 +2374,25 @@ async function waitForStop(session, options, registerMaxEventsSignal) {
2285
2374
 
2286
2375
  // src/cli/commands/log.ts
2287
2376
  init_types();
2377
+
2378
+ // src/cli/signals.ts
2379
+ import process7 from "process";
2380
+ async function withTerminationSignal(fn) {
2381
+ const abort = new AbortController();
2382
+ const onSignal = () => {
2383
+ abort.abort();
2384
+ };
2385
+ process7.once("SIGINT", onSignal);
2386
+ process7.once("SIGTERM", onSignal);
2387
+ try {
2388
+ return await fn(abort.signal);
2389
+ } finally {
2390
+ process7.off("SIGINT", onSignal);
2391
+ process7.off("SIGTERM", onSignal);
2392
+ }
2393
+ }
2394
+
2395
+ // src/cli/commands/log.ts
2288
2396
  async function handleLog(opts) {
2289
2397
  const target = resolveTarget(opts);
2290
2398
  const location = parseBreakpointSpec(opts.at);
@@ -2297,13 +2405,7 @@ async function handleLog(opts) {
2297
2405
  throw new CfInspectorError("INVALID_EXPRESSION", "--expr must not be empty");
2298
2406
  }
2299
2407
  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 {
2408
+ await withTerminationSignal(async (signal) => {
2307
2409
  await withSession(target, async (session) => {
2308
2410
  await validateExpression(session, expression);
2309
2411
  if (condition !== void 0) {
@@ -2317,7 +2419,7 @@ async function handleLog(opts) {
2317
2419
  ...maxEvents === void 0 ? {} : { maxEvents },
2318
2420
  ...hitCount === void 0 ? {} : { hitCount },
2319
2421
  ...condition === void 0 ? {} : { condition },
2320
- signal: abort.signal,
2422
+ signal,
2321
2423
  onEvent: (event) => {
2322
2424
  writeLogEvent(event, opts.json);
2323
2425
  },
@@ -2327,18 +2429,15 @@ async function handleLog(opts) {
2327
2429
  });
2328
2430
  writeLogSummary(result.stoppedReason, result.emitted, opts.json);
2329
2431
  });
2330
- } finally {
2331
- process7.off("SIGINT", onSig);
2332
- process7.off("SIGTERM", onSig);
2333
- }
2432
+ });
2334
2433
  }
2335
2434
  function writeLogSummary(stoppedReason, emitted, json) {
2336
2435
  if (json) {
2337
- process7.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
2436
+ process8.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
2338
2437
  `);
2339
2438
  return;
2340
2439
  }
2341
- process7.stderr.write(
2440
+ process8.stderr.write(
2342
2441
  `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
2343
2442
  `
2344
2443
  );
@@ -2346,7 +2445,7 @@ function writeLogSummary(stoppedReason, emitted, json) {
2346
2445
 
2347
2446
  // src/cli/commands/snapshot.ts
2348
2447
  import { performance as performance4 } from "perf_hooks";
2349
- import process8 from "process";
2448
+ import process9 from "process";
2350
2449
  init_types();
2351
2450
  async function handleSnapshot(opts) {
2352
2451
  const prepared = prepareSnapshotCommand(opts);
@@ -2435,7 +2534,7 @@ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
2435
2534
  await resume(session);
2436
2535
  return withPausedDuration(snapshot, roundDurationMs(performance4.now() - pausedStartedAt));
2437
2536
  } catch {
2438
- process8.stderr.write(
2537
+ process9.stderr.write(
2439
2538
  "[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
2440
2539
  );
2441
2540
  return withPausedDuration(snapshot, null);
@@ -2443,28 +2542,20 @@ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
2443
2542
  }
2444
2543
 
2445
2544
  // src/cli/commands/watch.ts
2446
- import process9 from "process";
2545
+ import { performance as performance5 } from "perf_hooks";
2546
+ import process10 from "process";
2447
2547
  init_types();
2448
2548
  async function handleWatch(opts) {
2449
2549
  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
2550
  let stoppedReason = "signal";
2457
2551
  let emitted = 0;
2458
- try {
2552
+ await withTerminationSignal(async (signal) => {
2459
2553
  await withSession(prepared.target, async (session) => {
2460
- const result = await runWatchLoop(session, prepared, opts, abort.signal);
2554
+ const result = await runWatchLoop(session, prepared, opts, signal);
2461
2555
  stoppedReason = result.stoppedReason;
2462
2556
  emitted = result.emitted;
2463
2557
  });
2464
- } finally {
2465
- process9.off("SIGINT", onSig);
2466
- process9.off("SIGTERM", onSig);
2467
- }
2558
+ });
2468
2559
  writeWatchSummary(stoppedReason, emitted, opts.json);
2469
2560
  }
2470
2561
  function prepareWatchCommand(opts) {
@@ -2545,7 +2636,7 @@ async function runWatchLoop(session, command, opts, signal) {
2545
2636
  break;
2546
2637
  }
2547
2638
  if (pause === "timeout") {
2548
- if (deadline !== void 0 && Date.now() >= deadline) {
2639
+ if (deadline !== void 0 && performance5.now() >= deadline) {
2549
2640
  setStop("duration");
2550
2641
  break;
2551
2642
  }
@@ -2557,7 +2648,7 @@ async function runWatchLoop(session, command, opts, signal) {
2557
2648
  try {
2558
2649
  await resume(session);
2559
2650
  } catch {
2560
- process9.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
2651
+ process10.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
2561
2652
  setStop("transport-closed");
2562
2653
  break;
2563
2654
  }
@@ -2575,13 +2666,13 @@ function computeDeadline(durationMs) {
2575
2666
  if (durationMs === void 0) {
2576
2667
  return void 0;
2577
2668
  }
2578
- return Date.now() + durationMs;
2669
+ return performance5.now() + durationMs;
2579
2670
  }
2580
2671
  function remainingForLoop(deadline, perHitTimeoutMs) {
2581
2672
  if (deadline === void 0) {
2582
2673
  return perHitTimeoutMs;
2583
2674
  }
2584
- const remaining = deadline - Date.now();
2675
+ const remaining = deadline - performance5.now();
2585
2676
  if (remaining <= 0) {
2586
2677
  return 0;
2587
2678
  }
@@ -2662,11 +2753,11 @@ function formatLocation(command, topFrame) {
2662
2753
  }
2663
2754
  function writeWatchSummary(reason, emitted, json) {
2664
2755
  if (json) {
2665
- process9.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
2756
+ process10.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
2666
2757
  `);
2667
2758
  return;
2668
2759
  }
2669
- process9.stderr.write(
2760
+ process10.stderr.write(
2670
2761
  `Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
2671
2762
  `
2672
2763
  );
@@ -2745,20 +2836,20 @@ function registerAttach(program) {
2745
2836
  // src/cli.ts
2746
2837
  init_types();
2747
2838
  try {
2748
- await main(process10.argv);
2839
+ await main(process11.argv);
2749
2840
  } catch (err) {
2750
2841
  if (err instanceof CfInspectorError) {
2751
- process10.stderr.write(`Error [${err.code}]: ${err.message}
2842
+ process11.stderr.write(`Error [${err.code}]: ${err.message}
2752
2843
  `);
2753
2844
  if (err.detail !== void 0) {
2754
- process10.stderr.write(` detail: ${err.detail}
2845
+ process11.stderr.write(` detail: ${err.detail}
2755
2846
  `);
2756
2847
  }
2757
- process10.exit(1);
2848
+ process11.exit(1);
2758
2849
  }
2759
2850
  const message = err instanceof Error ? err.message : String(err);
2760
- process10.stderr.write(`Error: ${message}
2851
+ process11.stderr.write(`Error: ${message}
2761
2852
  `);
2762
- process10.exit(1);
2853
+ process11.exit(1);
2763
2854
  }
2764
2855
  //# sourceMappingURL=cli.js.map