@saptools/cf-inspector 0.3.19 → 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 process8 from "process";
155
+ import process11 from "process";
114
156
 
115
157
  // src/cli/program.ts
116
158
  import { Command } from "commander";
@@ -250,8 +292,11 @@ function writeHumanSnapshot(snapshot) {
250
292
  ` reason: ${snapshot.reason}`,
251
293
  ` paused: ${pausedDuration}`
252
294
  );
295
+ if (snapshot.exception !== void 0) {
296
+ appendExceptionLines(lines, snapshot.exception);
297
+ }
253
298
  if (snapshot.topFrame) {
254
- appendFrameLines(lines, snapshot);
299
+ appendFrameLines(lines, snapshot.topFrame);
255
300
  }
256
301
  if (snapshot.captures.length > 0) {
257
302
  lines.push(" captures:");
@@ -260,14 +305,16 @@ function writeHumanSnapshot(snapshot) {
260
305
  lines.push(` ${capture.expression} = ${detail}`);
261
306
  }
262
307
  }
308
+ if (snapshot.stack !== void 0 && snapshot.stack.length > 0) {
309
+ lines.push(" stack:");
310
+ for (const frame of snapshot.stack) {
311
+ appendStackFrameLine(lines, frame);
312
+ }
313
+ }
263
314
  process.stdout.write(`${lines.join("\n")}
264
315
  `);
265
316
  }
266
- function appendFrameLines(lines, snapshot) {
267
- const frame = snapshot.topFrame;
268
- if (frame === void 0) {
269
- return;
270
- }
317
+ function appendFrameLines(lines, frame) {
271
318
  const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
272
319
  const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
273
320
  lines.push(
@@ -283,6 +330,25 @@ function appendFrameLines(lines, snapshot) {
283
330
  }
284
331
  }
285
332
  }
333
+ function appendStackFrameLine(lines, frame) {
334
+ const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
335
+ const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
336
+ lines.push(` ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`);
337
+ if (frame.captures !== void 0) {
338
+ for (const capture of frame.captures) {
339
+ const detail = capture.error ?? capture.value ?? "undefined";
340
+ lines.push(` ${capture.expression} = ${detail}`);
341
+ }
342
+ }
343
+ }
344
+ function appendExceptionLines(lines, exception) {
345
+ if (exception.error !== void 0) {
346
+ lines.push(` exception: !err ${exception.error}`);
347
+ return;
348
+ }
349
+ const detail = exception.description ?? exception.value ?? "(unknown)";
350
+ lines.push(` exception: ${detail}`);
351
+ }
286
352
  function writeLogEvent(event, json) {
287
353
  if (json) {
288
354
  process.stdout.write(`${JSON.stringify(event)}
@@ -297,6 +363,25 @@ function writeLogEvent(event, json) {
297
363
  process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
298
364
  `);
299
365
  }
366
+ function writeWatchEvent(event, json) {
367
+ if (json) {
368
+ process.stdout.write(`${JSON.stringify(event)}
369
+ `);
370
+ return;
371
+ }
372
+ process.stdout.write(`[${event.ts}] hit#${event.hit.toString()} ${event.at}
373
+ `);
374
+ if (event.exception !== void 0) {
375
+ const detail = event.exception.description ?? event.exception.value ?? event.exception.error ?? "(unknown)";
376
+ process.stdout.write(` exception: ${detail}
377
+ `);
378
+ }
379
+ for (const capture of event.captures) {
380
+ const detail = capture.error ?? capture.value ?? "undefined";
381
+ process.stdout.write(` ${capture.expression} = ${detail}
382
+ `);
383
+ }
384
+ }
300
385
 
301
386
  // src/cf/tunnel.ts
302
387
  import { startDebugger } from "@saptools/cf-debugger";
@@ -361,10 +446,19 @@ var CdpClient = class _CdpClient {
361
446
  return;
362
447
  }
363
448
  if (typeof parsed.method === "string") {
364
- this.emitter.emit(parsed.method, parsed.params);
365
- 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 });
366
451
  }
367
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
+ }
368
462
  handleClose = () => {
369
463
  this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
370
464
  };
@@ -382,7 +476,8 @@ var CdpClient = class _CdpClient {
382
476
  }
383
477
  static async connect(options) {
384
478
  const factory = options.transportFactory ?? await loadDefaultFactory();
385
- const transport = await factory(options.url);
479
+ const factoryOptions = options.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs };
480
+ const transport = await factory(options.url, factoryOptions);
386
481
  return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
387
482
  }
388
483
  async send(method, params = {}) {
@@ -432,8 +527,16 @@ var CdpClient = class _CdpClient {
432
527
  return;
433
528
  }
434
529
  const params = raw;
435
- if (options.predicate && !options.predicate(params)) {
436
- 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
+ }
437
540
  }
438
541
  finish(params);
439
542
  });
@@ -634,12 +737,13 @@ function toCallFrames(value, scripts) {
634
737
  });
635
738
  }
