@saptools/cf-inspector 0.3.18 → 0.4.0

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
@@ -110,7 +110,7 @@ var init_wsTransport = __esm({
110
110
  });
111
111
 
112
112
  // src/cli.ts
113
- import process8 from "process";
113
+ import process10 from "process";
114
114
 
115
115
  // src/cli/program.ts
116
116
  import { Command } from "commander";
@@ -250,8 +250,11 @@ function writeHumanSnapshot(snapshot) {
250
250
  ` reason: ${snapshot.reason}`,
251
251
  ` paused: ${pausedDuration}`
252
252
  );
253
+ if (snapshot.exception !== void 0) {
254
+ appendExceptionLines(lines, snapshot.exception);
255
+ }
253
256
  if (snapshot.topFrame) {
254
- appendFrameLines(lines, snapshot);
257
+ appendFrameLines(lines, snapshot.topFrame);
255
258
  }
256
259
  if (snapshot.captures.length > 0) {
257
260
  lines.push(" captures:");
@@ -260,14 +263,16 @@ function writeHumanSnapshot(snapshot) {
260
263
  lines.push(` ${capture.expression} = ${detail}`);
261
264
  }
262
265
  }
266
+ if (snapshot.stack !== void 0 && snapshot.stack.length > 0) {
267
+ lines.push(" stack:");
268
+ for (const frame of snapshot.stack) {
269
+ appendStackFrameLine(lines, frame);
270
+ }
271
+ }
263
272
  process.stdout.write(`${lines.join("\n")}
264
273
  `);
265
274
  }