636
739
  function toPauseEvent(params, receivedAtMs, scripts) {
637
- return {
740
+ const base = {
638
741
  reason: asString(params.reason),
639
742
  hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
640
743
  callFrames: toCallFrames(params.callFrames, scripts),
641
744
  receivedAtMs
642
745
  };
746
+ return params.data === void 0 ? base : { ...base, data: params.data };
643
747
  }
644
748
  function topFrameLocation(pause) {
645
749
  const top = pause.callFrames[0];
@@ -672,7 +776,18 @@ async function connectInspector(options) {
672
776
  `No inspector targets available on ${host}:${options.port.toString()}`
673
777
  );
674
778
  }
675
- 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) {
676
791
  const scripts = /* @__PURE__ */ new Map();
677
792
  client.on("Debugger.scriptParsed", (raw) => {
678
793
  const params = raw;
@@ -725,6 +840,7 @@ init_types();
725
840
  // src/cli/commandTypes.ts
726
841
  var DEFAULT_BREAKPOINT_TIMEOUT_SEC = 30;
727
842
  var DEFAULT_CF_TIMEOUT_SEC = 60;
843
+ var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
728
844
 
729
845
  // src/cli/target.ts
730
846
  function parsePositiveInt(raw, label) {
@@ -827,6 +943,9 @@ init_types();
827
943
  async function resume(session) {
828
944
  await session.client.send("Debugger.resume");
829
945
  }
946
+ async function setPauseOnExceptions(session, state) {
947
+ await session.client.send("Debugger.setPauseOnExceptions", { state });
948
+ }
830
949
  async function evaluateOnFrame(session, callFrameId, expression) {
831
950
  return await session.client.send("Debugger.evaluateOnCallFrame", {
832
951
  callFrameId,
@@ -914,23 +1033,9 @@ function writeHumanEvalResult(result) {
914
1033
  `);
915
1034
  }
916
1035
 
917
- // src/cli/commands/listScripts.ts
918
- import process4 from "process";
919
- async function handleListScripts(opts) {
920
- const target = resolveTarget(opts);
921
- const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
922
- if (opts.json) {
923
- writeJson(scripts);
924
- return;
925
- }
926
- for (const script of scripts) {
927
- process4.stdout.write(`${script.scriptId} ${script.url}
928
- `);
929
- }
930
- }
931
-
932
- // src/cli/commands/log.ts
933
- import process6 from "process";
1036
+ // src/cli/commands/exception.ts
1037
+ import { performance as performance3 } from "perf_hooks";
1038
+ import process5 from "process";
934
1039
 
935
1040
  // src/pathMapper.ts
936
1041
  init_types();
@@ -1032,9 +1137,11 @@ function normalizeRegexRootPattern(pattern) {
1032
1137
  const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
1033
1138
  return stripTrailingSlash(withoutEndAnchor);
1034
1139
  }
1035
- function buildFileUrlRegex(rootPattern, tail) {
1036
- const separator = rootPattern.endsWith("/") ? "" : "/";
1037
- 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("/") ? "" : "/";
1038
1145
  }
1039
1146
  function escapeRegExp(value) {
1040
1147
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -1061,18 +1168,58 @@ function buildBreakpointUrlRegex(input) {
1061
1168
  return `(?:^|/)${tail}$`;
1062
1169
  }
1063
1170
  case "literal": {
1064
- const escapedRoot = escapeRegExp(input.remoteRoot.value);
1065
- return buildFileUrlRegex(escapedRoot, tail);
1171
+ const root = input.remoteRoot.value;
1172
+ return buildFileUrlRegex(escapeRegExp(root), tail, rootSeparator(root));
1066
1173
  }
1067
1174
  case "regex": {
1068
- const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
1069
- return buildFileUrlRegex(rootPattern, tail);
1175
+ const root = normalizeRegexRootPattern(input.remoteRoot.pattern);
1176
+ return buildFileUrlRegex(`(?:${root})`, tail, rootSeparator(root));
1070
1177
  }
1071
1178
  }
1072
1179
  }
1073
1180
 
1074
1181
  // src/inspector/breakpoints.ts
1075
1182
  init_types();
1183
+ var HITS_GLOBAL = "globalThis.__CFI_HITS";
1184
+ var counterKeyCounter = 0;
1185
+ function nextCounterKey(file, line) {
1186
+ counterKeyCounter += 1;
1187
+ return `${file}:${line.toString()}:${counterKeyCounter.toString()}`;
1188
+ }
1189
+ function validateHitCount(hitCount) {
1190
+ if (hitCount === void 0) {
1191
+ return void 0;
1192
+ }
1193
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
1194
+ throw new CfInspectorError(
1195
+ "INVALID_HIT_COUNT",
1196
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
1197
+ );
1198
+ }
1199
+ return hitCount;
1200
+ }
1201
+ function buildHitCountedCondition(hitCount, counterKey, userCondition) {
1202
+ const keyLiteral = JSON.stringify(counterKey);
1203
+ const baseCondition = userCondition !== void 0 && userCondition.trim().length > 0 ? `(${userCondition})` : "true";
1204
+ return [
1205
+ "(function(){",
1206
+ `var m=(${HITS_GLOBAL}=${HITS_GLOBAL}||{});`,
1207
+ `var k=${keyLiteral};`,
1208
+ "m[k]=(m[k]||0)+1;",
1209
+ `if(m[k]<${hitCount.toString()})return false;`,
1210
+ `return ${baseCondition};`,
1211
+ "})()"
1212
+ ].join("");
1213
+ }
1214
+ function resolveCondition(input) {
1215
+ const condition = input.condition;
1216
+ const hitCount = validateHitCount(input.hitCount);
1217
+ if (hitCount === void 0) {
1218
+ return condition !== void 0 && condition.length > 0 ? condition : void 0;
1219
+ }
1220
+ const counterKey = nextCounterKey(input.file, input.line);
1221
+ return buildHitCountedCondition(hitCount, counterKey, condition);
1222
+ }
1076
1223
  async function setBreakpoint(session, input) {
1077
1224
  const remoteRoot = input.remoteRoot ?? { kind: "none" };
1078
1225
  const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
@@ -1080,8 +1227,9 @@ async function setBreakpoint(session, input) {
1080
1227
  lineNumber: input.line - 1,
1081
1228
  urlRegex
1082
1229
  };
1083
- if (input.condition !== void 0 && input.condition.length > 0) {
1084
- params["condition"] = input.condition;
1230
+ const condition = resolveCondition(input);
1231
+ if (condition !== void 0) {
1232
+ params["condition"] = condition;
1085
1233
  }
1086
1234
  const result = await session.client.send(
1087
1235
  "Debugger.setBreakpointByUrl",
@@ -1106,813 +1254,1199 @@ async function removeBreakpoint(session, breakpointId) {
1106
1254
  await session.client.send("Debugger.removeBreakpoint", { breakpointId });
1107
1255
  }
1108
1256
 
1109
- // src/logpoint/condition.ts
1110
- import { randomBytes } from "crypto";
1111
- var SENTINEL_PREFIX = "__CFI_LOG_";
1112
- var SENTINEL_SUFFIX = "__";
1113
- function buildLogpointCondition(sentinel, expression) {
1114
- return [
1115
- "(function(){",
1116
- `var s=${JSON.stringify(sentinel)};`,
1117
- "try{",
1118
- `var v=(${expression});`,
1119
- "var r=typeof v==='string'?v:JSON.stringify(v);",
1120
- "console.log(s, r);",
1121
- "}catch(e){",
1122
- "console.log(s, '!err:'+(e&&e.message?e.message:String(e)));",
1123
- "}",
1124
- "return false;",
1125
- "})()"
1126
- ].join("");
1257
+ // src/inspector/pause.ts
1258
+ init_types();
1259
+ import { performance as performance2 } from "perf_hooks";
1260
+ function pauseMatches(pause, breakpointIds, pauseReasons) {
1261
+ if (pauseReasons !== void 0 && pauseReasons.length > 0) {
1262
+ return pauseReasons.includes(pause.reason);
1263
+ }
1264
+ if (breakpointIds === void 0 || breakpointIds.length === 0) {
1265
+ return true;
1266
+ }
1267
+ return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
1127
1268
  }
1128
- function generateSentinel() {
1129
- return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1269
+ function remainingUntil(deadlineMs) {
1270
+ return Math.max(0, deadlineMs - performance2.now());
1130
1271
  }
1131
-
1132
- // src/logpoint/events.ts
1133
- function asString2(value) {
1134
- return typeof value === "string" ? value : void 0;
1272
+ function hasResumedSincePause(session, pause) {
1273
+ const pauseAt = pause.receivedAtMs;
1274
+ const resumedAt = session.debuggerState.lastResumedAtMs;
1275
+ return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
1135
1276
  }
1136
- function readArg(arg, index) {
1137
- if (typeof arg !== "object" || arg === null) {
1138
- return void 0;
1277
+ function throwBreakpointTimeout(timeoutMs) {
1278
+ throw new CfInspectorError(
1279
+ "BREAKPOINT_NOT_HIT",
1280
+ `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
1281
+ );
1282
+ }
1283
+ function throwUnrelatedPauseTimeout(pause, timeoutMs) {
1284
+ throw new CfInspectorError(
1285
+ "UNRELATED_PAUSE_TIMEOUT",
1286
+ `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
1287
+ pauseDetail(pause)
1288
+ );
1289
+ }
1290
+ async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
1291
+ if (hasResumedSincePause(session, pause)) {
1292
+ return;
1139
1293
  }
1140
- const candidate = arg;
1141
- if (candidate.type === "string" && typeof candidate.value === "string") {
1142
- return candidate.value;
1294
+ const remainingMs = remainingUntil(deadlineMs);
1295
+ if (remainingMs <= 0) {
1296
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
1143
1297
  }
1144
- const isPrimitiveType = candidate.type === "number" || candidate.type === "boolean" || candidate.type === "bigint";
1145
- const isPrimitiveValue = typeof candidate.value === "number" || typeof candidate.value === "boolean" || typeof candidate.value === "bigint";
1146
- if (isPrimitiveType && isPrimitiveValue) {
1147
- return String(candidate.value);
1298
+ try {
1299
+ await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
1300
+ session.debuggerState.lastResumedAtMs = performance2.now();
1301
+ } catch (err) {
1302
+ if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
1303
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
1304
+ }
1305
+ throw err;
1148
1306
  }
1149
- return index === 0 ? void 0 : "";
1150
1307
  }
1151
- function parseLogEvent(rawArgs, sentinel, location, timestamp) {
1152
- if (!Array.isArray(rawArgs) || rawArgs.length < 2) {
1153
- return void 0;
1308
+ async function handleUnmatchedPause(session, pause, options, deadlineMs) {
1309
+ if (options.unmatchedPausePolicy === "fail") {
1310
+ throw new CfInspectorError(
1311
+ "UNRELATED_PAUSE",
1312
+ "Target paused before this command's breakpoint was reached",
1313
+ pauseDetail(pause)
1314
+ );
1154
1315
  }
1155
- const tag = readArg(rawArgs[0], 0);
1156
- if (tag !== sentinel) {
1157
- return void 0;
1316
+ if (hasResumedSincePause(session, pause)) {
1317
+ return;
1158
1318
  }
1159
- const payload = readArg(rawArgs[1], 1) ?? "";
1160
- const ts = new Date(typeof timestamp === "number" ? timestamp : Date.now()).toISOString();
1161
- const at = `${location.file}:${location.line.toString()}`;
1162
- if (payload.startsWith("!err:")) {
1163
- return { ts, at, error: payload.slice("!err:".length) };
1319
+ options.onUnmatchedPause?.(pause);
1320
+ await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
1321
+ }
1322
+ async function waitForPause(session, options) {
1323
+ const deadlineMs = performance2.now() + options.timeoutMs;
1324
+ const buffer = session.pauseBuffer;
1325
+ while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
1326
+ while (buffer.length > 0) {
1327
+ const buffered = buffer.shift();
1328
+ if (buffered === void 0) {
1329
+ continue;
1330
+ }
1331
+ if (pauseMatches(buffered, options.breakpointIds, options.pauseReasons)) {
1332
+ return buffered;
1333
+ }
1334
+ await handleUnmatchedPause(session, buffered, options, deadlineMs);
1335
+ }
1336
+ const pause = await waitForLivePause(session, options, deadlineMs);
1337
+ if (pauseMatches(pause, options.breakpointIds, options.pauseReasons)) {
1338
+ return pause;
1339
+ }
1340
+ await handleUnmatchedPause(session, pause, options, deadlineMs);
1164
1341
  }
1165
- return parsePayload(ts, at, payload);
1342
+ throwBreakpointTimeout(options.timeoutMs);
1166
1343
  }
1167
- function parsePayload(ts, at, payload) {
1344
+ async function waitForLivePause(session, options, deadlineMs) {
1345
+ const remainingMs = remainingUntil(deadlineMs);
1346
+ if (remainingMs <= 0) {
1347
+ throwBreakpointTimeout(options.timeoutMs);
1348
+ }
1349
+ session.pauseWaitGate.active = true;
1350
+ let receivedAtMs;
1351
+ let params;
1168
1352
  try {
1169
- const parsed = JSON.parse(payload);
1170
- if (typeof parsed === "string") {
1171
- return { ts, at, value: parsed };
1172
- }
1173
- return { ts, at, value: JSON.stringify(parsed) };
1174
- } catch {
1175
- return { ts, at, value: payload, raw: payload };
1353
+ params = await session.client.waitFor("Debugger.paused", {
1354
+ timeoutMs: remainingMs,
1355
+ predicate: () => {
1356
+ receivedAtMs = performance2.now();
1357
+ return true;
1358
+ }
1359
+ });
1360
+ } finally {
1361
+ session.pauseWaitGate.active = false;
1176
1362
  }
1363
+ return toPauseEvent(params, receivedAtMs ?? performance2.now(), session.scripts);
1177
1364
  }
1178
1365
 
1179
- // src/logpoint/stream.ts
1180
- async function streamLogpoint(session, options) {
1181
- const sentinel = generateSentinel();
1182
- const condition = buildLogpointCondition(sentinel, options.expression);
1183
- let emitted = 0;
1184
- const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1185
- const event = toLogpointEvent(raw, sentinel, options.location);
1186
- if (event === void 0) {
1187
- return;
1188
- }
1189
- emitted += 1;
1190
- options.onEvent(event);
1191
- });
1192
- let handle;
1193
- try {
1194
- handle = await setBreakpoint(session, {
1195
- file: options.location.file,
1196
- line: options.location.line,
1197
- ...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
1198
- condition
1199
- });
1200
- } catch (err) {
1201
- offEvent();
1202
- throw err;
1366
+ // src/snapshot/values.ts
1367
+ init_types();
1368
+ var DEFAULT_MAX_VALUE_LENGTH = 4096;
1369
+ function isPrimitive(value) {
1370
+ const t = typeof value;
1371
+ return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
1372
+ }
1373
+ function formatPrimitive(value) {
1374
+ if (typeof value === "symbol") {
1375
+ return value.toString();
1203
1376
  }
1204
- options.onBreakpointSet?.(handle);
1205
- try {
1206
- const reason = await waitForStop(session, options);
1207
- return { handle, sentinel, emitted, stoppedReason: reason };
1208
- } finally {
1209
- offEvent();
1210
- await removeBreakpointBestEffort(session, handle.breakpointId);
1377
+ if (typeof value === "bigint") {
1378
+ return `${value.toString()}n`;
1211
1379
  }
1380
+ return String(value);
1212
1381
  }
1213
- function toLogpointEvent(raw, sentinel, location) {
1214
- const params = raw;
1215
- if (asString2(params.type) !== "log") {
1216
- return void 0;
1382
+ function resolveMaxValueLength(value) {
1383
+ if (value === void 0) {
1384
+ return DEFAULT_MAX_VALUE_LENGTH;
1217
1385
  }
1218
- const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
1219
- return parseLogEvent(params.args, sentinel, location, ts);
1386
+ if (!Number.isInteger(value) || value <= 0) {
1387
+ throw new CfInspectorError(
1388
+ "INVALID_ARGUMENT",
1389
+ `Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
1390
+ );
1391
+ }
1392
+ return value;
1220
1393
  }
1221
- async function removeBreakpointBestEffort(session, breakpointId) {
1394
+ function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1395
+ if (raw.length <= maxValueLength) {
1396
+ return raw;
1397
+ }
1398
+ return `${raw.slice(0, maxValueLength)}...`;
1399
+ }
1400
+ function parseQuotedString(value) {
1222
1401
  try {
1223
- await removeBreakpoint(session, breakpointId);
1402
+ const parsed = JSON.parse(value);
1403
+ return typeof parsed === "string" ? parsed : value;
1224
1404
  } catch {
1405
+ return value;
1225
1406
  }
1226
1407
  }
1227
- async function waitForStop(session, options) {
1228
- return await new Promise((resolve) => {
1229
- let settled = false;
1230
- const finish = (reason) => {
1231
- if (settled) {
1232
- return;
1233
- }
1234
- settled = true;
1235
- cleanup();
1236
- resolve(reason);
1237
- };
1238
- const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
1239
- finish("duration");
1240
- }, options.durationMs);
1241
- const offClose = session.client.onClose(() => {
1242
- finish("transport-closed");
1243
- });
1244
- const onAbort = () => {
1245
- finish("signal");
1246
- };
1247
- options.signal?.addEventListener("abort", onAbort, { once: true });
1248
- if (options.signal?.aborted === true) {
1249
- finish("signal");
1408
+ function parseNumericIndex(name) {
1409
+ const parsed = Number.parseInt(name, 10);
1410
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed.toString() !== name) {
1411
+ return void 0;
1412
+ }
1413
+ return parsed;
1414
+ }
1415
+ function scalarFromVariable(variable) {
1416
+ const value = variable.value;
1417
+ if (variable.type === "string") {
1418
+ return parseQuotedString(value);
1419
+ }
1420
+ if (variable.type === "number") {
1421
+ const parsed = Number(value);
1422
+ return Number.isFinite(parsed) ? parsed : value;
1423
+ }
1424
+ if (variable.type === "boolean") {
1425
+ if (value === "true") {
1426
+ return true;
1250
1427
  }
1251
- function cleanup() {
1252
- if (timer !== void 0) {
1253
- clearTimeout(timer);
1428
+ if (value === "false") {
1429
+ return false;
1430
+ }
1431
+ }
1432
+ if (variable.type === "undefined") {
1433
+ return "[undefined]";
1434
+ }
1435
+ if (variable.type === "bigint") {
1436
+ return value;
1437
+ }
1438
+ return value === "null" ? null : value;
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
+ }
1453
+ function toStructuredValue(variable) {
1454
+ const children = variable.children;
1455
+ if (children === void 0 || children.length === 0) {
1456
+ return scalarFromVariable(variable);
1457
+ }
1458
+ if (isArrayLikeChildren(children)) {
1459
+ const indexed = children.flatMap((child) => {
1460
+ const index = parseNumericIndex(child.name);
1461
+ if (index === void 0) {
1462
+ return [];
1254
1463
  }
1255
- offClose();
1256
- options.signal?.removeEventListener("abort", onAbort);
1464
+ return [[index, toStructuredValue(child)]];
1465
+ });
1466
+ const maxIndex = Math.max(...indexed.map(([index]) => index));
1467
+ const out2 = Array.from({ length: maxIndex + 1 }, () => null);
1468
+ for (const [index, entry] of indexed) {
1469
+ out2[index] = entry;
1257
1470
  }
1258
- });
1471
+ return out2;
1472
+ }
1473
+ const out = {};
1474
+ for (const child of children) {
1475
+ out[child.name] = toStructuredValue(child);
1476
+ }
1477
+ return out;
1259
1478
  }
1260
1479
 
1261
- // src/cli/commands/log.ts
1262
- init_types();
1480
+ // src/snapshot/evaluation.ts
1481
+ function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1482
+ if (result.exceptionDetails !== void 0) {
1483
+ return { expression, error: readEvalError(result, maxValueLength) };
1484
+ }
1485
+ const inner = result.result;
1486
+ if (!inner) {
1487
+ return { expression, error: "no result returned" };
1488
+ }
1489
+ const type = typeof inner.type === "string" ? inner.type : void 0;
1490
+ const buildCaptured = (rendered) => {
1491
+ const sanitized = limitValueLength(rendered, maxValueLength);
1492
+ const base = { expression, value: sanitized };
1493
+ return type === void 0 ? base : { ...base, type };
1494
+ };
1495
+ if (type === "string" && typeof inner.value === "string") {
1496
+ return buildCaptured(JSON.stringify(inner.value));
1497
+ }
1498
+ if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
1499
+ return buildCaptured(formatPrimitive(inner.value));
1500
+ }
1501
+ if (typeof inner.description === "string") {
1502
+ return buildCaptured(inner.description);
1503
+ }
1504
+ if (isPrimitive(inner.value)) {
1505
+ return buildCaptured(formatPrimitive(inner.value));
1506
+ }
1507
+ return buildCaptured("undefined");
1508
+ }
1509
+ function readEvalError(result, maxValueLength) {
1510
+ const text = typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1511
+ return limitValueLength(text, maxValueLength);
1512
+ }
1263
1513
 
1264
- // src/cli/warnings.ts
1265
- import process5 from "process";
1266
- function warnOnUnboundBreakpoints(handles) {
1267
- for (const handle of handles) {
1268
- if (handle.resolvedLocations.length === 0) {
1269
- process5.stderr.write(
1270
- `[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.
1271
- `
1272
- );
1273
- }
1514
+ // src/snapshot/properties.ts
1515
+ var MAX_SCOPE_VARIABLES = 20;
1516
+ var MAX_CHILD_VARIABLES = 8;
1517
+ var MAX_VARIABLE_DEPTH = 2;
1518
+ function buildDescribed(value, type, objectId) {
1519
+ const base = { value };
1520
+ if (type !== void 0) {
1521
+ base.type = type;
1522
+ }
1523
+ if (objectId !== void 0) {
1524
+ base.objectId = objectId;
1274
1525
  }
1526
+ return base;
1275
1527
  }
1276
- function roundDurationMs(durationMs) {
1277
- return Math.round(durationMs * 1e3) / 1e3;
1528
+ function describeProperty(prop) {
1529
+ const value = prop.value;
1530
+ if (value === void 0) {
1531
+ return { value: "undefined" };
1532
+ }
1533
+ const type = typeof value.type === "string" ? value.type : void 0;
1534
+ const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
1535
+ if (type === "undefined") {
1536
+ return buildDescribed("undefined", type);
1537
+ }
1538
+ if (type === "string" && typeof value.value === "string") {
1539
+ return buildDescribed(JSON.stringify(value.value), type);
1540
+ }
1541
+ if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
1542
+ return buildDescribed(formatPrimitive(value.value), type);
1543
+ }
1544
+ if (typeof value.description === "string") {
1545
+ return buildDescribed(value.description, type, objectId);
1546
+ }
1547
+ if (isPrimitive(value.value)) {
1548
+ return buildDescribed(formatPrimitive(value.value), type);
1549
+ }
1550
+ if (objectId === void 0) {
1551
+ return buildDescribed("undefined", type);
1552
+ }
1553
+ return buildDescribed("[object]", type, objectId);
1278
1554
  }
1279
- function warnOnUnmatchedPause(pause) {
1280
- const reason = pause.reason.length > 0 ? pause.reason : "unknown";
1281
- process5.stderr.write(
1282
- `[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
1283
- `
1555
+ function isExpandable(type) {
1556
+ return type === "object" || type === "function";
1557
+ }
1558
+ async function captureProperties(session, objectId, limit, depth, maxValueLength) {
1559
+ const properties = await getProperties(session, objectId);
1560
+ const limited = properties.slice(0, limit);
1561
+ const variables = await Promise.all(
1562
+ limited.map(async (prop) => {
1563
+ return await captureProperty(session, prop, depth, maxValueLength);
1564
+ })
1284
1565
  );
1566
+ return variables;
1285
1567
  }
1286
- function withPausedDuration(snapshot, pausedDurationMs) {
1287
- return {
1288
- reason: snapshot.reason,
1289
- hitBreakpoints: snapshot.hitBreakpoints,
1290
- capturedAt: snapshot.capturedAt,
1291
- pausedDurationMs,
1292
- ...snapshot.topFrame === void 0 ? {} : { topFrame: snapshot.topFrame },
1293
- captures: snapshot.captures
1294
- };
1568
+ async function captureProperty(session, prop, depth, maxValueLength) {
1569
+ const name = typeof prop.name === "string" ? prop.name : "?";
1570
+ const described = describeProperty(prop);
1571
+ const children = await capturePropertyChildren(session, described, depth, maxValueLength);
1572
+ const sanitizedValue = limitValueLength(described.value, maxValueLength);
1573
+ const base = { name, value: sanitizedValue };
1574
+ const withType = described.type === void 0 ? base : { ...base, type: described.type };
1575
+ return children === void 0 ? withType : { ...withType, children };
1295
1576
  }
1296
- function formatPauseLocation(pause) {
1297
- const top = pause.callFrames[0];
1298
- if (top === void 0) {
1299
- return "(no call frame)";
1577
+ async function capturePropertyChildren(session, described, depth, maxValueLength) {
1578
+ if (depth <= 0 || described.objectId === void 0 || !isExpandable(described.type)) {
1579
+ return void 0;
1580
+ }
1581
+ try {
1582
+ const nested = await captureProperties(
1583
+ session,
1584
+ described.objectId,
1585
+ MAX_CHILD_VARIABLES,
1586
+ depth - 1,
1587
+ maxValueLength
1588
+ );
1589
+ return nested.length > 0 ? nested : void 0;
1590
+ } catch {
1591
+ return void 0;
1300
1592
  }
1301
- const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
1302
- return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
1303
1593
  }
1304
1594
 
1305
- // src/cli/commands/log.ts
1306
- async function handleLog(opts) {
1307
- const target = resolveTarget(opts);
1308
- const location = parseBreakpointSpec(opts.at);
1309
- const remoteRoot = parseRemoteRoot(opts.remoteRoot);
1310
- const durationSec = parsePositiveInt(opts.duration, "--duration");
1311
- const expression = opts.expr.trim();
1312
- if (expression.length === 0) {
1313
- throw new CfInspectorError("INVALID_BREAKPOINT", "--expr must not be empty");
1595
+ // src/snapshot/exception.ts
1596
+ function asString2(value) {
1597
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1598
+ }
1599
+ async function materializeObject(session, objectId, maxValueLength) {
1600
+ try {
1601
+ const properties = await captureProperties(
1602
+ session,
1603
+ objectId,
1604
+ MAX_SCOPE_VARIABLES,
1605
+ MAX_VARIABLE_DEPTH,
1606
+ maxValueLength
1607
+ );
1608
+ if (properties.length === 0) {
1609
+ return void 0;
1610
+ }
1611
+ const structured = {};
1612
+ for (const variable of properties) {
1613
+ structured[variable.name] = toStructuredValue(variable);
1614
+ }
1615
+ return JSON.stringify(structured);
1616
+ } catch {
1617
+ return void 0;
1314
1618
  }
1315
- const abort = new AbortController();
1316
- const onSig = () => {
1317
- abort.abort();
1318
- };
1319
- process6.once("SIGINT", onSig);
1320
- process6.once("SIGTERM", onSig);
1619
+ }
1620
+ async function readPropertyDescription(session, objectId, name) {
1321
1621
  try {
1322
- await withSession(target, async (session) => {
1323
- await validateExpression(session, expression);
1324
- const result = await streamLogpoint(session, {
1325
- location,
1326
- expression,
1327
- remoteRoot,
1328
- ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
1329
- signal: abort.signal,
1330
- onEvent: (event) => {
1331
- writeLogEvent(event, opts.json);
1332
- },
1333
- onBreakpointSet: (handle) => {
1334
- warnOnUnboundBreakpoints([handle]);
1335
- }
1336
- });
1337
- writeLogSummary(result.stoppedReason, result.emitted, opts.json);
1338
- });
1339
- } finally {
1340
- process6.off("SIGINT", onSig);
1341
- process6.off("SIGTERM", onSig);
1622
+ const properties = await getProperties(session, objectId);
1623
+ for (const prop of properties) {
1624
+ if (prop.name !== name) {
1625
+ continue;
1626
+ }
1627
+ const value = prop.value;
1628
+ if (value === void 0) {
1629
+ continue;
1630
+ }
1631
+ if (typeof value.value === "string") {
1632
+ return value.value;
1633
+ }
1634
+ if (typeof value.description === "string") {
1635
+ return value.description;
1636
+ }
1637
+ }
1638
+ return void 0;
1639
+ } catch {
1640
+ return void 0;
1641
+ }
1642
+ }
1643
+ async function captureException(session, pause, maxValueLength) {
1644
+ if (pause.reason !== "exception" && pause.reason !== "promiseRejection") {
1645
+ return void 0;
1646
+ }
1647
+ const data = pause.data;
1648
+ if (typeof data !== "object" || data === null) {
1649
+ return { error: "no exception data attached" };
1650
+ }
1651
+ const candidate = data;
1652
+ const type = asString2(candidate.type);
1653
+ const description = asString2(candidate.description);
1654
+ if (typeof candidate.value === "string") {
1655
+ return buildResult(type, description, JSON.stringify(candidate.value), maxValueLength);
1656
+ }
1657
+ if (typeof candidate.value === "number" || typeof candidate.value === "boolean") {
1658
+ return buildResult(type, description, String(candidate.value), maxValueLength);
1659
+ }
1660
+ const objectId = asString2(candidate.objectId);
1661
+ if (objectId === void 0) {
1662
+ if (description !== void 0) {
1663
+ return buildResult(type, description, description, maxValueLength);
1664
+ }
1665
+ return { error: "exception data has no objectId or value" };
1666
+ }
1667
+ const message = await readPropertyDescription(session, objectId, "message");
1668
+ const rendered = await materializeObject(session, objectId, maxValueLength);
1669
+ if (rendered !== void 0) {
1670
+ const result = buildResult(type, description, rendered, maxValueLength);
1671
+ return message === void 0 ? result : { ...result, description: limitValueLength(message, maxValueLength) };
1672
+ }
1673
+ return buildResult(type, description, description ?? "[exception]", maxValueLength);
1674
+ }
1675
+ function buildResult(type, description, value, maxValueLength) {
1676
+ const safeValue = limitValueLength(value, maxValueLength);
1677
+ const base = { value: safeValue };
1678
+ const withType = type === void 0 ? base : { ...base, type };
1679
+ return description === void 0 ? withType : { ...withType, description: limitValueLength(description, maxValueLength) };
1680
+ }
1681
+
1682
+ // src/snapshot/objects.ts
1683
+ function objectIdFromEvalResult(result) {
1684
+ const inner = result.result;
1685
+ if (inner?.type !== "object") {
1686
+ return void 0;
1687
+ }
1688
+ const objectId = inner.objectId;
1689
+ if (typeof objectId !== "string" || objectId.length === 0) {
1690
+ return void 0;
1691
+ }
1692
+ return objectId;
1693
+ }
1694
+ async function renderObjectCapture(session, objectId, maxValueLength) {
1695
+ try {
1696
+ const properties = await captureProperties(
1697
+ session,
1698
+ objectId,
1699
+ MAX_SCOPE_VARIABLES,
1700
+ MAX_VARIABLE_DEPTH,
1701
+ maxValueLength
1702
+ );
1703
+ const structured = {};
1704
+ for (const variable of properties) {
1705
+ structured[variable.name] = toStructuredValue(variable);
1706
+ }
1707
+ return JSON.stringify(structured);
1708
+ } catch {
1709
+ return void 0;
1342
1710
  }
1343
1711
  }
1344
- function writeLogSummary(stoppedReason, emitted, json) {
1345
- if (json) {
1346
- process6.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
1347
- `);
1348
- return;
1712
+ function normalizeRenderedObjectCapture(rendered, original) {
1713
+ if (rendered === "{}" && original !== "Object") {
1714
+ return void 0;
1349
1715
  }
1350
- process6.stderr.write(
1351
- `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
1352
- `
1353
- );
1716
+ if (original.startsWith("Array(") && rendered === '{"length":0}') {
1717
+ return "[]";
1718
+ }
1719
+ return rendered;
1354
1720
  }
1355
-
1356
- // src/cli/commands/snapshot.ts
1357
- import { performance as performance3 } from "perf_hooks";
1358
- import process7 from "process";
1359
-
1360
- // src/inspector/pause.ts
1361
- init_types();
1362
- import { performance as performance2 } from "perf_hooks";
1363
- function pauseMatches(pause, breakpointIds) {
1364
- if (breakpointIds === void 0 || breakpointIds.length === 0) {
1365
- return true;
1721
+ async function withSerializedObjectCapture(session, expression, evalResult, captured, maxValueLength) {
1722
+ if (captured.error !== void 0 || captured.value === void 0) {
1723
+ return captured;
1366
1724
  }
1367
- return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
1725
+ const objectId = objectIdFromEvalResult(evalResult);
1726
+ if (objectId === void 0) {
1727
+ return captured;
1728
+ }
1729
+ const rendered = await renderObjectCapture(session, objectId, maxValueLength);
1730
+ if (rendered === void 0) {
1731
+ return captured;
1732
+ }
1733
+ const normalized = normalizeRenderedObjectCapture(rendered, captured.value);
1734
+ if (normalized === void 0) {
1735
+ return captured;
1736
+ }
1737
+ const value = limitValueLength(normalized, maxValueLength);
1738
+ return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
1368
1739
  }
1369
- function remainingUntil(deadlineMs) {
1370
- return Math.max(0, deadlineMs - performance2.now());
1740
+
1741
+ // src/snapshot/scopes.ts
1742
+ var MAX_SCOPES = 3;
1743
+ var PRIORITY_BY_TYPE = {
1744
+ local: 0,
1745
+ arguments: 1,
1746
+ block: 2,
1747
+ closure: 3,
1748
+ catch: 4,
1749
+ with: 5,
1750
+ module: 6,
1751
+ script: 7
1752
+ };
1753
+ function selectScopes(scopeChain) {
1754
+ const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
1755
+ return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
1371
1756
  }
1372
- function hasResumedSincePause(session, pause) {
1373
- const pauseAt = pause.receivedAtMs;
1374
- const resumedAt = session.debuggerState.lastResumedAtMs;
1375
- return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
1757
+ function priorityOf(type) {
1758
+ return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
1376
1759
  }
1377
- function throwBreakpointTimeout(timeoutMs) {
1378
- throw new CfInspectorError(
1379
- "BREAKPOINT_NOT_HIT",
1380
- `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
1760
+ async function captureScopes(session, frame, maxValueLength) {
1761
+ const scopes = selectScopes(frame.scopeChain);
1762
+ return await Promise.all(
1763
+ scopes.map(async (scope) => {
1764
+ const objectId = scope.objectId;
1765
+ if (objectId === void 0) {
1766
+ return { type: scope.type, variables: [] };
1767
+ }
1768
+ try {
1769
+ const variables = await captureProperties(
1770
+ session,
1771
+ objectId,
1772
+ MAX_SCOPE_VARIABLES,
1773
+ MAX_VARIABLE_DEPTH,
1774
+ maxValueLength
1775
+ );
1776
+ return { type: scope.type, variables };
1777
+ } catch {
1778
+ return { type: scope.type, variables: [] };
1779
+ }
1780
+ })
1381
1781
  );
1382
1782
  }
1383
- function throwUnrelatedPauseTimeout(pause, timeoutMs) {
1384
- throw new CfInspectorError(
1385
- "UNRELATED_PAUSE_TIMEOUT",
1386
- `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
1387
- pauseDetail(pause)
1388
- );
1783
+
1784
+ // src/snapshot/stack.ts
1785
+ var DEFAULT_STACK_DEPTH = 1;
1786
+ var MAX_STACK_DEPTH = 64;
1787
+ function clampDepth(depth, frameCount) {
1788
+ if (depth <= 0) {
1789
+ return 0;
1790
+ }
1791
+ return Math.min(depth, frameCount, MAX_STACK_DEPTH);
1792
+ }
1793
+ function buildBaseFrame(frame) {
1794
+ const base = {
1795
+ functionName: frame.functionName,
1796
+ line: frame.lineNumber + 1,
1797
+ column: frame.columnNumber + 1
1798
+ };
1799
+ return frame.url === void 0 ? base : { ...base, url: frame.url };
1389
1800
  }
1390
- async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
1391
- if (hasResumedSincePause(session, pause)) {
1392
- return;
1393
- }
1394
- const remainingMs = remainingUntil(deadlineMs);
1395
- if (remainingMs <= 0) {
1396
- throwUnrelatedPauseTimeout(pause, timeoutMs);
1397
- }
1801
+ async function captureFrameExpression(session, callFrameId, expression, maxValueLength) {
1398
1802
  try {
1399
- await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
1400
- session.debuggerState.lastResumedAtMs = performance2.now();
1803
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1804
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1805
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1401
1806
  } catch (err) {
1402
- if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
1403
- throwUnrelatedPauseTimeout(pause, timeoutMs);
1404
- }
1405
- throw err;
1406
- }
1407
- }
1408
- async function handleUnmatchedPause(session, pause, options, deadlineMs) {
1409
- if (options.unmatchedPausePolicy === "fail") {
1410
- throw new CfInspectorError(
1411
- "UNRELATED_PAUSE",
1412
- "Target paused before this command's breakpoint was reached",
1413
- pauseDetail(pause)
1414
- );
1415
- }
1416
- if (hasResumedSincePause(session, pause)) {
1417
- return;
1807
+ const message = err instanceof Error ? err.message : String(err);
1808
+ return { expression, error: limitValueLength(message, maxValueLength) };
1418
1809
  }
1419
- options.onUnmatchedPause?.(pause);
1420
- await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
1421
1810
  }
1422
- async function waitForPause(session, options) {
1423
- const deadlineMs = performance2.now() + options.timeoutMs;
1424
- const buffer = session.pauseBuffer;
1425
- while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
1426
- while (buffer.length > 0) {
1427
- const buffered = buffer.shift();
1428
- if (buffered === void 0) {
1429
- continue;
1430
- }
1431
- if (pauseMatches(buffered, options.breakpointIds)) {
1432
- return buffered;
1433
- }
1434
- await handleUnmatchedPause(session, buffered, options, deadlineMs);
1435
- }
1436
- const pause = await waitForLivePause(session, options, deadlineMs);
1437
- if (pauseMatches(pause, options.breakpointIds)) {
1438
- return pause;
1439
- }
1440
- await handleUnmatchedPause(session, pause, options, deadlineMs);
1811
+ async function captureFrameExpressions(session, frame, expressions, maxValueLength) {
1812
+ if (expressions.length === 0) {
1813
+ return [];
1441
1814
  }
1442
- throwBreakpointTimeout(options.timeoutMs);
1815
+ return await Promise.all(
1816
+ expressions.map(
1817
+ (expression) => captureFrameExpression(session, frame.callFrameId, expression, maxValueLength)
1818
+ )
1819
+ );
1443
1820
  }
1444
- async function waitForLivePause(session, options, deadlineMs) {
1445
- const remainingMs = remainingUntil(deadlineMs);
1446
- if (remainingMs <= 0) {
1447
- throwBreakpointTimeout(options.timeoutMs);
1821
+ async function walkStack(session, callFrames, options) {
1822
+ const depth = clampDepth(options.stackDepth, callFrames.length);
1823
+ if (depth <= 1) {
1824
+ return [];
1448
1825
  }
1449
- session.pauseWaitGate.active = true;
1450
- let receivedAtMs;
1451
- let params;
1452
- try {
1453
- params = await session.client.waitFor("Debugger.paused", {
1454
- timeoutMs: remainingMs,
1455
- predicate: () => {
1456
- receivedAtMs = performance2.now();
1457
- return true;
1826
+ const slice = callFrames.slice(0, depth);
1827
+ return await Promise.all(
1828
+ slice.map(async (frame) => {
1829
+ const base = buildBaseFrame(frame);
1830
+ if (options.stackCaptures.length === 0) {
1831
+ return base;
1458
1832
  }
1459
- });
1460
- } finally {
1461
- session.pauseWaitGate.active = false;
1462
- }
1463
- return toPauseEvent(params, receivedAtMs ?? performance2.now(), session.scripts);
1833
+ const captures = await captureFrameExpressions(
1834
+ session,
1835
+ frame,
1836
+ options.stackCaptures,
1837
+ options.maxValueLength
1838
+ );
1839
+ return { ...base, captures };
1840
+ })
1841
+ );
1464
1842
  }
1465
1843
 
1466
- // src/snapshot/values.ts
1467
- init_types();
1468
- var DEFAULT_MAX_VALUE_LENGTH = 4096;
1469
- function isPrimitive(value) {
1470
- const t = typeof value;
1471
- return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
1472
- }
1473
- function formatPrimitive(value) {
1474
- if (typeof value === "symbol") {
1475
- return value.toString();
1476
- }
1477
- if (typeof value === "bigint") {
1478
- return `${value.toString()}n`;
1844
+ // src/snapshot/capture.ts
1845
+ async function captureSnapshot(session, pause, options = {}) {
1846
+ const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1847
+ const top = pause.callFrames[0];
1848
+ let topFrame;
1849
+ let captures = [];
1850
+ let stack = [];
1851
+ if (top) {
1852
+ topFrame = {
1853
+ functionName: top.functionName,
1854
+ ...top.url === void 0 ? {} : { url: top.url },
1855
+ line: top.lineNumber + 1,
1856
+ column: top.columnNumber + 1
1857
+ };
1858
+ if (options.includeScopes === true) {
1859
+ const scopes = await captureScopes(session, top, maxValueLength);
1860
+ topFrame = { ...topFrame, scopes };
1861
+ }
1862
+ captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
1863
+ stack = await walkStack(session, pause.callFrames, {
1864
+ stackDepth: options.stackDepth ?? DEFAULT_STACK_DEPTH,
1865
+ stackCaptures: options.stackCaptures ?? [],
1866
+ maxValueLength
1867
+ });
1479
1868
  }
1480
- return String(value);
1869
+ const exception = await captureException(session, pause, maxValueLength);
1870
+ return buildResult2({
1871
+ pause,
1872
+ topFrame,
1873
+ captures,
1874
+ stack,
1875
+ exception
1876
+ });
1481
1877
  }
1482
- function resolveMaxValueLength(value) {
1483
- if (value === void 0) {
1484
- return DEFAULT_MAX_VALUE_LENGTH;
1485
- }
1486
- if (!Number.isInteger(value) || value <= 0) {
1487
- throw new CfInspectorError(
1488
- "INVALID_ARGUMENT",
1489
- `Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
1490
- );
1878
+ function buildResult2(input) {
1879
+ const base = {
1880
+ reason: input.pause.reason,
1881
+ hitBreakpoints: input.pause.hitBreakpoints,
1882
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1883
+ captures: input.captures
1884
+ };
1885
+ const withFrame = input.topFrame === void 0 ? base : { ...base, topFrame: input.topFrame };
1886
+ const withStack = input.stack.length > 0 ? { ...withFrame, stack: input.stack } : withFrame;
1887
+ return input.exception === void 0 ? withStack : { ...withStack, exception: input.exception };
1888
+ }
1889
+ async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1890
+ if (captures === void 0 || captures.length === 0) {
1891
+ return [];
1491
1892
  }
1492
- return value;
1893
+ return await Promise.all(
1894
+ captures.map(async (expression) => {
1895
+ return await captureExpression(session, callFrameId, expression, maxValueLength);
1896
+ })
1897
+ );
1493
1898
  }
1494
- function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1495
- if (raw.length <= maxValueLength) {
1496
- return raw;
1899
+ async function captureExpression(session, callFrameId, expression, maxValueLength) {
1900
+ try {
1901
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1902
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1903
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1904
+ } catch (err) {
1905
+ const message = err instanceof Error ? err.message : String(err);
1906
+ return { expression, error: limitValueLength(message, maxValueLength) };
1497
1907
  }
1498
- return `${raw.slice(0, maxValueLength)}...`;
1499
1908
  }
1500
- function parseQuotedString(value) {
1501
- try {
1502
- const parsed = JSON.parse(value);
1503
- return typeof parsed === "string" ? parsed : value;
1504
- } catch {
1505
- return value;
1909
+
1910
+ // src/cli/commands/exception.ts
1911
+ init_types();
1912
+
1913
+ // src/cli/captureParser.ts
1914
+ function parseCaptureList(raw) {
1915
+ if (raw === void 0 || raw.trim().length === 0) {
1916
+ return [];
1506
1917
  }
1918
+ return splitCaptureExpressions(raw);
1507
1919
  }
1508
- function parseNumericIndex(name) {
1509
- const parsed = Number.parseInt(name, 10);
1510
- if (!Number.isInteger(parsed) || parsed < 0 || parsed.toString() !== name) {
1511
- return void 0;
1512
- }
1513
- return parsed;
1920
+ function isQuoteChar(value) {
1921
+ return value === "'" || value === '"' || value === "`";
1514
1922
  }
1515
- function scalarFromVariable(variable) {
1516
- const value = variable.value;
1517
- if (variable.type === "string") {
1518
- return parseQuotedString(value);
1923
+ function consumeQuotedChar(state, char) {
1924
+ if (state.quote === void 0) {
1925
+ return false;
1519
1926
  }
1520
- if (variable.type === "number") {
1521
- const parsed = Number(value);
1522
- return Number.isFinite(parsed) ? parsed : value;
1927
+ if (state.escaped) {
1928
+ state.escaped = false;
1929
+ return true;
1523
1930
  }
1524
- if (variable.type === "boolean") {
1525
- if (value === "true") {
1526
- return true;
1527
- }
1528
- if (value === "false") {
1529
- return false;
1530
- }
1931
+ if (char === "\\") {
1932
+ state.escaped = true;
1933
+ return true;
1531
1934
  }
1532
- if (variable.type === "undefined") {
1533
- return "[undefined]";
1935
+ if (char === state.quote) {
1936
+ state.quote = void 0;
1534
1937
  }
1535
- if (variable.type === "bigint") {
1536
- return value;
1938
+ return true;
1939
+ }
1940
+ function updateCaptureDepth(state, char) {
1941
+ if (char === "(") {
1942
+ state.parenDepth += 1;
1943
+ } else if (char === ")") {
1944
+ state.parenDepth = Math.max(0, state.parenDepth - 1);
1945
+ } else if (char === "[") {
1946
+ state.bracketDepth += 1;
1947
+ } else if (char === "]") {
1948
+ state.bracketDepth = Math.max(0, state.bracketDepth - 1);
1949
+ } else if (char === "{") {
1950
+ state.braceDepth += 1;
1951
+ } else if (char === "}") {
1952
+ state.braceDepth = Math.max(0, state.braceDepth - 1);
1537
1953
  }
1538
- return value === "null" ? null : value;
1539
1954
  }
1540
- function toStructuredValue(variable) {
1541
- const children = variable.children;
1542
- if (children === void 0 || children.length === 0) {
1543
- return scalarFromVariable(variable);
1955
+ function isTopLevel(state) {
1956
+ return state.parenDepth === 0 && state.bracketDepth === 0 && state.braceDepth === 0;
1957
+ }
1958
+ function appendCapturePiece(raw, state, end) {
1959
+ const piece = raw.slice(state.start, end).trim();
1960
+ if (piece.length > 0) {
1961
+ state.pieces.push(piece);
1544
1962
  }
1545
- const indexed = children.flatMap((child) => {
1546
- const index = parseNumericIndex(child.name);
1547
- if (index === void 0) {
1548
- return [];
1963
+ }
1964
+ function splitCaptureExpressions(raw) {
1965
+ const state = {
1966
+ escaped: false,
1967
+ parenDepth: 0,
1968
+ bracketDepth: 0,
1969
+ braceDepth: 0,
1970
+ quote: void 0,
1971
+ start: 0,
1972
+ pieces: []
1973
+ };
1974
+ for (let idx = 0; idx < raw.length; idx += 1) {
1975
+ const char = raw.charAt(idx);
1976
+ if (consumeQuotedChar(state, char)) {
1977
+ continue;
1549
1978
  }
1550
- return [[index, toStructuredValue(child)]];
1551
- });
1552
- if (indexed.length > 0) {
1553
- const maxIndex = Math.max(...indexed.map(([index]) => index));
1554
- const out2 = Array.from({ length: maxIndex + 1 }, () => null);
1555
- for (const [index, entry] of indexed) {
1556
- out2[index] = entry;
1979
+ if (isQuoteChar(char)) {
1980
+ state.quote = char;
1981
+ continue;
1982
+ }
1983
+ updateCaptureDepth(state, char);
1984
+ if (char === "," && isTopLevel(state)) {
1985
+ appendCapturePiece(raw, state, idx);
1986
+ state.start = idx + 1;
1557
1987
  }
1558
- return out2;
1559
- }
1560
- const out = {};
1561
- for (const child of children) {
1562
- out[child.name] = toStructuredValue(child);
1563
1988
  }
1564
- return out;
1989
+ appendCapturePiece(raw, state, raw.length);
1990
+ return state.pieces;
1565
1991
  }
1566
1992
 
1567
- // src/snapshot/evaluation.ts
1568
- function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1569
- if (result.exceptionDetails !== void 0) {
1570
- return { expression, error: readEvalError(result, maxValueLength) };
1571
- }
1572
- const inner = result.result;
1573
- if (!inner) {
1574
- return { expression, error: "no result returned" };
1993
+ // src/cli/warnings.ts
1994
+ import process4 from "process";
1995
+ function warnOnUnboundBreakpoints(handles) {
1996
+ for (const handle of handles) {
1997
+ if (handle.resolvedLocations.length === 0) {
1998
+ process4.stderr.write(
1999
+ `[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.
2000
+ `
2001
+ );
2002
+ }
1575
2003
  }
1576
- const type = typeof inner.type === "string" ? inner.type : void 0;
1577
- const buildCaptured = (rendered) => {
1578
- const sanitized = limitValueLength(rendered, maxValueLength);
1579
- const base = { expression, value: sanitized };
1580
- return type === void 0 ? base : { ...base, type };
2004
+ }
2005
+ function roundDurationMs(durationMs) {
2006
+ return Math.round(durationMs * 1e3) / 1e3;
2007
+ }
2008
+ function warnOnUnmatchedPause(pause) {
2009
+ const reason = pause.reason.length > 0 ? pause.reason : "unknown";
2010
+ process4.stderr.write(
2011
+ `[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
2012
+ `
2013
+ );
2014
+ }
2015
+ function withPausedDuration(snapshot, pausedDurationMs) {
2016
+ const base = {
2017
+ reason: snapshot.reason,
2018
+ hitBreakpoints: snapshot.hitBreakpoints,
2019
+ capturedAt: snapshot.capturedAt,
2020
+ pausedDurationMs,
2021
+ captures: snapshot.captures
1581
2022
  };
1582
- if (type === "string" && typeof inner.value === "string") {
1583
- return buildCaptured(JSON.stringify(inner.value));
2023
+ const withFrame = snapshot.topFrame === void 0 ? base : { ...base, topFrame: snapshot.topFrame };
2024
+ const withStack = snapshot.stack === void 0 ? withFrame : { ...withFrame, stack: snapshot.stack };
2025
+ return snapshot.exception === void 0 ? withStack : { ...withStack, exception: snapshot.exception };
2026
+ }
2027
+ function formatPauseLocation(pause) {
2028
+ const top = pause.callFrames[0];
2029
+ if (top === void 0) {
2030
+ return "(no call frame)";
1584
2031
  }
1585
- if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
1586
- return buildCaptured(formatPrimitive(inner.value));
2032
+ const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
2033
+ return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
2034
+ }
2035
+
2036
+ // src/cli/commands/exception.ts
2037
+ var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
2038
+ async function handleException(opts) {
2039
+ const prepared = prepareExceptionCommand(opts);
2040
+ const result = await runExceptionCommand(prepared, opts);
2041
+ if (opts.json) {
2042
+ writeJson(result);
2043
+ } else {
2044
+ writeHumanSnapshot(result);
1587
2045
  }
1588
- if (typeof inner.description === "string") {
1589
- return buildCaptured(inner.description);
2046
+ }
2047
+ function prepareExceptionCommand(opts) {
2048
+ const target = resolveTarget(opts);
2049
+ const stateRaw = (opts.type ?? "uncaught").trim().toLowerCase();
2050
+ if (!VALID_PAUSE_TYPES.includes(stateRaw)) {
2051
+ throw new CfInspectorError(
2052
+ "INVALID_PAUSE_TYPE",
2053
+ `--type must be one of ${VALID_PAUSE_TYPES.join(", ")} (received "${stateRaw}")`
2054
+ );
1590
2055
  }
1591
- if (isPrimitive(inner.value)) {
1592
- return buildCaptured(formatPrimitive(inner.value));
2056
+ const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_EXCEPTION_TIMEOUT_SEC;
2057
+ const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
2058
+ const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
2059
+ return {
2060
+ target,
2061
+ state: stateRaw,
2062
+ captures: parseCaptureList(opts.capture),
2063
+ remoteRoot: parseRemoteRoot(opts.remoteRoot),
2064
+ timeoutMs: timeoutSec * 1e3,
2065
+ ...maxValueLength === void 0 ? {} : { maxValueLength },
2066
+ ...stackDepth === void 0 ? {} : { stackDepth },
2067
+ stackCaptures: parseCaptureList(opts.stackCaptures)
2068
+ };
2069
+ }
2070
+ async function runExceptionCommand(command, opts) {
2071
+ return await withSession(command.target, async (session) => {
2072
+ await setPauseOnExceptions(session, command.state);
2073
+ try {
2074
+ const pause = await waitForPause(session, {
2075
+ timeoutMs: command.timeoutMs,
2076
+ pauseReasons: ["exception", "promiseRejection"],
2077
+ unmatchedPausePolicy: "wait-for-resume"
2078
+ });
2079
+ const pausedStartedAt = pause.receivedAtMs ?? performance3.now();
2080
+ const snapshot = await captureSnapshot(session, pause, {
2081
+ captures: command.captures,
2082
+ includeScopes: opts.includeScopes === true,
2083
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength },
2084
+ ...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
2085
+ stackCaptures: command.stackCaptures
2086
+ });
2087
+ if (opts.keepPaused === true) {
2088
+ return withPausedDuration(snapshot, null);
2089
+ }
2090
+ return await resumeAfterException(session, snapshot, pausedStartedAt);
2091
+ } finally {
2092
+ await disablePauseOnExceptionsBestEffort(session);
2093
+ }
2094
+ });
2095
+ }
2096
+ async function resumeAfterException(session, snapshot, pausedStartedAt) {
2097
+ try {
2098
+ await resume(session);
2099
+ return withPausedDuration(snapshot, roundDurationMs(performance3.now() - pausedStartedAt));
2100
+ } catch {
2101
+ process5.stderr.write(
2102
+ "[cf-inspector] warning: Debugger.resume failed after exception capture; pausedDurationMs is unknown.\n"
2103
+ );
2104
+ return withPausedDuration(snapshot, null);
1593
2105
  }
1594
- return buildCaptured("undefined");
1595
2106
  }
1596
- function readEvalError(result, maxValueLength) {
1597
- const text = typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1598
- return limitValueLength(text, maxValueLength);
2107
+ async function disablePauseOnExceptionsBestEffort(session) {
2108
+ try {
2109
+ await setPauseOnExceptions(session, "none");
2110
+ } catch {
2111
+ }
1599
2112
  }
1600
2113
 
1601
- // src/snapshot/properties.ts
1602
- var MAX_SCOPE_VARIABLES = 20;
1603
- var MAX_CHILD_VARIABLES = 8;
1604
- var MAX_VARIABLE_DEPTH = 2;
1605
- function buildDescribed(value, type, objectId) {
1606
- const base = { value };
1607
- if (type !== void 0) {
1608
- base.type = type;
2114
+ // src/cli/commands/listScripts.ts
2115
+ import process6 from "process";
2116
+ async function handleListScripts(opts) {
2117
+ const target = resolveTarget(opts);
2118
+ const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
2119
+ if (opts.json) {
2120
+ writeJson(scripts);
2121
+ return;
1609
2122
  }
1610
- if (objectId !== void 0) {
1611
- base.objectId = objectId;
2123
+ for (const script of scripts) {
2124
+ process6.stdout.write(`${script.scriptId} ${script.url}
2125
+ `);
1612
2126
  }
1613
- return base;
1614
2127
  }
1615
- function describeProperty(prop) {
1616
- const value = prop.value;
1617
- if (value === void 0) {
1618
- return { value: "undefined" };
1619
- }
1620
- const type = typeof value.type === "string" ? value.type : void 0;
1621
- const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
1622
- if (type === "undefined") {
1623
- return buildDescribed("undefined", type);
1624
- }
1625
- if (type === "string" && typeof value.value === "string") {
1626
- return buildDescribed(JSON.stringify(value.value), type);
2128
+
2129
+ // src/cli/commands/log.ts
2130
+ import process8 from "process";
2131
+
2132
+ // src/logpoint/stream.ts
2133
+ init_types();
2134
+
2135
+ // src/logpoint/condition.ts
2136
+ import { randomBytes } from "crypto";
2137
+ var SENTINEL_PREFIX = "__CFI_LOG_";
2138
+ var SENTINEL_SUFFIX = "__";
2139
+ var HITS_GLOBAL2 = "globalThis.__CFI_LOG_HITS";
2140
+ function buildLoggingIife(sentinel, expression) {
2141
+ return [
2142
+ "(function(){",
2143
+ `var s=${JSON.stringify(sentinel)};`,
2144
+ "try{",
2145
+ `var v=(${expression});`,
2146
+ "var r=typeof v==='string'?v:JSON.stringify(v);",
2147
+ "console.log(s, r);",
2148
+ "}catch(e){",
2149
+ "console.log(s, '!err:'+(e&&e.message?e.message:String(e)));",
2150
+ "}",
2151
+ "return false;",
2152
+ "})()"
2153
+ ].join("");
2154
+ }
2155
+ function buildHitGate(hitCount, counterKey) {
2156
+ const keyLiteral = JSON.stringify(counterKey);
2157
+ return [
2158
+ "(function(){",
2159
+ `var m=(${HITS_GLOBAL2}=${HITS_GLOBAL2}||{});`,
2160
+ `var k=${keyLiteral};`,
2161
+ "m[k]=(m[k]||0)+1;",
2162
+ `return m[k]>=${hitCount.toString()};`,
2163
+ "})()"
2164
+ ].join("");
2165
+ }
2166
+ function combineGuards(guards) {
2167
+ const filtered = guards.filter((guard) => guard.length > 0);
2168
+ if (filtered.length === 0) {
2169
+ return void 0;
1627
2170
  }
1628
- if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
1629
- return buildDescribed(formatPrimitive(value.value), type);
2171
+ if (filtered.length === 1) {
2172
+ return filtered[0];
1630
2173
  }
1631
- if (typeof value.description === "string") {
1632
- return buildDescribed(value.description, type, objectId);
2174
+ return filtered.map((guard) => `(${guard})`).join("&&");
2175
+ }
2176
+ function buildLogpointCondition(sentinel, expression, options = {}) {
2177
+ const guards = [];
2178
+ if (options.hitCount !== void 0) {
2179
+ const counterKey = options.counterKey ?? sentinel;
2180
+ guards.push(buildHitGate(options.hitCount, counterKey));
1633
2181
  }
1634
- if (isPrimitive(value.value)) {
1635
- return buildDescribed(formatPrimitive(value.value), type);
2182
+ const userPredicate = options.predicate?.trim();
2183
+ if (userPredicate !== void 0 && userPredicate.length > 0) {
2184
+ guards.push(`(${userPredicate})`);
1636
2185
  }
1637
- if (objectId === void 0) {
1638
- return buildDescribed("undefined", type);
2186
+ const iife = buildLoggingIife(sentinel, expression);
2187
+ const guard = combineGuards(guards);
2188
+ if (guard === void 0) {
2189
+ return iife;
1639
2190
  }
1640
- return buildDescribed("[object]", type, objectId);
2191
+ return `(${guard})?${iife}:false`;
1641
2192
  }
1642
- function isExpandable(type) {
1643
- return type === "object" || type === "function";
1644
- }
1645
- async function captureProperties(session, objectId, limit, depth, maxValueLength) {
1646
- const properties = await getProperties(session, objectId);
1647
- const limited = properties.slice(0, limit);
1648
- const variables = await Promise.all(
1649
- limited.map(async (prop) => {
1650
- return await captureProperty(session, prop, depth, maxValueLength);
1651
- })
1652
- );
1653
- return variables;
2193
+ function generateSentinel() {
2194
+ return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1654
2195
  }
1655
- async function captureProperty(session, prop, depth, maxValueLength) {
1656
- const name = typeof prop.name === "string" ? prop.name : "?";
1657
- const described = describeProperty(prop);
1658
- const children = await capturePropertyChildren(session, described, depth, maxValueLength);
1659
- const sanitizedValue = limitValueLength(described.value, maxValueLength);
1660
- const base = { name, value: sanitizedValue };
1661
- const withType = described.type === void 0 ? base : { ...base, type: described.type };
1662
- return children === void 0 ? withType : { ...withType, children };
2196
+
2197
+ // src/logpoint/events.ts
2198
+ function asString3(value) {
2199
+ return typeof value === "string" ? value : void 0;
1663
2200
  }
1664
- async function capturePropertyChildren(session, described, depth, maxValueLength) {
1665
- if (depth <= 0 || described.objectId === void 0 || !isExpandable(described.type)) {
2201
+ function readArg(arg, index) {
2202
+ if (typeof arg !== "object" || arg === null) {
1666
2203
  return void 0;
1667
2204
  }
1668
- try {
1669
- const nested = await captureProperties(
1670
- session,
1671
- described.objectId,
1672
- MAX_CHILD_VARIABLES,
1673
- depth - 1,
1674
- maxValueLength
1675
- );
1676
- return nested.length > 0 ? nested : void 0;
1677
- } catch {
1678
- return void 0;
2205
+ const candidate = arg;
2206
+ if (candidate.type === "string" && typeof candidate.value === "string") {
2207
+ return candidate.value;
2208
+ }
2209
+ const isPrimitiveType = candidate.type === "number" || candidate.type === "boolean" || candidate.type === "bigint";
2210
+ const isPrimitiveValue = typeof candidate.value === "number" || typeof candidate.value === "boolean" || typeof candidate.value === "bigint";
2211
+ if (isPrimitiveType && isPrimitiveValue) {
2212
+ return String(candidate.value);
1679
2213
  }
2214
+ return index === 0 ? void 0 : "";
1680
2215
  }
1681
-
1682
- // src/snapshot/objects.ts
1683
- function objectIdFromEvalResult(result) {
1684
- const inner = result.result;
1685
- if (inner?.type !== "object") {
2216
+ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
2217
+ if (!Array.isArray(rawArgs) || rawArgs.length < 2) {
1686
2218
  return void 0;
1687
2219
  }
1688
- const objectId = inner.objectId;
1689
- if (typeof objectId !== "string" || objectId.length === 0) {
2220
+ const tag = readArg(rawArgs[0], 0);
2221
+ if (tag !== sentinel) {
1690
2222
  return void 0;
1691
2223
  }
1692
- return objectId;
2224
+ const payload = readArg(rawArgs[1], 1) ?? "";
2225
+ const ts = new Date(typeof timestamp === "number" ? timestamp : Date.now()).toISOString();
2226
+ const at = `${location.file}:${location.line.toString()}`;
2227
+ if (payload.startsWith("!err:")) {
2228
+ return { ts, at, error: payload.slice("!err:".length) };
2229
+ }
2230
+ return parsePayload(ts, at, payload);
1693
2231
  }
1694
- async function renderObjectCapture(session, objectId, maxValueLength) {
2232
+ function parsePayload(ts, at, payload) {
1695
2233
  try {
1696
- const properties = await captureProperties(
1697
- session,
1698
- objectId,
1699
- MAX_SCOPE_VARIABLES,
1700
- MAX_VARIABLE_DEPTH,
1701
- maxValueLength
1702
- );
1703
- const structured = {};
1704
- for (const variable of properties) {
1705
- structured[variable.name] = toStructuredValue(variable);
2234
+ const parsed = JSON.parse(payload);
2235
+ if (typeof parsed === "string") {
2236
+ return { ts, at, value: parsed };
1706
2237
  }
1707
- return JSON.stringify(structured);
2238
+ return { ts, at, value: JSON.stringify(parsed) };
1708
2239
  } catch {
1709
- return void 0;
2240
+ return { ts, at, value: payload, raw: payload };
1710
2241
  }
1711
2242
  }
1712
- function normalizeRenderedObjectCapture(rendered, original) {
1713
- if (rendered === "{}" && original !== "Object") {
2243
+
2244
+ // src/logpoint/stream.ts
2245
+ function validateMaxEvents(maxEvents) {
2246
+ if (maxEvents === void 0) {
1714
2247
  return void 0;
1715
2248
  }
1716
- if (original.startsWith("Array(") && rendered === '{"length":0}') {
1717
- return "[]";
2249
+ if (!Number.isInteger(maxEvents) || maxEvents <= 0) {
2250
+ throw new CfInspectorError(
2251
+ "INVALID_ARGUMENT",
2252
+ `maxEvents must be a positive integer, received: ${maxEvents.toString()}`
2253
+ );
1718
2254
  }
1719
- return rendered;
2255
+ return maxEvents;
1720
2256
  }
1721
- async function withSerializedObjectCapture(session, expression, evalResult, captured, maxValueLength) {
1722
- if (captured.error !== void 0 || captured.value === void 0) {
1723
- return captured;
1724
- }
1725
- const objectId = objectIdFromEvalResult(evalResult);
1726
- if (objectId === void 0) {
1727
- return captured;
1728
- }
1729
- const rendered = await renderObjectCapture(session, objectId, maxValueLength);
1730
- if (rendered === void 0) {
1731
- return captured;
2257
+ function validateHitCount2(hitCount) {
2258
+ if (hitCount === void 0) {
2259
+ return void 0;
1732
2260
  }
1733
- const normalized = normalizeRenderedObjectCapture(rendered, captured.value);
1734
- if (normalized === void 0) {
1735
- return captured;
2261
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
2262
+ throw new CfInspectorError(
2263
+ "INVALID_HIT_COUNT",
2264
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
2265
+ );
1736
2266
  }
1737
- const value = limitValueLength(normalized, maxValueLength);
1738
- return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
1739
- }
1740
-
1741
- // src/snapshot/scopes.ts
1742
- var MAX_SCOPES = 3;
1743
- var PRIORITY_BY_TYPE = {
1744
- local: 0,
1745
- arguments: 1,
1746
- block: 2,
1747
- closure: 3,
1748
- catch: 4,
1749
- with: 5,
1750
- module: 6,
1751
- script: 7
1752
- };
1753
- function selectScopes(scopeChain) {
1754
- const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
1755
- return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
1756
- }
1757
- function priorityOf(type) {
1758
- return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
1759
- }
1760
- async function captureScopes(session, frame, maxValueLength) {
1761
- const scopes = selectScopes(frame.scopeChain);
1762
- return await Promise.all(
1763
- scopes.map(async (scope) => {
1764
- const objectId = scope.objectId;
1765
- if (objectId === void 0) {
1766
- return { type: scope.type, variables: [] };
1767
- }
1768
- try {
1769
- const variables = await captureProperties(
1770
- session,
1771
- objectId,
1772
- MAX_SCOPE_VARIABLES,
1773
- MAX_VARIABLE_DEPTH,
1774
- maxValueLength
1775
- );
1776
- return { type: scope.type, variables };
1777
- } catch {
1778
- return { type: scope.type, variables: [] };
1779
- }
1780
- })
1781
- );
2267
+ return hitCount;
1782
2268
  }
1783
-
1784
- // src/snapshot/capture.ts
1785
- async function captureSnapshot(session, pause, options = {}) {
1786
- const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1787
- const top = pause.callFrames[0];
1788
- let topFrame;
1789
- let captures = [];
1790
- if (top) {
1791
- topFrame = {
1792
- functionName: top.functionName,
1793
- ...top.url === void 0 ? {} : { url: top.url },
1794
- line: top.lineNumber + 1,
1795
- column: top.columnNumber + 1
1796
- };
1797
- if (options.includeScopes === true) {
1798
- const scopes = await captureScopes(session, top, maxValueLength);
1799
- topFrame = { ...topFrame, scopes };
2269
+ async function streamLogpoint(session, options) {
2270
+ const maxEvents = validateMaxEvents(options.maxEvents);
2271
+ const hitCount = validateHitCount2(options.hitCount);
2272
+ const sentinel = generateSentinel();
2273
+ const condition = buildLogpointCondition(sentinel, options.expression, {
2274
+ ...options.condition === void 0 ? {} : { predicate: options.condition },
2275
+ ...hitCount === void 0 ? {} : { hitCount }
2276
+ });
2277
+ let emitted = 0;
2278
+ let maxEventsReached = false;
2279
+ let stopMaxEvents;
2280
+ const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
2281
+ if (maxEventsReached) {
2282
+ return;
1800
2283
  }
1801
- captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
2284
+ const event = toLogpointEvent(raw, sentinel, options.location);
2285
+ if (event === void 0) {
2286
+ return;
2287
+ }
2288
+ emitted += 1;
2289
+ try {
2290
+ options.onEvent(event);
2291
+ } catch {
2292
+ }
2293
+ if (maxEvents !== void 0 && emitted >= maxEvents) {
2294
+ maxEventsReached = true;
2295
+ stopMaxEvents?.();
2296
+ }
2297
+ });
2298
+ let handle;
2299
+ try {
2300
+ handle = await setBreakpoint(session, {
2301
+ file: options.location.file,
2302
+ line: options.location.line,
2303
+ ...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
2304
+ condition
2305
+ });
2306
+ } catch (err) {
2307
+ offEvent();
2308
+ throw err;
1802
2309
  }
1803
- return {
1804
- reason: pause.reason,
1805
- hitBreakpoints: pause.hitBreakpoints,
1806
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1807
- ...topFrame === void 0 ? {} : { topFrame },
1808
- captures
1809
- };
1810
- }
1811
- async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1812
- if (captures === void 0 || captures.length === 0) {
1813
- return [];
2310
+ options.onBreakpointSet?.(handle);
2311
+ try {
2312
+ const reason = await waitForStop(session, options, (signal) => {
2313
+ stopMaxEvents = signal;
2314
+ if (maxEventsReached) {
2315
+ signal();
2316
+ }
2317
+ });
2318
+ return { handle, sentinel, emitted, stoppedReason: reason };
2319
+ } finally {
2320
+ offEvent();
2321
+ await removeBreakpointBestEffort(session, handle.breakpointId);
1814
2322
  }
1815
- return await Promise.all(
1816
- captures.map(async (expression) => {
1817
- return await captureExpression(session, callFrameId, expression, maxValueLength);
1818
- })
1819
- );
1820
2323
  }
1821
- async function captureExpression(session, callFrameId, expression, maxValueLength) {
2324
+ function toLogpointEvent(raw, sentinel, location) {
2325
+ const params = raw;
2326
+ if (asString3(params.type) !== "log") {
2327
+ return void 0;
2328
+ }
2329
+ const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
2330
+ return parseLogEvent(params.args, sentinel, location, ts);
2331
+ }
2332
+ async function removeBreakpointBestEffort(session, breakpointId) {
1822
2333
  try {
1823
- const result = await evaluateOnFrame(session, callFrameId, expression);
1824
- const captured = evalResultToCaptured(expression, result, maxValueLength);
1825
- return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1826
- } catch (err) {
1827
- const message = err instanceof Error ? err.message : String(err);
1828
- return { expression, error: limitValueLength(message, maxValueLength) };
2334
+ await removeBreakpoint(session, breakpointId);
2335
+ } catch {
1829
2336
  }
1830
2337
  }
2338
+ async function waitForStop(session, options, registerMaxEventsSignal) {
2339
+ return await new Promise((resolve) => {
2340
+ let settled = false;
2341
+ const finish = (reason) => {
2342
+ if (settled) {
2343
+ return;
2344
+ }
2345
+ settled = true;
2346
+ cleanup();
2347
+ resolve(reason);
2348
+ };
2349
+ const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
2350
+ finish("duration");
2351
+ }, options.durationMs);
2352
+ const offClose = session.client.onClose(() => {
2353
+ finish("transport-closed");
2354
+ });
2355
+ const onAbort = () => {
2356
+ finish("signal");
2357
+ };
2358
+ options.signal?.addEventListener("abort", onAbort, { once: true });
2359
+ if (options.signal?.aborted === true) {
2360
+ finish("signal");
2361
+ }
2362
+ registerMaxEventsSignal(() => {
2363
+ finish("max-events");
2364
+ });
2365
+ function cleanup() {
2366
+ if (timer !== void 0) {
2367
+ clearTimeout(timer);
2368
+ }
2369
+ offClose();
2370
+ options.signal?.removeEventListener("abort", onAbort);
2371
+ }
2372
+ });
2373
+ }
1831
2374
 
1832
- // src/cli/commands/snapshot.ts
2375
+ // src/cli/commands/log.ts
1833
2376
  init_types();
1834
2377
 
1835
- // src/cli/captureParser.ts
1836
- function parseCaptureList(raw) {
1837
- if (raw === void 0 || raw.trim().length === 0) {
1838
- return [];
1839
- }
1840
- return splitCaptureExpressions(raw);
1841
- }
1842
- function isQuoteChar(value) {
1843
- return value === "'" || value === '"' || value === "`";
1844
- }
1845
- function consumeQuotedChar(state, char) {
1846
- if (state.quote === void 0) {
1847
- return false;
1848
- }
1849
- if (state.escaped) {
1850
- state.escaped = false;
1851
- return true;
1852
- }
1853
- if (char === "\\") {
1854
- state.escaped = true;
1855
- return true;
1856
- }
1857
- if (char === state.quote) {
1858
- state.quote = void 0;
1859
- }
1860
- return true;
1861
- }
1862
- function updateCaptureDepth(state, char) {
1863
- if (char === "(") {
1864
- state.parenDepth += 1;
1865
- } else if (char === ")") {
1866
- state.parenDepth = Math.max(0, state.parenDepth - 1);
1867
- } else if (char === "[") {
1868
- state.bracketDepth += 1;
1869
- } else if (char === "]") {
1870
- state.bracketDepth = Math.max(0, state.bracketDepth - 1);
1871
- } else if (char === "{") {
1872
- state.braceDepth += 1;
1873
- } else if (char === "}") {
1874
- state.braceDepth = Math.max(0, state.braceDepth - 1);
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);
1875
2392
  }
1876
2393
  }
1877
- function isTopLevel(state) {
1878
- return state.parenDepth === 0 && state.bracketDepth === 0 && state.braceDepth === 0;
1879
- }
1880
- function appendCapturePiece(raw, state, end) {
1881
- const piece = raw.slice(state.start, end).trim();
1882
- if (piece.length > 0) {
1883
- state.pieces.push(piece);
2394
+
2395
+ // src/cli/commands/log.ts
2396
+ async function handleLog(opts) {
2397
+ const target = resolveTarget(opts);
2398
+ const location = parseBreakpointSpec(opts.at);
2399
+ const remoteRoot = parseRemoteRoot(opts.remoteRoot);
2400
+ const durationSec = parsePositiveInt(opts.duration, "--duration");
2401
+ const maxEvents = parsePositiveInt(opts.maxEvents, "--max-events");
2402
+ const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
2403
+ const expression = opts.expr.trim();
2404
+ if (expression.length === 0) {
2405
+ throw new CfInspectorError("INVALID_EXPRESSION", "--expr must not be empty");
1884
2406
  }
2407
+ const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2408
+ await withTerminationSignal(async (signal) => {
2409
+ await withSession(target, async (session) => {
2410
+ await validateExpression(session, expression);
2411
+ if (condition !== void 0) {
2412
+ await validateExpression(session, condition);
2413
+ }
2414
+ const result = await streamLogpoint(session, {
2415
+ location,
2416
+ expression,
2417
+ remoteRoot,
2418
+ ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
2419
+ ...maxEvents === void 0 ? {} : { maxEvents },
2420
+ ...hitCount === void 0 ? {} : { hitCount },
2421
+ ...condition === void 0 ? {} : { condition },
2422
+ signal,
2423
+ onEvent: (event) => {
2424
+ writeLogEvent(event, opts.json);
2425
+ },
2426
+ onBreakpointSet: (handle) => {
2427
+ warnOnUnboundBreakpoints([handle]);
2428
+ }
2429
+ });
2430
+ writeLogSummary(result.stoppedReason, result.emitted, opts.json);
2431
+ });
2432
+ });
1885
2433
  }
1886
- function splitCaptureExpressions(raw) {
1887
- const state = {
1888
- escaped: false,
1889
- parenDepth: 0,
1890
- bracketDepth: 0,
1891
- braceDepth: 0,
1892
- quote: void 0,
1893
- start: 0,
1894
- pieces: []
1895
- };
1896
- for (let idx = 0; idx < raw.length; idx += 1) {
1897
- const char = raw.charAt(idx);
1898
- if (consumeQuotedChar(state, char)) {
1899
- continue;
1900
- }
1901
- if (isQuoteChar(char)) {
1902
- state.quote = char;
1903
- continue;
1904
- }
1905
- updateCaptureDepth(state, char);
1906
- if (char === "," && isTopLevel(state)) {
1907
- appendCapturePiece(raw, state, idx);
1908
- state.start = idx + 1;
1909
- }
2434
+ function writeLogSummary(stoppedReason, emitted, json) {
2435
+ if (json) {
2436
+ process8.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
2437
+ `);
2438
+ return;
1910
2439
  }
1911
- appendCapturePiece(raw, state, raw.length);
1912
- return state.pieces;
2440
+ process8.stderr.write(
2441
+ `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
2442
+ `
2443
+ );
1913
2444
  }
1914
2445
 
1915
2446
  // src/cli/commands/snapshot.ts
2447
+ import { performance as performance4 } from "perf_hooks";
2448
+ import process9 from "process";
2449
+ init_types();
1916
2450
  async function handleSnapshot(opts) {
1917
2451
  const prepared = prepareSnapshotCommand(opts);
1918
2452
  const result = await runSnapshotCommand(prepared, opts);
@@ -1933,6 +2467,8 @@ function prepareSnapshotCommand(opts) {
1933
2467
  const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
1934
2468
  const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
1935
2469
  const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2470
+ const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
2471
+ const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
1936
2472
  return {
1937
2473
  target,
1938
2474
  breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
@@ -1940,7 +2476,10 @@ function prepareSnapshotCommand(opts) {
1940
2476
  remoteRoot: parseRemoteRoot(opts.remoteRoot),
1941
2477
  timeoutMs: timeoutSec * 1e3,
1942
2478
  ...condition === void 0 ? {} : { condition },
1943
- ...maxValueLength === void 0 ? {} : { maxValueLength }
2479
+ ...maxValueLength === void 0 ? {} : { maxValueLength },
2480
+ ...hitCount === void 0 ? {} : { hitCount },
2481
+ ...stackDepth === void 0 ? {} : { stackDepth },
2482
+ stackCaptures: parseCaptureList(opts.stackCaptures)
1944
2483
  };
1945
2484
  }
1946
2485
  async function runSnapshotCommand(command, opts) {
@@ -1954,17 +2493,20 @@ async function runSnapshotCommand(command, opts) {
1954
2493
  file: bp.file,
1955
2494
  line: bp.line,
1956
2495
  remoteRoot: command.remoteRoot,
1957
- ...command.condition === void 0 ? {} : { condition: command.condition }
2496
+ ...command.condition === void 0 ? {} : { condition: command.condition },
2497
+ ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
1958
2498
  })
1959
2499
  )
1960
2500
  );
1961
2501
  warnOnUnboundBreakpoints(handles);
1962
2502
  const pause = await waitForCommandPause(session, opts, handles, command.timeoutMs);
1963
- const pausedStartedAt = pause.receivedAtMs ?? performance3.now();
2503
+ const pausedStartedAt = pause.receivedAtMs ?? performance4.now();
1964
2504
  const snapshot = await captureSnapshot(session, pause, {
1965
2505
  captures: command.captures,
1966
2506
  includeScopes: opts.includeScopes === true,
1967
- ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength }
2507
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength },
2508
+ ...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
2509
+ stackCaptures: command.stackCaptures
1968
2510
  });
1969
2511
  if (opts.keepPaused === true) {
1970
2512
  return withPausedDuration(snapshot, null);
@@ -1990,47 +2532,285 @@ async function waitForCommandPause(session, opts, handles, timeoutMs) {
1990
2532
  async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
1991
2533
  try {
1992
2534
  await resume(session);
1993
- return withPausedDuration(snapshot, roundDurationMs(performance3.now() - pausedStartedAt));
2535
+ return withPausedDuration(snapshot, roundDurationMs(performance4.now() - pausedStartedAt));
1994
2536
  } catch {
1995
- process7.stderr.write(
2537
+ process9.stderr.write(
1996
2538
  "[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
1997
2539
  );
1998
2540
  return withPausedDuration(snapshot, null);
1999
2541
  }
2000
2542
  }
2001
2543
 
2544
+ // src/cli/commands/watch.ts
2545
+ import { performance as performance5 } from "perf_hooks";
2546
+ import process10 from "process";
2547
+ init_types();
2548
+ async function handleWatch(opts) {
2549
+ const prepared = prepareWatchCommand(opts);
2550
+ let stoppedReason = "signal";
2551
+ let emitted = 0;
2552
+ await withTerminationSignal(async (signal) => {
2553
+ await withSession(prepared.target, async (session) => {
2554
+ const result = await runWatchLoop(session, prepared, opts, signal);
2555
+ stoppedReason = result.stoppedReason;
2556
+ emitted = result.emitted;
2557
+ });
2558
+ });
2559
+ writeWatchSummary(stoppedReason, emitted, opts.json);
2560
+ }
2561
+ function prepareWatchCommand(opts) {
2562
+ const target = resolveTarget(opts);
2563
+ if (opts.bp.length === 0) {
2564
+ throw new CfInspectorError(
2565
+ "INVALID_BREAKPOINT",
2566
+ "At least one --bp <file:line> is required."
2567
+ );
2568
+ }
2569
+ const perHitTimeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
2570
+ const durationSec = parsePositiveInt(opts.duration, "--duration");
2571
+ const maxEvents = parsePositiveInt(opts.maxEvents, "--max-events");
2572
+ const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
2573
+ const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
2574
+ const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
2575
+ const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2576
+ return {
2577
+ target,
2578
+ breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
2579
+ captures: parseCaptureList(opts.capture),
2580
+ remoteRoot: parseRemoteRoot(opts.remoteRoot),
2581
+ perHitTimeoutMs: perHitTimeoutSec * 1e3,
2582
+ ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
2583
+ ...maxEvents === void 0 ? {} : { maxEvents },
2584
+ ...maxValueLength === void 0 ? {} : { maxValueLength },
2585
+ ...condition === void 0 ? {} : { condition },
2586
+ ...hitCount === void 0 ? {} : { hitCount },
2587
+ ...stackDepth === void 0 ? {} : { stackDepth },
2588
+ stackCaptures: parseCaptureList(opts.stackCaptures)
2589
+ };
2590
+ }
2591
+ async function runWatchLoop(session, command, opts, signal) {
2592
+ if (command.condition !== void 0) {
2593
+ await validateExpression(session, command.condition);
2594
+ }
2595
+ const handles = await Promise.all(
2596
+ command.breakpoints.map(
2597
+ (bp) => setBreakpoint(session, {
2598
+ file: bp.file,
2599
+ line: bp.line,
2600
+ remoteRoot: command.remoteRoot,
2601
+ ...command.condition === void 0 ? {} : { condition: command.condition },
2602
+ ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
2603
+ })
2604
+ )
2605
+ );
2606
+ warnOnUnboundBreakpoints(handles);
2607
+ const deadline = computeDeadline(command.durationMs);
2608
+ let emitted = 0;
2609
+ const state = { stopped: false, reason: "signal" };
2610
+ const setStop = (reason) => {
2611
+ if (state.stopped) {
2612
+ return;
2613
+ }
2614
+ state.reason = reason;
2615
+ state.stopped = true;
2616
+ };
2617
+ const transportClosed = waitForTransportClose(session);
2618
+ transportClosed.promise.then(() => {
2619
+ setStop("transport-closed");
2620
+ }).catch(() => {
2621
+ });
2622
+ try {
2623
+ while (!state.stopped) {
2624
+ if (signal.aborted) {
2625
+ setStop("signal");
2626
+ break;
2627
+ }
2628
+ const remainingMs = remainingForLoop(deadline, command.perHitTimeoutMs);
2629
+ if (remainingMs <= 0) {
2630
+ setStop("duration");
2631
+ break;
2632
+ }
2633
+ const pause = await waitForNextWatchPause(session, handles, remainingMs, signal);
2634
+ if (pause === "signal") {
2635
+ setStop("signal");
2636
+ break;
2637
+ }
2638
+ if (pause === "timeout") {
2639
+ if (deadline !== void 0 && performance5.now() >= deadline) {
2640
+ setStop("duration");
2641
+ break;
2642
+ }
2643
+ continue;
2644
+ }
2645
+ const event = await captureWatchEvent(session, command, pause, emitted + 1, opts);
2646
+ emitted += 1;
2647
+ writeWatchEvent(event, opts.json);
2648
+ try {
2649
+ await resume(session);
2650
+ } catch {
2651
+ process10.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
2652
+ setStop("transport-closed");
2653
+ break;
2654
+ }
2655
+ if (command.maxEvents !== void 0 && emitted >= command.maxEvents) {
2656
+ setStop("max-events");
2657
+ break;
2658
+ }
2659
+ }
2660
+ } finally {
2661
+ transportClosed.cancel();
2662
+ }
2663
+ return { emitted, stoppedReason: state.reason };
2664
+ }
2665
+ function computeDeadline(durationMs) {
2666
+ if (durationMs === void 0) {
2667
+ return void 0;
2668
+ }
2669
+ return performance5.now() + durationMs;
2670
+ }
2671
+ function remainingForLoop(deadline, perHitTimeoutMs) {
2672
+ if (deadline === void 0) {
2673
+ return perHitTimeoutMs;
2674
+ }
2675
+ const remaining = deadline - performance5.now();
2676
+ if (remaining <= 0) {
2677
+ return 0;
2678
+ }
2679
+ return Math.min(remaining, perHitTimeoutMs);
2680
+ }
2681
+ function waitForTransportClose(session) {
2682
+ let cancelled = false;
2683
+ let resolve;
2684
+ const promise = new Promise((res) => {
2685
+ resolve = res;
2686
+ });
2687
+ const off = session.client.onClose(() => {
2688
+ if (!cancelled) {
2689
+ resolve?.();
2690
+ }
2691
+ });
2692
+ return {
2693
+ promise,
2694
+ cancel: () => {
2695
+ cancelled = true;
2696
+ off();
2697
+ resolve?.();
2698
+ }
2699
+ };
2700
+ }
2701
+ async function waitForNextWatchPause(session, handles, timeoutMs, signal) {
2702
+ if (signal.aborted) {
2703
+ return "signal";
2704
+ }
2705
+ try {
2706
+ return await waitForPause(session, {
2707
+ timeoutMs,
2708
+ breakpointIds: handles.map((h) => h.breakpointId),
2709
+ unmatchedPausePolicy: "wait-for-resume"
2710
+ });
2711
+ } catch (err) {
2712
+ if (err instanceof CfInspectorError) {
2713
+ if (err.code === "BREAKPOINT_NOT_HIT") {
2714
+ return "timeout";
2715
+ }
2716
+ if (err.code === "UNRELATED_PAUSE_TIMEOUT") {
2717
+ return "timeout";
2718
+ }
2719
+ }
2720
+ throw err;
2721
+ }
2722
+ }
2723
+ async function captureWatchEvent(session, command, pause, hit, opts) {
2724
+ const snapshot = await captureSnapshot(session, pause, {
2725
+ captures: command.captures,
2726
+ includeScopes: opts.includeScopes === true,
2727
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength },
2728
+ ...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
2729
+ stackCaptures: command.stackCaptures
2730
+ });
2731
+ const at = formatLocation(command, snapshot.topFrame);
2732
+ const base = {
2733
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2734
+ at,
2735
+ hit,
2736
+ reason: snapshot.reason,
2737
+ hitBreakpoints: snapshot.hitBreakpoints,
2738
+ captures: snapshot.captures
2739
+ };
2740
+ const withFrame = snapshot.topFrame === void 0 ? base : { ...base, topFrame: snapshot.topFrame };
2741
+ const withStack = snapshot.stack === void 0 ? withFrame : { ...withFrame, stack: snapshot.stack };
2742
+ return snapshot.exception === void 0 ? withStack : { ...withStack, exception: snapshot.exception };
2743
+ }
2744
+ function formatLocation(command, topFrame) {
2745
+ if (topFrame?.url !== void 0) {
2746
+ return `${topFrame.url}:${topFrame.line.toString()}`;
2747
+ }
2748
+ const first = command.breakpoints[0];
2749
+ if (first === void 0) {
2750
+ return "(unknown)";
2751
+ }
2752
+ return `${first.file}:${first.line.toString()}`;
2753
+ }
2754
+ function writeWatchSummary(reason, emitted, json) {
2755
+ if (json) {
2756
+ process10.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
2757
+ `);
2758
+ return;
2759
+ }
2760
+ process10.stderr.write(
2761
+ `Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
2762
+ `
2763
+ );
2764
+ }
2765
+
2002
2766
  // src/cli/program.ts
2003
2767
  function applyTargetOptions(cmd) {
2004
2768
  return cmd.option("--port <number>", "Local port the inspector or tunnel listens on").option("--host <host>", "Hostname (default: 127.0.0.1)", "127.0.0.1").option("--region <key>", "CF region key (e.g. eu10)").option("--org <name>", "CF org name").option("--space <name>", "CF space name").option("--app <name>", "CF app name").option("--cf-timeout <seconds>", "Timeout for CF tunnel readiness in seconds");
2005
2769
  }
2770
+ var collectStrings = (value, prev = []) => [
2771
+ ...prev,
2772
+ value
2773
+ ];
2006
2774
  async function main(argv) {
2007
2775
  const program = new Command();
2008
2776
  program.name("cf-inspector").description("Drive a Node.js inspector from the command line \u2014 set breakpoints, capture snapshots, evaluate expressions");
2009
2777
  registerSnapshot(program);
2010
2778
  registerLog(program);
2779
+ registerWatch(program);
2780
+ registerException(program);
2011
2781
  registerEval(program);
2012
2782
  registerListScripts(program);
2013
2783
  registerAttach(program);
2014
2784
  await program.parseAsync([...argv]);
2015
2785
  }
2016
2786
  function registerSnapshot(program) {
2017
- const collectStrings = (value, prev = []) => [
2018
- ...prev,
2019
- value
2020
- ];
2021
2787
  applyTargetOptions(
2022
2788
  program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
2023
- ).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("--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) => {
2789
+ ).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) => {
2024
2790
  await handleSnapshot(opts);
2025
2791
  });
2026
2792
  }
2027
2793
  function registerLog(program) {
2028
2794
  applyTargetOptions(
2029
2795
  program.command("log").description("Stream a non-pausing logpoint: log an expression each time a line executes")
2030
- ).requiredOption("--at <file:line>", "Logpoint location, e.g. src/handler.ts:42").requiredOption("--expr <expression>", "JavaScript expression to log on each hit").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
2796
+ ).requiredOption("--at <file:line>", "Logpoint location, e.g. src/handler.ts:42").requiredOption("--expr <expression>", "JavaScript expression to log on each hit").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N log events").option("--hit-count <n>", "Start logging once the line has been hit N or more times").option("--condition <expr>", "Only log when this JS expression evaluates truthy on the inspectee").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
2031
2797
  await handleLog(opts);
2032
2798
  });
2033
2799
  }
2800
+ function registerWatch(program) {
2801
+ applyTargetOptions(
2802
+ program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits")
2803
+ ).option("--bp <file:line>", "Breakpoint location (repeatable), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate per hit").option("--condition <expr>", "Only emit hits where this JS expression evaluates truthy").option("--hit-count <n>", "Start emitting after the line has been hit N or more times").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N watch events").option("--timeout <seconds>", "How long to wait for the next hit before giving up (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--stack-depth <n>", "Walk this many call frames per hit (default: 1)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame").option("--include-scopes", "Include expanded paused-frame scopes per hit").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
2804
+ await handleWatch(opts);
2805
+ });
2806
+ }
2807
+ function registerException(program) {
2808
+ applyTargetOptions(
2809
+ program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume")
2810
+ ).option("--type <state>", "Pause type: uncaught (default), caught, or all").option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--timeout <seconds>", "How long to wait for an exception (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1)").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("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--no-json", "Print a human-readable summary instead of JSON").action(async (opts) => {
2811
+ await handleException(opts);
2812
+ });
2813
+ }
2034
2814
  function registerEval(program) {
2035
2815
  applyTargetOptions(
2036
2816
  program.command("eval").description("Evaluate an expression against the global Runtime")
@@ -2056,20 +2836,20 @@ function registerAttach(program) {
2056
2836
  // src/cli.ts
2057
2837
  init_types();
2058
2838
  try {
2059
- await main(process8.argv);
2839
+ await main(process11.argv);
2060
2840
  } catch (err) {
2061
2841
  if (err instanceof CfInspectorError) {
2062
- process8.stderr.write(`Error [${err.code}]: ${err.message}
2842
+ process11.stderr.write(`Error [${err.code}]: ${err.message}
2063
2843
  `);
2064
2844
  if (err.detail !== void 0) {
2065
- process8.stderr.write(` detail: ${err.detail}
2845
+ process11.stderr.write(` detail: ${err.detail}
2066
2846
  `);
2067
2847
  }
2068
- process8.exit(1);
2848
+ process11.exit(1);
2069
2849
  }
2070
2850
  const message = err instanceof Error ? err.message : String(err);
2071
- process8.stderr.write(`Error: ${message}
2851
+ process11.stderr.write(`Error: ${message}
2072
2852
  `);
2073
- process8.exit(1);
2853
+ process11.exit(1);
2074
2854
  }
2075
2855
  //# sourceMappingURL=cli.js.map