266
- function appendFrameLines(lines, snapshot) {
267
- const frame = snapshot.topFrame;
268
- if (frame === void 0) {
269
- return;
270
- }
275
+ function appendFrameLines(lines, frame) {
271
276
  const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
272
277
  const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
273
278
  lines.push(
@@ -283,6 +288,25 @@ function appendFrameLines(lines, snapshot) {
283
288
  }
284
289
  }
285
290
  }
291
+ function appendStackFrameLine(lines, frame) {
292
+ const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
293
+ const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
294
+ lines.push(` ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`);
295
+ if (frame.captures !== void 0) {
296
+ for (const capture of frame.captures) {
297
+ const detail = capture.error ?? capture.value ?? "undefined";
298
+ lines.push(` ${capture.expression} = ${detail}`);
299
+ }
300
+ }
301
+ }
302
+ function appendExceptionLines(lines, exception) {
303
+ if (exception.error !== void 0) {
304
+ lines.push(` exception: !err ${exception.error}`);
305
+ return;
306
+ }
307
+ const detail = exception.description ?? exception.value ?? "(unknown)";
308
+ lines.push(` exception: ${detail}`);
309
+ }
286
310
  function writeLogEvent(event, json) {
287
311
  if (json) {
288
312
  process.stdout.write(`${JSON.stringify(event)}
@@ -297,6 +321,25 @@ function writeLogEvent(event, json) {
297
321
  process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
298
322
  `);
299
323
  }
324
+ function writeWatchEvent(event, json) {
325
+ if (json) {
326
+ process.stdout.write(`${JSON.stringify(event)}
327
+ `);
328
+ return;
329
+ }
330
+ process.stdout.write(`[${event.ts}] hit#${event.hit.toString()} ${event.at}
331
+ `);
332
+ if (event.exception !== void 0) {
333
+ const detail = event.exception.description ?? event.exception.value ?? event.exception.error ?? "(unknown)";
334
+ process.stdout.write(` exception: ${detail}
335
+ `);
336
+ }
337
+ for (const capture of event.captures) {
338
+ const detail = capture.error ?? capture.value ?? "undefined";
339
+ process.stdout.write(` ${capture.expression} = ${detail}
340
+ `);
341
+ }
342
+ }
300
343
 
301
344
  // src/cf/tunnel.ts
302
345
  import { startDebugger } from "@saptools/cf-debugger";
@@ -557,6 +600,9 @@ function asString(value, fallback = "") {
557
600
  function asNumber(value, fallback = 0) {
558
601
  return typeof value === "number" && Number.isFinite(value) ? value : fallback;
559
602
  }
603
+ function nonEmptyString(value) {
604
+ return typeof value === "string" && value.length > 0 ? value : void 0;
605
+ }
560
606
  function toResolvedLocations(value) {
561
607
  if (!Array.isArray(value)) {
562
608
  return [];
@@ -595,7 +641,18 @@ function toScopeChain(value) {
595
641
  return [objectId === void 0 ? base : { ...base, objectId }];
596
642
  });
597
643
  }
598
- function toCallFrames(value) {
644
+ function resolveCallFrameUrl(frame, scripts) {
645
+ const direct = nonEmptyString(frame.url);
646
+ if (direct !== void 0) {
647
+ return direct;
648
+ }
649
+ const scriptId = nonEmptyString(frame.location?.scriptId);
650
+ if (scriptId === void 0) {
651
+ return void 0;
652
+ }
653
+ return nonEmptyString(scripts?.get(scriptId)?.url);
654
+ }
655
+ function toCallFrames(value, scripts) {
599
656
  if (!Array.isArray(value)) {
600
657
  return [];
601
658
  }
@@ -608,7 +665,7 @@ function toCallFrames(value) {
608
665
  if (callFrameId.length === 0) {
609
666
  return [];
610
667
  }
611
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
668
+ const url = resolveCallFrameUrl(candidate, scripts);
612
669
  const base = {
613
670
  callFrameId,
614
671
  functionName: asString(candidate.functionName),
@@ -619,13 +676,14 @@ function toCallFrames(value) {
619
676
  return [url === void 0 ? base : { ...base, url }];
620
677
  });
621
678
  }
622
- function toPauseEvent(params, receivedAtMs) {
623
- return {
679
+ function toPauseEvent(params, receivedAtMs, scripts) {
680
+ const base = {
624
681
  reason: asString(params.reason),
625
682
  hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
626
- callFrames: toCallFrames(params.callFrames),
683
+ callFrames: toCallFrames(params.callFrames, scripts),
627
684
  receivedAtMs
628
685
  };
686
+ return params.data === void 0 ? base : { ...base, data: params.data };
629
687
  }
630
688
  function topFrameLocation(pause) {
631
689
  const top = pause.callFrames[0];
@@ -677,7 +735,7 @@ async function connectInspector(options) {
677
735
  return;
678
736
  }
679
737
  const params = raw;
680
- const event = toPauseEvent(params, performance.now());
738
+ const event = toPauseEvent(params, performance.now(), scripts);
681
739
  if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
682
740
  pauseBuffer.shift();
683
741
  }
@@ -711,6 +769,7 @@ init_types();
711
769
  // src/cli/commandTypes.ts
712
770
  var DEFAULT_BREAKPOINT_TIMEOUT_SEC = 30;
713
771
  var DEFAULT_CF_TIMEOUT_SEC = 60;
772
+ var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
714
773
 
715
774
  // src/cli/target.ts
716
775
  function parsePositiveInt(raw, label) {
@@ -813,6 +872,9 @@ init_types();
813
872
  async function resume(session) {
814
873
  await session.client.send("Debugger.resume");
815
874
  }
875
+ async function setPauseOnExceptions(session, state) {
876
+ await session.client.send("Debugger.setPauseOnExceptions", { state });
877
+ }
816
878
  async function evaluateOnFrame(session, callFrameId, expression) {
817
879
  return await session.client.send("Debugger.evaluateOnCallFrame", {
818
880
  callFrameId,
@@ -900,23 +962,9 @@ function writeHumanEvalResult(result) {
900
962
  `);
901
963
  }
902
964
 
903
- // src/cli/commands/listScripts.ts
904
- import process4 from "process";
905
- async function handleListScripts(opts) {
906
- const target = resolveTarget(opts);
907
- const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
908
- if (opts.json) {
909
- writeJson(scripts);
910
- return;
911
- }
912
- for (const script of scripts) {
913
- process4.stdout.write(`${script.scriptId} ${script.url}
914
- `);
915
- }
916
- }
917
-
918
- // src/cli/commands/log.ts
919
- import process6 from "process";
965
+ // src/cli/commands/exception.ts
966
+ import { performance as performance3 } from "perf_hooks";
967
+ import process5 from "process";
920
968
 
921
969
  // src/pathMapper.ts
922
970
  init_types();
@@ -1059,6 +1107,46 @@ function buildBreakpointUrlRegex(input) {
1059
1107
 
1060
1108
  // src/inspector/breakpoints.ts
1061
1109
  init_types();
1110
+ var HITS_GLOBAL = "globalThis.__CFI_HITS";
1111
+ var counterKeyCounter = 0;
1112
+ function nextCounterKey(file, line) {
1113
+ counterKeyCounter += 1;
1114
+ return `${file}:${line.toString()}:${counterKeyCounter.toString()}`;
1115
+ }
1116
+ function validateHitCount(hitCount) {
1117
+ if (hitCount === void 0) {
1118
+ return void 0;
1119
+ }
1120
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
1121
+ throw new CfInspectorError(
1122
+ "INVALID_HIT_COUNT",
1123
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
1124
+ );
1125
+ }
1126
+ return hitCount;
1127
+ }
1128
+ function buildHitCountedCondition(hitCount, counterKey, userCondition) {
1129
+ const keyLiteral = JSON.stringify(counterKey);
1130
+ const baseCondition = userCondition !== void 0 && userCondition.trim().length > 0 ? `(${userCondition})` : "true";
1131
+ return [
1132
+ "(function(){",
1133
+ `var m=(${HITS_GLOBAL}=${HITS_GLOBAL}||{});`,
1134
+ `var k=${keyLiteral};`,
1135
+ "m[k]=(m[k]||0)+1;",
1136
+ `if(m[k]<${hitCount.toString()})return false;`,
1137
+ `return ${baseCondition};`,
1138
+ "})()"
1139
+ ].join("");
1140
+ }
1141
+ function resolveCondition(input) {
1142
+ const condition = input.condition;
1143
+ const hitCount = validateHitCount(input.hitCount);
1144
+ if (hitCount === void 0) {
1145
+ return condition !== void 0 && condition.length > 0 ? condition : void 0;
1146
+ }
1147
+ const counterKey = nextCounterKey(input.file, input.line);
1148
+ return buildHitCountedCondition(hitCount, counterKey, condition);
1149
+ }
1062
1150
  async function setBreakpoint(session, input) {
1063
1151
  const remoteRoot = input.remoteRoot ?? { kind: "none" };
1064
1152
  const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
@@ -1066,8 +1154,9 @@ async function setBreakpoint(session, input) {
1066
1154
  lineNumber: input.line - 1,
1067
1155
  urlRegex
1068
1156
  };
1069
- if (input.condition !== void 0 && input.condition.length > 0) {
1070
- params["condition"] = input.condition;
1157
+ const condition = resolveCondition(input);
1158
+ if (condition !== void 0) {
1159
+ params["condition"] = condition;
1071
1160
  }
1072
1161
  const result = await session.client.send(
1073
1162
  "Debugger.setBreakpointByUrl",
@@ -1092,378 +1181,130 @@ async function removeBreakpoint(session, breakpointId) {
1092
1181
  await session.client.send("Debugger.removeBreakpoint", { breakpointId });
1093
1182
  }
1094
1183
 
1095
- // src/logpoint/condition.ts
1096
- import { randomBytes } from "crypto";
1097
- var SENTINEL_PREFIX = "__CFI_LOG_";
1098
- var SENTINEL_SUFFIX = "__";
1099
- function buildLogpointCondition(sentinel, expression) {
1100
- return [
1101
- "(function(){",
1102
- `var s=${JSON.stringify(sentinel)};`,
1103
- "try{",
1104
- `var v=(${expression});`,
1105
- "var r=typeof v==='string'?v:JSON.stringify(v);",
1106
- "console.log(s, r);",
1107
- "}catch(e){",
1108
- "console.log(s, '!err:'+(e&&e.message?e.message:String(e)));",
1109
- "}",
1110
- "return false;",
1111
- "})()"
1112
- ].join("");
1184
+ // src/inspector/pause.ts
1185
+ init_types();
1186
+ import { performance as performance2 } from "perf_hooks";
1187
+ function pauseMatches(pause, breakpointIds, pauseReasons) {
1188
+ if (pauseReasons !== void 0 && pauseReasons.length > 0) {
1189
+ return pauseReasons.includes(pause.reason);
1190
+ }
1191
+ if (breakpointIds === void 0 || breakpointIds.length === 0) {
1192
+ return true;
1193
+ }
1194
+ return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
1113
1195
  }
1114
- function generateSentinel() {
1115
- return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1196
+ function remainingUntil(deadlineMs) {
1197
+ return Math.max(0, deadlineMs - performance2.now());
1116
1198
  }
1117
-
1118
- // src/logpoint/events.ts
1119
- function asString2(value) {
1120
- return typeof value === "string" ? value : void 0;
1199
+ function hasResumedSincePause(session, pause) {
1200
+ const pauseAt = pause.receivedAtMs;
1201
+ const resumedAt = session.debuggerState.lastResumedAtMs;
1202
+ return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
1121
1203
  }
1122
- function readArg(arg, index) {
1123
- if (typeof arg !== "object" || arg === null) {
1124
- return void 0;
1204
+ function throwBreakpointTimeout(timeoutMs) {
1205
+ throw new CfInspectorError(
1206
+ "BREAKPOINT_NOT_HIT",
1207
+ `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
1208
+ );
1209
+ }
1210
+ function throwUnrelatedPauseTimeout(pause, timeoutMs) {
1211
+ throw new CfInspectorError(
1212
+ "UNRELATED_PAUSE_TIMEOUT",
1213
+ `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
1214
+ pauseDetail(pause)
1215
+ );
1216
+ }
1217
+ async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
1218
+ if (hasResumedSincePause(session, pause)) {
1219
+ return;
1125
1220
  }
1126
- const candidate = arg;
1127
- if (candidate.type === "string" && typeof candidate.value === "string") {
1128
- return candidate.value;
1221
+ const remainingMs = remainingUntil(deadlineMs);
1222
+ if (remainingMs <= 0) {
1223
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
1129
1224
  }
1130
- const isPrimitiveType = candidate.type === "number" || candidate.type === "boolean" || candidate.type === "bigint";
1131
- const isPrimitiveValue = typeof candidate.value === "number" || typeof candidate.value === "boolean" || typeof candidate.value === "bigint";
1132
- if (isPrimitiveType && isPrimitiveValue) {
1133
- return String(candidate.value);
1225
+ try {
1226
+ await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
1227
+ session.debuggerState.lastResumedAtMs = performance2.now();
1228
+ } catch (err) {
1229
+ if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
1230
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
1231
+ }
1232
+ throw err;
1134
1233
  }
1135
- return index === 0 ? void 0 : "";
1136
1234
  }
1137
- function parseLogEvent(rawArgs, sentinel, location, timestamp) {
1138
- if (!Array.isArray(rawArgs) || rawArgs.length < 2) {
1139
- return void 0;
1140
- }
1141
- const tag = readArg(rawArgs[0], 0);
1142
- if (tag !== sentinel) {
1143
- return void 0;
1235
+ async function handleUnmatchedPause(session, pause, options, deadlineMs) {
1236
+ if (options.unmatchedPausePolicy === "fail") {
1237
+ throw new CfInspectorError(
1238
+ "UNRELATED_PAUSE",
1239
+ "Target paused before this command's breakpoint was reached",
1240
+ pauseDetail(pause)
1241
+ );
1144
1242
  }
1145
- const payload = readArg(rawArgs[1], 1) ?? "";
1146
- const ts = new Date(typeof timestamp === "number" ? timestamp : Date.now()).toISOString();
1147
- const at = `${location.file}:${location.line.toString()}`;
1148
- if (payload.startsWith("!err:")) {
1149
- return { ts, at, error: payload.slice("!err:".length) };
1243
+ if (hasResumedSincePause(session, pause)) {
1244
+ return;
1150
1245
  }
1151
- return parsePayload(ts, at, payload);
1246
+ options.onUnmatchedPause?.(pause);
1247
+ await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
1152
1248
  }
1153
- function parsePayload(ts, at, payload) {
1154
- try {
1155
- const parsed = JSON.parse(payload);
1156
- if (typeof parsed === "string") {
1157
- return { ts, at, value: parsed };
1249
+ async function waitForPause(session, options) {
1250
+ const deadlineMs = performance2.now() + options.timeoutMs;
1251
+ const buffer = session.pauseBuffer;
1252
+ while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
1253
+ while (buffer.length > 0) {
1254
+ const buffered = buffer.shift();
1255
+ if (buffered === void 0) {
1256
+ continue;
1257
+ }
1258
+ if (pauseMatches(buffered, options.breakpointIds, options.pauseReasons)) {
1259
+ return buffered;
1260
+ }
1261
+ await handleUnmatchedPause(session, buffered, options, deadlineMs);
1158
1262
  }
1159
- return { ts, at, value: JSON.stringify(parsed) };
1160
- } catch {
1161
- return { ts, at, value: payload, raw: payload };
1263
+ const pause = await waitForLivePause(session, options, deadlineMs);
1264
+ if (pauseMatches(pause, options.breakpointIds, options.pauseReasons)) {
1265
+ return pause;
1266
+ }
1267
+ await handleUnmatchedPause(session, pause, options, deadlineMs);
1162
1268
  }
1269
+ throwBreakpointTimeout(options.timeoutMs);
1163
1270
  }
1164
-
1165
- // src/logpoint/stream.ts
1166
- async function streamLogpoint(session, options) {
1167
- const sentinel = generateSentinel();
1168
- const condition = buildLogpointCondition(sentinel, options.expression);
1169
- let emitted = 0;
1170
- const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1171
- const event = toLogpointEvent(raw, sentinel, options.location);
1172
- if (event === void 0) {
1173
- return;
1174
- }
1175
- emitted += 1;
1176
- options.onEvent(event);
1177
- });
1178
- let handle;
1179
- try {
1180
- handle = await setBreakpoint(session, {
1181
- file: options.location.file,
1182
- line: options.location.line,
1183
- ...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
1184
- condition
1185
- });
1186
- } catch (err) {
1187
- offEvent();
1188
- throw err;
1271
+ async function waitForLivePause(session, options, deadlineMs) {
1272
+ const remainingMs = remainingUntil(deadlineMs);
1273
+ if (remainingMs <= 0) {
1274
+ throwBreakpointTimeout(options.timeoutMs);
1189
1275
  }
1190
- options.onBreakpointSet?.(handle);
1276
+ session.pauseWaitGate.active = true;
1277
+ let receivedAtMs;
1278
+ let params;
1191
1279
  try {
1192
- const reason = await waitForStop(session, options);
1193
- return { handle, sentinel, emitted, stoppedReason: reason };
1280
+ params = await session.client.waitFor("Debugger.paused", {
1281
+ timeoutMs: remainingMs,
1282
+ predicate: () => {
1283
+ receivedAtMs = performance2.now();
1284
+ return true;
1285
+ }
1286
+ });
1194
1287
  } finally {
1195
- offEvent();
1196
- await removeBreakpointBestEffort(session, handle.breakpointId);
1288
+ session.pauseWaitGate.active = false;
1197
1289
  }
1290
+ return toPauseEvent(params, receivedAtMs ?? performance2.now(), session.scripts);
1198
1291
  }
1199
- function toLogpointEvent(raw, sentinel, location) {
1200
- const params = raw;
1201
- if (asString2(params.type) !== "log") {
1202
- return void 0;
1203
- }
1204
- const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
1205
- return parseLogEvent(params.args, sentinel, location, ts);
1292
+
1293
+ // src/snapshot/values.ts
1294
+ init_types();
1295
+ var DEFAULT_MAX_VALUE_LENGTH = 4096;
1296
+ function isPrimitive(value) {
1297
+ const t = typeof value;
1298
+ return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
1206
1299
  }
1207
- async function removeBreakpointBestEffort(session, breakpointId) {
1208
- try {
1209
- await removeBreakpoint(session, breakpointId);
1210
- } catch {
1300
+ function formatPrimitive(value) {
1301
+ if (typeof value === "symbol") {
1302
+ return value.toString();
1211
1303
  }
1212
- }
1213
- async function waitForStop(session, options) {
1214
- return await new Promise((resolve) => {
1215
- let settled = false;
1216
- const finish = (reason) => {
1217
- if (settled) {
1218
- return;
1219
- }
1220
- settled = true;
1221
- cleanup();
1222
- resolve(reason);
1223
- };
1224
- const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
1225
- finish("duration");
1226
- }, options.durationMs);
1227
- const offClose = session.client.onClose(() => {
1228
- finish("transport-closed");
1229
- });
1230
- const onAbort = () => {
1231
- finish("signal");
1232
- };
1233
- options.signal?.addEventListener("abort", onAbort, { once: true });
1234
- if (options.signal?.aborted === true) {
1235
- finish("signal");
1236
- }
1237
- function cleanup() {
1238
- if (timer !== void 0) {
1239
- clearTimeout(timer);
1240
- }
1241
- offClose();
1242
- options.signal?.removeEventListener("abort", onAbort);
1243
- }
1244
- });
1245
- }
1246
-
1247
- // src/cli/commands/log.ts
1248
- init_types();
1249
-
1250
- // src/cli/warnings.ts
1251
- import process5 from "process";
1252
- function warnOnUnboundBreakpoints(handles) {
1253
- for (const handle of handles) {
1254
- if (handle.resolvedLocations.length === 0) {
1255
- process5.stderr.write(
1256
- `[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.
1257
- `
1258
- );
1259
- }
1260
- }
1261
- }
1262
- function roundDurationMs(durationMs) {
1263
- return Math.round(durationMs * 1e3) / 1e3;
1264
- }
1265
- function warnOnUnmatchedPause(pause) {
1266
- const reason = pause.reason.length > 0 ? pause.reason : "unknown";
1267
- process5.stderr.write(
1268
- `[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
1269
- `
1270
- );
1271
- }
1272
- function withPausedDuration(snapshot, pausedDurationMs) {
1273
- return {
1274
- reason: snapshot.reason,
1275
- hitBreakpoints: snapshot.hitBreakpoints,
1276
- capturedAt: snapshot.capturedAt,
1277
- pausedDurationMs,
1278
- ...snapshot.topFrame === void 0 ? {} : { topFrame: snapshot.topFrame },
1279
- captures: snapshot.captures
1280
- };
1281
- }
1282
- function formatPauseLocation(pause) {
1283
- const top = pause.callFrames[0];
1284
- if (top === void 0) {
1285
- return "(no call frame)";
1286
- }
1287
- const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
1288
- return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
1289
- }
1290
-
1291
- // src/cli/commands/log.ts
1292
- async function handleLog(opts) {
1293
- const target = resolveTarget(opts);
1294
- const location = parseBreakpointSpec(opts.at);
1295
- const remoteRoot = parseRemoteRoot(opts.remoteRoot);
1296
- const durationSec = parsePositiveInt(opts.duration, "--duration");
1297
- const expression = opts.expr.trim();
1298
- if (expression.length === 0) {
1299
- throw new CfInspectorError("INVALID_BREAKPOINT", "--expr must not be empty");
1300
- }
1301
- const abort = new AbortController();
1302
- const onSig = () => {
1303
- abort.abort();
1304
- };
1305
- process6.once("SIGINT", onSig);
1306
- process6.once("SIGTERM", onSig);
1307
- try {
1308
- await withSession(target, async (session) => {
1309
- await validateExpression(session, expression);
1310
- const result = await streamLogpoint(session, {
1311
- location,
1312
- expression,
1313
- remoteRoot,
1314
- ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
1315
- signal: abort.signal,
1316
- onEvent: (event) => {
1317
- writeLogEvent(event, opts.json);
1318
- },
1319
- onBreakpointSet: (handle) => {
1320
- warnOnUnboundBreakpoints([handle]);
1321
- }
1322
- });
1323
- writeLogSummary(result.stoppedReason, result.emitted, opts.json);
1324
- });
1325
- } finally {
1326
- process6.off("SIGINT", onSig);
1327
- process6.off("SIGTERM", onSig);
1328
- }
1329
- }
1330
- function writeLogSummary(stoppedReason, emitted, json) {
1331
- if (json) {
1332
- process6.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
1333
- `);
1334
- return;
1335
- }
1336
- process6.stderr.write(
1337
- `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
1338
- `
1339
- );
1340
- }
1341
-
1342
- // src/cli/commands/snapshot.ts
1343
- import { performance as performance3 } from "perf_hooks";
1344
- import process7 from "process";
1345
-
1346
- // src/inspector/pause.ts
1347
- init_types();
1348
- import { performance as performance2 } from "perf_hooks";
1349
- function pauseMatches(pause, breakpointIds) {
1350
- if (breakpointIds === void 0 || breakpointIds.length === 0) {
1351
- return true;
1352
- }
1353
- return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
1354
- }
1355
- function remainingUntil(deadlineMs) {
1356
- return Math.max(0, deadlineMs - performance2.now());
1357
- }
1358
- function hasResumedSincePause(session, pause) {
1359
- const pauseAt = pause.receivedAtMs;
1360
- const resumedAt = session.debuggerState.lastResumedAtMs;
1361
- return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
1362
- }
1363
- function throwBreakpointTimeout(timeoutMs) {
1364
- throw new CfInspectorError(
1365
- "BREAKPOINT_NOT_HIT",
1366
- `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
1367
- );
1368
- }
1369
- function throwUnrelatedPauseTimeout(pause, timeoutMs) {
1370
- throw new CfInspectorError(
1371
- "UNRELATED_PAUSE_TIMEOUT",
1372
- `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
1373
- pauseDetail(pause)
1374
- );
1375
- }
1376
- async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
1377
- if (hasResumedSincePause(session, pause)) {
1378
- return;
1379
- }
1380
- const remainingMs = remainingUntil(deadlineMs);
1381
- if (remainingMs <= 0) {
1382
- throwUnrelatedPauseTimeout(pause, timeoutMs);
1383
- }
1384
- try {
1385
- await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
1386
- session.debuggerState.lastResumedAtMs = performance2.now();
1387
- } catch (err) {
1388
- if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
1389
- throwUnrelatedPauseTimeout(pause, timeoutMs);
1390
- }
1391
- throw err;
1392
- }
1393
- }
1394
- async function handleUnmatchedPause(session, pause, options, deadlineMs) {
1395
- if (options.unmatchedPausePolicy === "fail") {
1396
- throw new CfInspectorError(
1397
- "UNRELATED_PAUSE",
1398
- "Target paused before this command's breakpoint was reached",
1399
- pauseDetail(pause)
1400
- );
1401
- }
1402
- if (hasResumedSincePause(session, pause)) {
1403
- return;
1404
- }
1405
- options.onUnmatchedPause?.(pause);
1406
- await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
1407
- }
1408
- async function waitForPause(session, options) {
1409
- const deadlineMs = performance2.now() + options.timeoutMs;
1410
- const buffer = session.pauseBuffer;
1411
- while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
1412
- while (buffer.length > 0) {
1413
- const buffered = buffer.shift();
1414
- if (buffered === void 0) {
1415
- continue;
1416
- }
1417
- if (pauseMatches(buffered, options.breakpointIds)) {
1418
- return buffered;
1419
- }
1420
- await handleUnmatchedPause(session, buffered, options, deadlineMs);
1421
- }
1422
- const pause = await waitForLivePause(session, options, deadlineMs);
1423
- if (pauseMatches(pause, options.breakpointIds)) {
1424
- return pause;
1425
- }
1426
- await handleUnmatchedPause(session, pause, options, deadlineMs);
1427
- }
1428
- throwBreakpointTimeout(options.timeoutMs);
1429
- }
1430
- async function waitForLivePause(session, options, deadlineMs) {
1431
- const remainingMs = remainingUntil(deadlineMs);
1432
- if (remainingMs <= 0) {
1433
- throwBreakpointTimeout(options.timeoutMs);
1434
- }
1435
- session.pauseWaitGate.active = true;
1436
- let receivedAtMs;
1437
- let params;
1438
- try {
1439
- params = await session.client.waitFor("Debugger.paused", {
1440
- timeoutMs: remainingMs,
1441
- predicate: () => {
1442
- receivedAtMs = performance2.now();
1443
- return true;
1444
- }
1445
- });
1446
- } finally {
1447
- session.pauseWaitGate.active = false;
1448
- }
1449
- return toPauseEvent(params, receivedAtMs ?? performance2.now());
1450
- }
1451
-
1452
- // src/snapshot/values.ts
1453
- init_types();
1454
- var DEFAULT_MAX_VALUE_LENGTH = 4096;
1455
- function isPrimitive(value) {
1456
- const t = typeof value;
1457
- return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
1458
- }
1459
- function formatPrimitive(value) {
1460
- if (typeof value === "symbol") {
1461
- return value.toString();
1462
- }
1463
- if (typeof value === "bigint") {
1464
- return `${value.toString()}n`;
1465
- }
1466
- return String(value);
1304
+ if (typeof value === "bigint") {
1305
+ return `${value.toString()}n`;
1306
+ }
1307
+ return String(value);
1467
1308
  }
1468
1309
  function resolveMaxValueLength(value) {
1469
1310
  if (value === void 0) {
@@ -1665,19 +1506,11 @@ async function capturePropertyChildren(session, described, depth, maxValueLength
1665
1506
  }
1666
1507
  }
1667
1508
 
1668
- // src/snapshot/objects.ts
1669
- function objectIdFromEvalResult(result) {
1670
- const inner = result.result;
1671
- if (inner?.type !== "object") {
1672
- return void 0;
1673
- }
1674
- const objectId = inner.objectId;
1675
- if (typeof objectId !== "string" || objectId.length === 0) {
1676
- return void 0;
1677
- }
1678
- return objectId;
1509
+ // src/snapshot/exception.ts
1510
+ function asString2(value) {
1511
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1679
1512
  }
1680
- async function renderObjectCapture(session, objectId, maxValueLength) {
1513
+ async function materializeObject(session, objectId, maxValueLength) {
1681
1514
  try {
1682
1515
  const properties = await captureProperties(
1683
1516
  session,
@@ -1686,6 +1519,9 @@ async function renderObjectCapture(session, objectId, maxValueLength) {
1686
1519
  MAX_VARIABLE_DEPTH,
1687
1520
  maxValueLength
1688
1521
  );
1522
+ if (properties.length === 0) {
1523
+ return void 0;
1524
+ }
1689
1525
  const structured = {};
1690
1526
  for (const variable of properties) {
1691
1527
  structured[variable.name] = toStructuredValue(variable);
@@ -1695,7 +1531,99 @@ async function renderObjectCapture(session, objectId, maxValueLength) {
1695
1531
  return void 0;
1696
1532
  }
1697
1533
  }
1698
- function normalizeRenderedObjectCapture(rendered, original) {
1534
+ async function readPropertyDescription(session, objectId, name) {
1535
+ try {
1536
+ const properties = await getProperties(session, objectId);
1537
+ for (const prop of properties) {
1538
+ if (prop.name !== name) {
1539
+ continue;
1540
+ }
1541
+ const value = prop.value;
1542
+ if (value === void 0) {
1543
+ continue;
1544
+ }
1545
+ if (typeof value.value === "string") {
1546
+ return value.value;
1547
+ }
1548
+ if (typeof value.description === "string") {
1549
+ return value.description;
1550
+ }
1551
+ }
1552
+ return void 0;
1553
+ } catch {
1554
+ return void 0;
1555
+ }
1556
+ }
1557
+ async function captureException(session, pause, maxValueLength) {
1558
+ if (pause.reason !== "exception" && pause.reason !== "promiseRejection") {
1559
+ return void 0;
1560
+ }
1561
+ const data = pause.data;
1562
+ if (typeof data !== "object" || data === null) {
1563
+ return { error: "no exception data attached" };
1564
+ }
1565
+ const candidate = data;
1566
+ const type = asString2(candidate.type);
1567
+ const description = asString2(candidate.description);
1568
+ if (typeof candidate.value === "string") {
1569
+ return buildResult(type, description, JSON.stringify(candidate.value), maxValueLength);
1570
+ }
1571
+ if (typeof candidate.value === "number" || typeof candidate.value === "boolean") {
1572
+ return buildResult(type, description, String(candidate.value), maxValueLength);
1573
+ }
1574
+ const objectId = asString2(candidate.objectId);
1575
+ if (objectId === void 0) {
1576
+ if (description !== void 0) {
1577
+ return buildResult(type, description, description, maxValueLength);
1578
+ }
1579
+ return { error: "exception data has no objectId or value" };
1580
+ }
1581
+ const message = await readPropertyDescription(session, objectId, "message");
1582
+ const rendered = await materializeObject(session, objectId, maxValueLength);
1583
+ if (rendered !== void 0) {
1584
+ const result = buildResult(type, description, rendered, maxValueLength);
1585
+ return message === void 0 ? result : { ...result, description: limitValueLength(message, maxValueLength) };
1586
+ }
1587
+ return buildResult(type, description, description ?? "[exception]", maxValueLength);
1588
+ }
1589
+ function buildResult(type, description, value, maxValueLength) {
1590
+ const safeValue = limitValueLength(value, maxValueLength);
1591
+ const base = { value: safeValue };
1592
+ const withType = type === void 0 ? base : { ...base, type };
1593
+ return description === void 0 ? withType : { ...withType, description: limitValueLength(description, maxValueLength) };
1594
+ }
1595
+
1596
+ // src/snapshot/objects.ts
1597
+ function objectIdFromEvalResult(result) {
1598
+ const inner = result.result;
1599
+ if (inner?.type !== "object") {
1600
+ return void 0;
1601
+ }
1602
+ const objectId = inner.objectId;
1603
+ if (typeof objectId !== "string" || objectId.length === 0) {
1604
+ return void 0;
1605
+ }
1606
+ return objectId;
1607
+ }
1608
+ async function renderObjectCapture(session, objectId, maxValueLength) {
1609
+ try {
1610
+ const properties = await captureProperties(
1611
+ session,
1612
+ objectId,
1613
+ MAX_SCOPE_VARIABLES,
1614
+ MAX_VARIABLE_DEPTH,
1615
+ maxValueLength
1616
+ );
1617
+ const structured = {};
1618
+ for (const variable of properties) {
1619
+ structured[variable.name] = toStructuredValue(variable);
1620
+ }
1621
+ return JSON.stringify(structured);
1622
+ } catch {
1623
+ return void 0;
1624
+ }
1625
+ }
1626
+ function normalizeRenderedObjectCapture(rendered, original) {
1699
1627
  if (rendered === "{}" && original !== "Object") {
1700
1628
  return void 0;
1701
1629
  }
@@ -1767,12 +1695,73 @@ async function captureScopes(session, frame, maxValueLength) {
1767
1695
  );
1768
1696
  }
1769
1697
 
1698
+ // src/snapshot/stack.ts
1699
+ var DEFAULT_STACK_DEPTH = 1;
1700
+ var MAX_STACK_DEPTH = 64;
1701
+ function clampDepth(depth, frameCount) {
1702
+ if (depth <= 0) {
1703
+ return 0;
1704
+ }
1705
+ return Math.min(depth, frameCount, MAX_STACK_DEPTH);
1706
+ }
1707
+ function buildBaseFrame(frame) {
1708
+ const base = {
1709
+ functionName: frame.functionName,
1710
+ line: frame.lineNumber + 1,
1711
+ column: frame.columnNumber + 1
1712
+ };
1713
+ return frame.url === void 0 ? base : { ...base, url: frame.url };
1714
+ }
1715
+ async function captureFrameExpression(session, callFrameId, expression, maxValueLength) {
1716
+ try {
1717
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1718
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1719
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1720
+ } catch (err) {
1721
+ const message = err instanceof Error ? err.message : String(err);
1722
+ return { expression, error: limitValueLength(message, maxValueLength) };
1723
+ }
1724
+ }
1725
+ async function captureFrameExpressions(session, frame, expressions, maxValueLength) {
1726
+ if (expressions.length === 0) {
1727
+ return [];
1728
+ }
1729
+ return await Promise.all(
1730
+ expressions.map(
1731
+ (expression) => captureFrameExpression(session, frame.callFrameId, expression, maxValueLength)
1732
+ )
1733
+ );
1734
+ }
1735
+ async function walkStack(session, callFrames, options) {
1736
+ const depth = clampDepth(options.stackDepth, callFrames.length);
1737
+ if (depth <= 1) {
1738
+ return [];
1739
+ }
1740
+ const slice = callFrames.slice(0, depth);
1741
+ return await Promise.all(
1742
+ slice.map(async (frame) => {
1743
+ const base = buildBaseFrame(frame);
1744
+ if (options.stackCaptures.length === 0) {
1745
+ return base;
1746
+ }
1747
+ const captures = await captureFrameExpressions(
1748
+ session,
1749
+ frame,
1750
+ options.stackCaptures,
1751
+ options.maxValueLength
1752
+ );
1753
+ return { ...base, captures };
1754
+ })
1755
+ );
1756
+ }
1757
+
1770
1758
  // src/snapshot/capture.ts
1771
1759
  async function captureSnapshot(session, pause, options = {}) {
1772
1760
  const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1773
1761
  const top = pause.callFrames[0];
1774
1762
  let topFrame;
1775
1763
  let captures = [];
1764
+ let stack = [];
1776
1765
  if (top) {
1777
1766
  topFrame = {
1778
1767
  functionName: top.functionName,
@@ -1785,14 +1774,31 @@ async function captureSnapshot(session, pause, options = {}) {
1785
1774
  topFrame = { ...topFrame, scopes };
1786
1775
  }
1787
1776
  captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
1777
+ stack = await walkStack(session, pause.callFrames, {
1778
+ stackDepth: options.stackDepth ?? DEFAULT_STACK_DEPTH,
1779
+ stackCaptures: options.stackCaptures ?? [],
1780
+ maxValueLength
1781
+ });
1788
1782
  }
1789
- return {
1790
- reason: pause.reason,
1791
- hitBreakpoints: pause.hitBreakpoints,
1783
+ const exception = await captureException(session, pause, maxValueLength);
1784
+ return buildResult2({
1785
+ pause,
1786
+ topFrame,
1787
+ captures,
1788
+ stack,
1789
+ exception
1790
+ });
1791
+ }
1792
+ function buildResult2(input) {
1793
+ const base = {
1794
+ reason: input.pause.reason,
1795
+ hitBreakpoints: input.pause.hitBreakpoints,
1792
1796
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1793
- ...topFrame === void 0 ? {} : { topFrame },
1794
- captures
1797
+ captures: input.captures
1795
1798
  };
1799
+ const withFrame = input.topFrame === void 0 ? base : { ...base, topFrame: input.topFrame };
1800
+ const withStack = input.stack.length > 0 ? { ...withFrame, stack: input.stack } : withFrame;
1801
+ return input.exception === void 0 ? withStack : { ...withStack, exception: input.exception };
1796
1802
  }
1797
1803
  async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1798
1804
  if (captures === void 0 || captures.length === 0) {
@@ -1815,7 +1821,7 @@ async function captureExpression(session, callFrameId, expression, maxValueLengt
1815
1821
  }
1816
1822
  }
1817
1823
 
1818
- // src/cli/commands/snapshot.ts
1824
+ // src/cli/commands/exception.ts
1819
1825
  init_types();
1820
1826
 
1821
1827
  // src/cli/captureParser.ts
@@ -1898,7 +1904,450 @@ function splitCaptureExpressions(raw) {
1898
1904
  return state.pieces;
1899
1905
  }
1900
1906
 
1907
+ // src/cli/warnings.ts
1908
+ import process4 from "process";
1909
+ function warnOnUnboundBreakpoints(handles) {
1910
+ for (const handle of handles) {
1911
+ if (handle.resolvedLocations.length === 0) {
1912
+ process4.stderr.write(
1913
+ `[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.
1914
+ `
1915
+ );
1916
+ }
1917
+ }
1918
+ }
1919
+ function roundDurationMs(durationMs) {
1920
+ return Math.round(durationMs * 1e3) / 1e3;
1921
+ }
1922
+ function warnOnUnmatchedPause(pause) {
1923
+ const reason = pause.reason.length > 0 ? pause.reason : "unknown";
1924
+ process4.stderr.write(
1925
+ `[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
1926
+ `
1927
+ );
1928
+ }
1929
+ function withPausedDuration(snapshot, pausedDurationMs) {
1930
+ const base = {
1931
+ reason: snapshot.reason,
1932
+ hitBreakpoints: snapshot.hitBreakpoints,
1933
+ capturedAt: snapshot.capturedAt,
1934
+ pausedDurationMs,
1935
+ captures: snapshot.captures
1936
+ };
1937
+ const withFrame = snapshot.topFrame === void 0 ? base : { ...base, topFrame: snapshot.topFrame };
1938
+ const withStack = snapshot.stack === void 0 ? withFrame : { ...withFrame, stack: snapshot.stack };
1939
+ return snapshot.exception === void 0 ? withStack : { ...withStack, exception: snapshot.exception };
1940
+ }
1941
+ function formatPauseLocation(pause) {
1942
+ const top = pause.callFrames[0];
1943
+ if (top === void 0) {
1944
+ return "(no call frame)";
1945
+ }
1946
+ const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
1947
+ return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
1948
+ }
1949
+
1950
+ // src/cli/commands/exception.ts
1951
+ var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
1952
+ async function handleException(opts) {
1953
+ const prepared = prepareExceptionCommand(opts);
1954
+ const result = await runExceptionCommand(prepared, opts);
1955
+ if (opts.json) {
1956
+ writeJson(result);
1957
+ } else {
1958
+ writeHumanSnapshot(result);
1959
+ }
1960
+ }
1961
+ function prepareExceptionCommand(opts) {
1962
+ const target = resolveTarget(opts);
1963
+ const stateRaw = (opts.type ?? "uncaught").trim().toLowerCase();
1964
+ if (!VALID_PAUSE_TYPES.includes(stateRaw)) {
1965
+ throw new CfInspectorError(
1966
+ "INVALID_PAUSE_TYPE",
1967
+ `--type must be one of ${VALID_PAUSE_TYPES.join(", ")} (received "${stateRaw}")`
1968
+ );
1969
+ }
1970
+ const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_EXCEPTION_TIMEOUT_SEC;
1971
+ const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
1972
+ const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
1973
+ return {
1974
+ target,
1975
+ state: stateRaw,
1976
+ captures: parseCaptureList(opts.capture),
1977
+ remoteRoot: parseRemoteRoot(opts.remoteRoot),
1978
+ timeoutMs: timeoutSec * 1e3,
1979
+ ...maxValueLength === void 0 ? {} : { maxValueLength },
1980
+ ...stackDepth === void 0 ? {} : { stackDepth },
1981
+ stackCaptures: parseCaptureList(opts.stackCaptures)
1982
+ };
1983
+ }
1984
+ async function runExceptionCommand(command, opts) {
1985
+ return await withSession(command.target, async (session) => {
1986
+ await setPauseOnExceptions(session, command.state);
1987
+ try {
1988
+ const pause = await waitForPause(session, {
1989
+ timeoutMs: command.timeoutMs,
1990
+ pauseReasons: ["exception", "promiseRejection"],
1991
+ unmatchedPausePolicy: "wait-for-resume"
1992
+ });
1993
+ const pausedStartedAt = pause.receivedAtMs ?? performance3.now();
1994
+ const snapshot = await captureSnapshot(session, pause, {
1995
+ captures: command.captures,
1996
+ includeScopes: opts.includeScopes === true,
1997
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength },
1998
+ ...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
1999
+ stackCaptures: command.stackCaptures
2000
+ });
2001
+ if (opts.keepPaused === true) {
2002
+ return withPausedDuration(snapshot, null);
2003
+ }
2004
+ return await resumeAfterException(session, snapshot, pausedStartedAt);
2005
+ } finally {
2006
+ await disablePauseOnExceptionsBestEffort(session);
2007
+ }
2008
+ });
2009
+ }
2010
+ async function resumeAfterException(session, snapshot, pausedStartedAt) {
2011
+ try {
2012
+ await resume(session);
2013
+ return withPausedDuration(snapshot, roundDurationMs(performance3.now() - pausedStartedAt));
2014
+ } catch {
2015
+ process5.stderr.write(
2016
+ "[cf-inspector] warning: Debugger.resume failed after exception capture; pausedDurationMs is unknown.\n"
2017
+ );
2018
+ return withPausedDuration(snapshot, null);
2019
+ }
2020
+ }
2021
+ async function disablePauseOnExceptionsBestEffort(session) {
2022
+ try {
2023
+ await setPauseOnExceptions(session, "none");
2024
+ } catch {
2025
+ }
2026
+ }
2027
+
2028
+ // src/cli/commands/listScripts.ts
2029
+ import process6 from "process";
2030
+ async function handleListScripts(opts) {
2031
+ const target = resolveTarget(opts);
2032
+ const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
2033
+ if (opts.json) {
2034
+ writeJson(scripts);
2035
+ return;
2036
+ }
2037
+ for (const script of scripts) {
2038
+ process6.stdout.write(`${script.scriptId} ${script.url}
2039
+ `);
2040
+ }
2041
+ }
2042
+
2043
+ // src/cli/commands/log.ts
2044
+ import process7 from "process";
2045
+
2046
+ // src/logpoint/stream.ts
2047
+ init_types();
2048
+
2049
+ // src/logpoint/condition.ts
2050
+ import { randomBytes } from "crypto";
2051
+ var SENTINEL_PREFIX = "__CFI_LOG_";
2052
+ var SENTINEL_SUFFIX = "__";
2053
+ var HITS_GLOBAL2 = "globalThis.__CFI_LOG_HITS";
2054
+ function buildLoggingIife(sentinel, expression) {
2055
+ return [
2056
+ "(function(){",
2057
+ `var s=${JSON.stringify(sentinel)};`,
2058
+ "try{",
2059
+ `var v=(${expression});`,
2060
+ "var r=typeof v==='string'?v:JSON.stringify(v);",
2061
+ "console.log(s, r);",
2062
+ "}catch(e){",
2063
+ "console.log(s, '!err:'+(e&&e.message?e.message:String(e)));",
2064
+ "}",
2065
+ "return false;",
2066
+ "})()"
2067
+ ].join("");
2068
+ }
2069
+ function buildHitGate(hitCount, counterKey) {
2070
+ const keyLiteral = JSON.stringify(counterKey);
2071
+ return [
2072
+ "(function(){",
2073
+ `var m=(${HITS_GLOBAL2}=${HITS_GLOBAL2}||{});`,
2074
+ `var k=${keyLiteral};`,
2075
+ "m[k]=(m[k]||0)+1;",
2076
+ `return m[k]>=${hitCount.toString()};`,
2077
+ "})()"
2078
+ ].join("");
2079
+ }
2080
+ function combineGuards(guards) {
2081
+ const filtered = guards.filter((guard) => guard.length > 0);
2082
+ if (filtered.length === 0) {
2083
+ return void 0;
2084
+ }
2085
+ if (filtered.length === 1) {
2086
+ return filtered[0];
2087
+ }
2088
+ return filtered.map((guard) => `(${guard})`).join("&&");
2089
+ }
2090
+ function buildLogpointCondition(sentinel, expression, options = {}) {
2091
+ const guards = [];
2092
+ if (options.hitCount !== void 0) {
2093
+ const counterKey = options.counterKey ?? sentinel;
2094
+ guards.push(buildHitGate(options.hitCount, counterKey));
2095
+ }
2096
+ const userPredicate = options.predicate?.trim();
2097
+ if (userPredicate !== void 0 && userPredicate.length > 0) {
2098
+ guards.push(`(${userPredicate})`);
2099
+ }
2100
+ const iife = buildLoggingIife(sentinel, expression);
2101
+ const guard = combineGuards(guards);
2102
+ if (guard === void 0) {
2103
+ return iife;
2104
+ }
2105
+ return `(${guard})?${iife}:false`;
2106
+ }
2107
+ function generateSentinel() {
2108
+ return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
2109
+ }
2110
+
2111
+ // src/logpoint/events.ts
2112
+ function asString3(value) {
2113
+ return typeof value === "string" ? value : void 0;
2114
+ }
2115
+ function readArg(arg, index) {
2116
+ if (typeof arg !== "object" || arg === null) {
2117
+ return void 0;
2118
+ }
2119
+ const candidate = arg;
2120
+ if (candidate.type === "string" && typeof candidate.value === "string") {
2121
+ return candidate.value;
2122
+ }
2123
+ const isPrimitiveType = candidate.type === "number" || candidate.type === "boolean" || candidate.type === "bigint";
2124
+ const isPrimitiveValue = typeof candidate.value === "number" || typeof candidate.value === "boolean" || typeof candidate.value === "bigint";
2125
+ if (isPrimitiveType && isPrimitiveValue) {
2126
+ return String(candidate.value);
2127
+ }
2128
+ return index === 0 ? void 0 : "";
2129
+ }
2130
+ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
2131
+ if (!Array.isArray(rawArgs) || rawArgs.length < 2) {
2132
+ return void 0;
2133
+ }
2134
+ const tag = readArg(rawArgs[0], 0);
2135
+ if (tag !== sentinel) {
2136
+ return void 0;
2137
+ }
2138
+ const payload = readArg(rawArgs[1], 1) ?? "";
2139
+ const ts = new Date(typeof timestamp === "number" ? timestamp : Date.now()).toISOString();
2140
+ const at = `${location.file}:${location.line.toString()}`;
2141
+ if (payload.startsWith("!err:")) {
2142
+ return { ts, at, error: payload.slice("!err:".length) };
2143
+ }
2144
+ return parsePayload(ts, at, payload);
2145
+ }
2146
+ function parsePayload(ts, at, payload) {
2147
+ try {
2148
+ const parsed = JSON.parse(payload);
2149
+ if (typeof parsed === "string") {
2150
+ return { ts, at, value: parsed };
2151
+ }
2152
+ return { ts, at, value: JSON.stringify(parsed) };
2153
+ } catch {
2154
+ return { ts, at, value: payload, raw: payload };
2155
+ }
2156
+ }
2157
+
2158
+ // src/logpoint/stream.ts
2159
+ function validateMaxEvents(maxEvents) {
2160
+ if (maxEvents === void 0) {
2161
+ return void 0;
2162
+ }
2163
+ if (!Number.isInteger(maxEvents) || maxEvents <= 0) {
2164
+ throw new CfInspectorError(
2165
+ "INVALID_ARGUMENT",
2166
+ `maxEvents must be a positive integer, received: ${maxEvents.toString()}`
2167
+ );
2168
+ }
2169
+ return maxEvents;
2170
+ }
2171
+ function validateHitCount2(hitCount) {
2172
+ if (hitCount === void 0) {
2173
+ return void 0;
2174
+ }
2175
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
2176
+ throw new CfInspectorError(
2177
+ "INVALID_HIT_COUNT",
2178
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
2179
+ );
2180
+ }
2181
+ return hitCount;
2182
+ }
2183
+ async function streamLogpoint(session, options) {
2184
+ const maxEvents = validateMaxEvents(options.maxEvents);
2185
+ const hitCount = validateHitCount2(options.hitCount);
2186
+ const sentinel = generateSentinel();
2187
+ const condition = buildLogpointCondition(sentinel, options.expression, {
2188
+ ...options.condition === void 0 ? {} : { predicate: options.condition },
2189
+ ...hitCount === void 0 ? {} : { hitCount }
2190
+ });
2191
+ let emitted = 0;
2192
+ let maxEventsReached = false;
2193
+ let stopMaxEvents;
2194
+ const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
2195
+ if (maxEventsReached) {
2196
+ return;
2197
+ }
2198
+ const event = toLogpointEvent(raw, sentinel, options.location);
2199
+ if (event === void 0) {
2200
+ return;
2201
+ }
2202
+ emitted += 1;
2203
+ options.onEvent(event);
2204
+ if (maxEvents !== void 0 && emitted >= maxEvents) {
2205
+ maxEventsReached = true;
2206
+ stopMaxEvents?.();
2207
+ }
2208
+ });
2209
+ let handle;
2210
+ try {
2211
+ handle = await setBreakpoint(session, {
2212
+ file: options.location.file,
2213
+ line: options.location.line,
2214
+ ...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
2215
+ condition
2216
+ });
2217
+ } catch (err) {
2218
+ offEvent();
2219
+ throw err;
2220
+ }
2221
+ options.onBreakpointSet?.(handle);
2222
+ try {
2223
+ const reason = await waitForStop(session, options, (signal) => {
2224
+ stopMaxEvents = signal;
2225
+ if (maxEventsReached) {
2226
+ signal();
2227
+ }
2228
+ });
2229
+ return { handle, sentinel, emitted, stoppedReason: reason };
2230
+ } finally {
2231
+ offEvent();
2232
+ await removeBreakpointBestEffort(session, handle.breakpointId);
2233
+ }
2234
+ }
2235
+ function toLogpointEvent(raw, sentinel, location) {
2236
+ const params = raw;
2237
+ if (asString3(params.type) !== "log") {
2238
+ return void 0;
2239
+ }
2240
+ const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
2241
+ return parseLogEvent(params.args, sentinel, location, ts);
2242
+ }
2243
+ async function removeBreakpointBestEffort(session, breakpointId) {
2244
+ try {
2245
+ await removeBreakpoint(session, breakpointId);
2246
+ } catch {
2247
+ }
2248
+ }
2249
+ async function waitForStop(session, options, registerMaxEventsSignal) {
2250
+ return await new Promise((resolve) => {
2251
+ let settled = false;
2252
+ const finish = (reason) => {
2253
+ if (settled) {
2254
+ return;
2255
+ }
2256
+ settled = true;
2257
+ cleanup();
2258
+ resolve(reason);
2259
+ };
2260
+ const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
2261
+ finish("duration");
2262
+ }, options.durationMs);
2263
+ const offClose = session.client.onClose(() => {
2264
+ finish("transport-closed");
2265
+ });
2266
+ const onAbort = () => {
2267
+ finish("signal");
2268
+ };
2269
+ options.signal?.addEventListener("abort", onAbort, { once: true });
2270
+ if (options.signal?.aborted === true) {
2271
+ finish("signal");
2272
+ }
2273
+ registerMaxEventsSignal(() => {
2274
+ finish("max-events");
2275
+ });
2276
+ function cleanup() {
2277
+ if (timer !== void 0) {
2278
+ clearTimeout(timer);
2279
+ }
2280
+ offClose();
2281
+ options.signal?.removeEventListener("abort", onAbort);
2282
+ }
2283
+ });
2284
+ }
2285
+
2286
+ // src/cli/commands/log.ts
2287
+ init_types();
2288
+ async function handleLog(opts) {
2289
+ const target = resolveTarget(opts);
2290
+ const location = parseBreakpointSpec(opts.at);
2291
+ const remoteRoot = parseRemoteRoot(opts.remoteRoot);
2292
+ const durationSec = parsePositiveInt(opts.duration, "--duration");
2293
+ const maxEvents = parsePositiveInt(opts.maxEvents, "--max-events");
2294
+ const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
2295
+ const expression = opts.expr.trim();
2296
+ if (expression.length === 0) {
2297
+ throw new CfInspectorError("INVALID_EXPRESSION", "--expr must not be empty");
2298
+ }
2299
+ const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2300
+ const abort = new AbortController();
2301
+ const onSig = () => {
2302
+ abort.abort();
2303
+ };
2304
+ process7.once("SIGINT", onSig);
2305
+ process7.once("SIGTERM", onSig);
2306
+ try {
2307
+ await withSession(target, async (session) => {
2308
+ await validateExpression(session, expression);
2309
+ if (condition !== void 0) {
2310
+ await validateExpression(session, condition);
2311
+ }
2312
+ const result = await streamLogpoint(session, {
2313
+ location,
2314
+ expression,
2315
+ remoteRoot,
2316
+ ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
2317
+ ...maxEvents === void 0 ? {} : { maxEvents },
2318
+ ...hitCount === void 0 ? {} : { hitCount },
2319
+ ...condition === void 0 ? {} : { condition },
2320
+ signal: abort.signal,
2321
+ onEvent: (event) => {
2322
+ writeLogEvent(event, opts.json);
2323
+ },
2324
+ onBreakpointSet: (handle) => {
2325
+ warnOnUnboundBreakpoints([handle]);
2326
+ }
2327
+ });
2328
+ writeLogSummary(result.stoppedReason, result.emitted, opts.json);
2329
+ });
2330
+ } finally {
2331
+ process7.off("SIGINT", onSig);
2332
+ process7.off("SIGTERM", onSig);
2333
+ }
2334
+ }
2335
+ function writeLogSummary(stoppedReason, emitted, json) {
2336
+ if (json) {
2337
+ process7.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
2338
+ `);
2339
+ return;
2340
+ }
2341
+ process7.stderr.write(
2342
+ `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
2343
+ `
2344
+ );
2345
+ }
2346
+
1901
2347
  // src/cli/commands/snapshot.ts
2348
+ import { performance as performance4 } from "perf_hooks";
2349
+ import process8 from "process";
2350
+ init_types();
1902
2351
  async function handleSnapshot(opts) {
1903
2352
  const prepared = prepareSnapshotCommand(opts);
1904
2353
  const result = await runSnapshotCommand(prepared, opts);
@@ -1919,6 +2368,8 @@ function prepareSnapshotCommand(opts) {
1919
2368
  const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
1920
2369
  const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
1921
2370
  const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2371
+ const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
2372
+ const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
1922
2373
  return {
1923
2374
  target,
1924
2375
  breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
@@ -1926,7 +2377,10 @@ function prepareSnapshotCommand(opts) {
1926
2377
  remoteRoot: parseRemoteRoot(opts.remoteRoot),
1927
2378
  timeoutMs: timeoutSec * 1e3,
1928
2379
  ...condition === void 0 ? {} : { condition },
1929
- ...maxValueLength === void 0 ? {} : { maxValueLength }
2380
+ ...maxValueLength === void 0 ? {} : { maxValueLength },
2381
+ ...hitCount === void 0 ? {} : { hitCount },
2382
+ ...stackDepth === void 0 ? {} : { stackDepth },
2383
+ stackCaptures: parseCaptureList(opts.stackCaptures)
1930
2384
  };
1931
2385
  }
1932
2386
  async function runSnapshotCommand(command, opts) {
@@ -1940,17 +2394,20 @@ async function runSnapshotCommand(command, opts) {
1940
2394
  file: bp.file,
1941
2395
  line: bp.line,
1942
2396
  remoteRoot: command.remoteRoot,
1943
- ...command.condition === void 0 ? {} : { condition: command.condition }
2397
+ ...command.condition === void 0 ? {} : { condition: command.condition },
2398
+ ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
1944
2399
  })
1945
2400
  )
1946
2401
  );
1947
2402
  warnOnUnboundBreakpoints(handles);
1948
2403
  const pause = await waitForCommandPause(session, opts, handles, command.timeoutMs);
1949
- const pausedStartedAt = pause.receivedAtMs ?? performance3.now();
2404
+ const pausedStartedAt = pause.receivedAtMs ?? performance4.now();
1950
2405
  const snapshot = await captureSnapshot(session, pause, {
1951
2406
  captures: command.captures,
1952
2407
  includeScopes: opts.includeScopes === true,
1953
- ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength }
2408
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength },
2409
+ ...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
2410
+ stackCaptures: command.stackCaptures
1954
2411
  });
1955
2412
  if (opts.keepPaused === true) {
1956
2413
  return withPausedDuration(snapshot, null);
@@ -1976,47 +2433,293 @@ async function waitForCommandPause(session, opts, handles, timeoutMs) {
1976
2433
  async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
1977
2434
  try {
1978
2435
  await resume(session);
1979
- return withPausedDuration(snapshot, roundDurationMs(performance3.now() - pausedStartedAt));
2436
+ return withPausedDuration(snapshot, roundDurationMs(performance4.now() - pausedStartedAt));
1980
2437
  } catch {
1981
- process7.stderr.write(
2438
+ process8.stderr.write(
1982
2439
  "[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
1983
2440
  );
1984
2441
  return withPausedDuration(snapshot, null);
1985
2442
  }
1986
2443
  }
1987
2444
 
2445
+ // src/cli/commands/watch.ts
2446
+ import process9 from "process";
2447
+ init_types();
2448
+ async function handleWatch(opts) {
2449
+ const prepared = prepareWatchCommand(opts);
2450
+ const abort = new AbortController();
2451
+ const onSig = () => {
2452
+ abort.abort();
2453
+ };
2454
+ process9.once("SIGINT", onSig);
2455
+ process9.once("SIGTERM", onSig);
2456
+ let stoppedReason = "signal";
2457
+ let emitted = 0;
2458
+ try {
2459
+ await withSession(prepared.target, async (session) => {
2460
+ const result = await runWatchLoop(session, prepared, opts, abort.signal);
2461
+ stoppedReason = result.stoppedReason;
2462
+ emitted = result.emitted;
2463
+ });
2464
+ } finally {
2465
+ process9.off("SIGINT", onSig);
2466
+ process9.off("SIGTERM", onSig);
2467
+ }
2468
+ writeWatchSummary(stoppedReason, emitted, opts.json);
2469
+ }
2470
+ function prepareWatchCommand(opts) {
2471
+ const target = resolveTarget(opts);
2472
+ if (opts.bp.length === 0) {
2473
+ throw new CfInspectorError(
2474
+ "INVALID_BREAKPOINT",
2475
+ "At least one --bp <file:line> is required."
2476
+ );
2477
+ }
2478
+ const perHitTimeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
2479
+ const durationSec = parsePositiveInt(opts.duration, "--duration");
2480
+ const maxEvents = parsePositiveInt(opts.maxEvents, "--max-events");
2481
+ const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
2482
+ const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
2483
+ const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
2484
+ const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
2485
+ return {
2486
+ target,
2487
+ breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
2488
+ captures: parseCaptureList(opts.capture),
2489
+ remoteRoot: parseRemoteRoot(opts.remoteRoot),
2490
+ perHitTimeoutMs: perHitTimeoutSec * 1e3,
2491
+ ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
2492
+ ...maxEvents === void 0 ? {} : { maxEvents },
2493
+ ...maxValueLength === void 0 ? {} : { maxValueLength },
2494
+ ...condition === void 0 ? {} : { condition },
2495
+ ...hitCount === void 0 ? {} : { hitCount },
2496
+ ...stackDepth === void 0 ? {} : { stackDepth },
2497
+ stackCaptures: parseCaptureList(opts.stackCaptures)
2498
+ };
2499
+ }
2500
+ async function runWatchLoop(session, command, opts, signal) {
2501
+ if (command.condition !== void 0) {
2502
+ await validateExpression(session, command.condition);
2503
+ }
2504
+ const handles = await Promise.all(
2505
+ command.breakpoints.map(
2506
+ (bp) => setBreakpoint(session, {
2507
+ file: bp.file,
2508
+ line: bp.line,
2509
+ remoteRoot: command.remoteRoot,
2510
+ ...command.condition === void 0 ? {} : { condition: command.condition },
2511
+ ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
2512
+ })
2513
+ )
2514
+ );
2515
+ warnOnUnboundBreakpoints(handles);
2516
+ const deadline = computeDeadline(command.durationMs);
2517
+ let emitted = 0;
2518
+ const state = { stopped: false, reason: "signal" };
2519
+ const setStop = (reason) => {
2520
+ if (state.stopped) {
2521
+ return;
2522
+ }
2523
+ state.reason = reason;
2524
+ state.stopped = true;
2525
+ };
2526
+ const transportClosed = waitForTransportClose(session);
2527
+ transportClosed.promise.then(() => {
2528
+ setStop("transport-closed");
2529
+ }).catch(() => {
2530
+ });
2531
+ try {
2532
+ while (!state.stopped) {
2533
+ if (signal.aborted) {
2534
+ setStop("signal");
2535
+ break;
2536
+ }
2537
+ const remainingMs = remainingForLoop(deadline, command.perHitTimeoutMs);
2538
+ if (remainingMs <= 0) {
2539
+ setStop("duration");
2540
+ break;
2541
+ }
2542
+ const pause = await waitForNextWatchPause(session, handles, remainingMs, signal);
2543
+ if (pause === "signal") {
2544
+ setStop("signal");
2545
+ break;
2546
+ }
2547
+ if (pause === "timeout") {
2548
+ if (deadline !== void 0 && Date.now() >= deadline) {
2549
+ setStop("duration");
2550
+ break;
2551
+ }
2552
+ continue;
2553
+ }
2554
+ const event = await captureWatchEvent(session, command, pause, emitted + 1, opts);
2555
+ emitted += 1;
2556
+ writeWatchEvent(event, opts.json);
2557
+ try {
2558
+ await resume(session);
2559
+ } catch {
2560
+ process9.stderr.write("[cf-inspector] warning: Debugger.resume failed during watch.\n");
2561
+ setStop("transport-closed");
2562
+ break;
2563
+ }
2564
+ if (command.maxEvents !== void 0 && emitted >= command.maxEvents) {
2565
+ setStop("max-events");
2566
+ break;
2567
+ }
2568
+ }
2569
+ } finally {
2570
+ transportClosed.cancel();
2571
+ }
2572
+ return { emitted, stoppedReason: state.reason };
2573
+ }
2574
+ function computeDeadline(durationMs) {
2575
+ if (durationMs === void 0) {
2576
+ return void 0;
2577
+ }
2578
+ return Date.now() + durationMs;
2579
+ }
2580
+ function remainingForLoop(deadline, perHitTimeoutMs) {
2581
+ if (deadline === void 0) {
2582
+ return perHitTimeoutMs;
2583
+ }
2584
+ const remaining = deadline - Date.now();
2585
+ if (remaining <= 0) {
2586
+ return 0;
2587
+ }
2588
+ return Math.min(remaining, perHitTimeoutMs);
2589
+ }
2590
+ function waitForTransportClose(session) {
2591
+ let cancelled = false;
2592
+ let resolve;
2593
+ const promise = new Promise((res) => {
2594
+ resolve = res;
2595
+ });
2596
+ const off = session.client.onClose(() => {
2597
+ if (!cancelled) {
2598
+ resolve?.();
2599
+ }
2600
+ });
2601
+ return {
2602
+ promise,
2603
+ cancel: () => {
2604
+ cancelled = true;
2605
+ off();
2606
+ resolve?.();
2607
+ }
2608
+ };
2609
+ }
2610
+ async function waitForNextWatchPause(session, handles, timeoutMs, signal) {
2611
+ if (signal.aborted) {
2612
+ return "signal";
2613
+ }
2614
+ try {
2615
+ return await waitForPause(session, {
2616
+ timeoutMs,
2617
+ breakpointIds: handles.map((h) => h.breakpointId),
2618
+ unmatchedPausePolicy: "wait-for-resume"
2619
+ });
2620
+ } catch (err) {
2621
+ if (err instanceof CfInspectorError) {
2622
+ if (err.code === "BREAKPOINT_NOT_HIT") {
2623
+ return "timeout";
2624
+ }
2625
+ if (err.code === "UNRELATED_PAUSE_TIMEOUT") {
2626
+ return "timeout";
2627
+ }
2628
+ }
2629
+ throw err;
2630
+ }
2631
+ }
2632
+ async function captureWatchEvent(session, command, pause, hit, opts) {
2633
+ const snapshot = await captureSnapshot(session, pause, {
2634
+ captures: command.captures,
2635
+ includeScopes: opts.includeScopes === true,
2636
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength },
2637
+ ...command.stackDepth === void 0 ? {} : { stackDepth: command.stackDepth },
2638
+ stackCaptures: command.stackCaptures
2639
+ });
2640
+ const at = formatLocation(command, snapshot.topFrame);
2641
+ const base = {
2642
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2643
+ at,
2644
+ hit,
2645
+ reason: snapshot.reason,
2646
+ hitBreakpoints: snapshot.hitBreakpoints,
2647
+ captures: snapshot.captures
2648
+ };
2649
+ const withFrame = snapshot.topFrame === void 0 ? base : { ...base, topFrame: snapshot.topFrame };
2650
+ const withStack = snapshot.stack === void 0 ? withFrame : { ...withFrame, stack: snapshot.stack };
2651
+ return snapshot.exception === void 0 ? withStack : { ...withStack, exception: snapshot.exception };
2652
+ }
2653
+ function formatLocation(command, topFrame) {
2654
+ if (topFrame?.url !== void 0) {
2655
+ return `${topFrame.url}:${topFrame.line.toString()}`;
2656
+ }
2657
+ const first = command.breakpoints[0];
2658
+ if (first === void 0) {
2659
+ return "(unknown)";
2660
+ }
2661
+ return `${first.file}:${first.line.toString()}`;
2662
+ }
2663
+ function writeWatchSummary(reason, emitted, json) {
2664
+ if (json) {
2665
+ process9.stderr.write(`${JSON.stringify({ stopped: reason, emitted })}
2666
+ `);
2667
+ return;
2668
+ }
2669
+ process9.stderr.write(
2670
+ `Stopped (${reason}); emitted ${emitted.toString()} watch ${emitted === 1 ? "event" : "events"}.
2671
+ `
2672
+ );
2673
+ }
2674
+
1988
2675
  // src/cli/program.ts
1989
2676
  function applyTargetOptions(cmd) {
1990
2677
  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");
1991
2678
  }
2679
+ var collectStrings = (value, prev = []) => [
2680
+ ...prev,
2681
+ value
2682
+ ];
1992
2683
  async function main(argv) {
1993
2684
  const program = new Command();
1994
2685
  program.name("cf-inspector").description("Drive a Node.js inspector from the command line \u2014 set breakpoints, capture snapshots, evaluate expressions");
1995
2686
  registerSnapshot(program);
1996
2687
  registerLog(program);
2688
+ registerWatch(program);
2689
+ registerException(program);
1997
2690
  registerEval(program);
1998
2691
  registerListScripts(program);
1999
2692
  registerAttach(program);
2000
2693
  await program.parseAsync([...argv]);
2001
2694
  }
2002
2695
  function registerSnapshot(program) {
2003
- const collectStrings = (value, prev = []) => [
2004
- ...prev,
2005
- value
2006
- ];
2007
2696
  applyTargetOptions(
2008
2697
  program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
2009
- ).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) => {
2698
+ ).option("--bp <file:line>", "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--timeout <seconds>", "How long to wait for the breakpoint to hit (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--condition <expr>", "Only pause when this JS expression evaluates truthy in the paused frame").option("--hit-count <n>", "Only pause after the breakpoint has been hit N or more times").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1, only top frame)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--no-json", "Print a human-readable summary instead of JSON").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--fail-on-unmatched-pause", "Fail immediately if the target pauses somewhere else").action(async (opts) => {
2010
2699
  await handleSnapshot(opts);
2011
2700
  });
2012
2701
  }
2013
2702
  function registerLog(program) {
2014
2703
  applyTargetOptions(
2015
2704
  program.command("log").description("Stream a non-pausing logpoint: log an expression each time a line executes")
2016
- ).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) => {
2705
+ ).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) => {
2017
2706
  await handleLog(opts);
2018
2707
  });
2019
2708
  }
2709
+ function registerWatch(program) {
2710
+ applyTargetOptions(
2711
+ program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits")
2712
+ ).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) => {
2713
+ await handleWatch(opts);
2714
+ });
2715
+ }
2716
+ function registerException(program) {
2717
+ applyTargetOptions(
2718
+ program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume")
2719
+ ).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) => {
2720
+ await handleException(opts);
2721
+ });
2722
+ }
2020
2723
  function registerEval(program) {
2021
2724
  applyTargetOptions(
2022
2725
  program.command("eval").description("Evaluate an expression against the global Runtime")
@@ -2042,20 +2745,20 @@ function registerAttach(program) {
2042
2745
  // src/cli.ts
2043
2746
  init_types();
2044
2747
  try {
2045
- await main(process8.argv);
2748
+ await main(process10.argv);
2046
2749
  } catch (err) {
2047
2750
  if (err instanceof CfInspectorError) {
2048
- process8.stderr.write(`Error [${err.code}]: ${err.message}
2751
+ process10.stderr.write(`Error [${err.code}]: ${err.message}
2049
2752
  `);
2050
2753
  if (err.detail !== void 0) {
2051
- process8.stderr.write(` detail: ${err.detail}
2754
+ process10.stderr.write(` detail: ${err.detail}
2052
2755
  `);
2053
2756
  }
2054
- process8.exit(1);
2757
+ process10.exit(1);
2055
2758
  }
2056
2759
  const message = err instanceof Error ? err.message : String(err);
2057
- process8.stderr.write(`Error: ${message}
2760
+ process10.stderr.write(`Error: ${message}
2058
2761
  `);
2059
- process8.exit(1);
2762
+ process10.exit(1);
2060
2763
  }
2061
2764
  //# sourceMappingURL=cli.js.map