@saptools/cf-inspector 0.3.16 → 0.3.17

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
@@ -29,7 +29,7 @@ var init_types = __esm({
29
29
  }
30
30
  });
31
31
 
32
- // src/wsTransport.ts
32
+ // src/cdp/wsTransport.ts
33
33
  var wsTransport_exports = {};
34
34
  __export(wsTransport_exports, {
35
35
  wsTransportFactory: () => wsTransportFactory
@@ -37,6 +37,32 @@ __export(wsTransport_exports, {
37
37
  import { WebSocket } from "ws";
38
38
  async function wsTransportFactory(url) {
39
39
  const socket = new WebSocket(url, { perMessageDeflate: false });
40
+ await waitForOpen(socket, url);
41
+ const wrappers = /* @__PURE__ */ new WeakMap();
42
+ return {
43
+ send(payload) {
44
+ socket.send(payload);
45
+ },
46
+ close() {
47
+ socket.close();
48
+ },
49
+ get readyState() {
50
+ return socket.readyState;
51
+ },
52
+ on(event, listener) {
53
+ const wrapped = wrapListener(event, listener, wrappers);
54
+ socket.on(event, wrapped);
55
+ },
56
+ off(event, listener) {
57
+ const wrapped = wrappers.get(listener);
58
+ if (!wrapped) {
59
+ return;
60
+ }
61
+ socket.off(event, wrapped);
62
+ }
63
+ };
64
+ }
65
+ async function waitForOpen(socket, url) {
40
66
  await new Promise((resolve, reject) => {
41
67
  const onOpen = () => {
42
68
  socket.off("error", onError);
@@ -54,213 +80,591 @@ async function wsTransportFactory(url) {
54
80
  socket.once("open", onOpen);
55
81
  socket.once("error", onError);
56
82
  });
57
- const wrappers = /* @__PURE__ */ new WeakMap();
58
- const wrapMessage = (listener) => {
59
- const wrapped = (data) => {
83
+ }
84
+ function wrapListener(event, listener, wrappers) {
85
+ if (event === "message") {
86
+ const wrapped2 = (data) => {
60
87
  listener(data.toString("utf8"));
61
88
  };
62
- wrappers.set(listener, wrapped);
63
- return wrapped;
64
- };
65
- const wrapClose = (listener) => {
66
- const wrapped = () => {
89
+ wrappers.set(listener, wrapped2);
90
+ return wrapped2;
91
+ }
92
+ if (event === "close") {
93
+ const wrapped2 = () => {
67
94
  listener();
68
95
  };
69
- wrappers.set(listener, wrapped);
70
- return wrapped;
71
- };
72
- const wrapError = (listener) => {
73
- const wrapped = (err) => {
74
- listener(err);
75
- };
76
- wrappers.set(listener, wrapped);
77
- return wrapped;
78
- };
79
- return {
80
- send(payload) {
81
- socket.send(payload);
82
- },
83
- close() {
84
- socket.close();
85
- },
86
- get readyState() {
87
- return socket.readyState;
88
- },
89
- on(event, listener) {
90
- if (event === "message") {
91
- socket.on("message", wrapMessage(listener));
92
- } else if (event === "close") {
93
- socket.on("close", wrapClose(listener));
94
- } else {
95
- socket.on("error", wrapError(listener));
96
- }
97
- },
98
- off(event, listener) {
99
- const wrapped = wrappers.get(listener);
100
- if (!wrapped) {
101
- return;
102
- }
103
- if (event === "message") {
104
- socket.off("message", wrapped);
105
- } else if (event === "close") {
106
- socket.off("close", wrapped);
107
- } else {
108
- socket.off("error", wrapped);
109
- }
110
- }
96
+ wrappers.set(listener, wrapped2);
97
+ return wrapped2;
98
+ }
99
+ const wrapped = (err) => {
100
+ listener(err);
111
101
  };
102
+ wrappers.set(listener, wrapped);
103
+ return wrapped;
112
104
  }
113
105
  var init_wsTransport = __esm({
114
- "src/wsTransport.ts"() {
106
+ "src/cdp/wsTransport.ts"() {
115
107
  "use strict";
116
108
  init_types();
117
109
  }
118
110
  });
119
111
 
120
112
  // src/cli.ts
121
- import { performance as performance2 } from "perf_hooks";
122
- import process2 from "process";
113
+ import process8 from "process";
114
+
115
+ // src/cli/program.ts
123
116
  import { Command } from "commander";
124
117
 
125
- // src/captureParser.ts
126
- function parseCaptureList(raw) {
127
- if (raw === void 0 || raw.trim().length === 0) {
128
- return [];
118
+ // src/cli/commands/attach.ts
119
+ import process2 from "process";
120
+
121
+ // src/pathMapper.ts
122
+ init_types();
123
+ var REGEX_PREFIX = "regex:";
124
+ var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
125
+ var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
126
+ function parseBreakpointSpec(input) {
127
+ const idx = input.lastIndexOf(":");
128
+ if (idx <= 0 || idx === input.length - 1) {
129
+ throw new CfInspectorError(
130
+ "INVALID_BREAKPOINT",
131
+ `Breakpoint must be in 'file:line' form, received: "${input}"`
132
+ );
129
133
  }
130
- return splitCaptureExpressions(raw);
134
+ const file = input.slice(0, idx).trim();
135
+ const lineRaw = input.slice(idx + 1).trim();
136
+ const line = Number.parseInt(lineRaw, 10);
137
+ if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
138
+ throw new CfInspectorError(
139
+ "INVALID_BREAKPOINT",
140
+ `Breakpoint line must be a positive integer, received: "${lineRaw}"`
141
+ );
142
+ }
143
+ if (file.length === 0) {
144
+ throw new CfInspectorError(
145
+ "INVALID_BREAKPOINT",
146
+ `Breakpoint file path is empty in "${input}"`
147
+ );
148
+ }
149
+ return { file, line };
131
150
  }
132
- function isQuoteChar(value) {
133
- return value === "'" || value === '"' || value === "`";
151
+ function parseRemoteRoot(value) {
152
+ const trimmed = value?.trim();
153
+ if (trimmed === void 0 || trimmed.length === 0) {
154
+ return { kind: "none" };
155
+ }
156
+ if (trimmed.startsWith(REGEX_PREFIX)) {
157
+ return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
158
+ }
159
+ const slashRegex = parseSlashDelimited(trimmed);
160
+ if (slashRegex !== void 0) {
161
+ return toRegex(slashRegex.pattern, slashRegex.flags);
162
+ }
163
+ return { kind: "literal", value: stripTrailingSlash(trimmed) };
134
164
  }
135
- function consumeQuotedChar(state, char) {
136
- if (state.quote === void 0) {
137
- return false;
165
+ function toRegex(pattern, flags) {
166
+ try {
167
+ const regex = new RegExp(pattern, flags);
168
+ return { kind: "regex", pattern, flags, regex };
169
+ } catch (err) {
170
+ const message = err instanceof Error ? err.message : String(err);
171
+ throw new CfInspectorError(
172
+ "INVALID_REMOTE_ROOT",
173
+ `Failed to compile remote-root regex "${pattern}" with flags "${flags}": ${message}`
174
+ );
138
175
  }
139
- if (state.escaped) {
140
- state.escaped = false;
141
- return true;
176
+ }
177
+ function parseSlashDelimited(value) {
178
+ if (!value.startsWith("/")) {
179
+ return void 0;
142
180
  }
143
- if (char === "\\") {
144
- state.escaped = true;
145
- return true;
181
+ const closing = findLastUnescapedSlash(value);
182
+ if (closing <= 0) {
183
+ return void 0;
146
184
  }
147
- if (char === state.quote) {
148
- state.quote = void 0;
185
+ const flags = value.slice(closing + 1);
186
+ if (flags.length === 0 || !REGEX_FLAGS_PATTERN.test(flags)) {
187
+ return void 0;
149
188
  }
150
- return true;
189
+ return { pattern: value.slice(1, closing), flags };
151
190
  }
152
- function updateCaptureDepth(state, char) {
153
- if (char === "(") {
154
- state.parenDepth += 1;
155
- } else if (char === ")") {
156
- state.parenDepth = Math.max(0, state.parenDepth - 1);
157
- } else if (char === "[") {
158
- state.bracketDepth += 1;
159
- } else if (char === "]") {
160
- state.bracketDepth = Math.max(0, state.bracketDepth - 1);
161
- } else if (char === "{") {
162
- state.braceDepth += 1;
163
- } else if (char === "}") {
164
- state.braceDepth = Math.max(0, state.braceDepth - 1);
191
+ function findLastUnescapedSlash(value) {
192
+ for (let i = value.length - 1; i > 0; i--) {
193
+ if (value[i] === "/" && !isEscaped(value, i)) {
194
+ return i;
195
+ }
165
196
  }
197
+ return -1;
166
198
  }
167
- function isTopLevel(state) {
168
- return state.parenDepth === 0 && state.bracketDepth === 0 && state.braceDepth === 0;
199
+ function isEscaped(value, idx) {
200
+ let backslashes = 0;
201
+ for (let i = idx - 1; i >= 0; i--) {
202
+ if (value[i] === "\\") {
203
+ backslashes++;
204
+ } else {
205
+ break;
206
+ }
207
+ }
208
+ return backslashes % 2 === 1;
169
209
  }
170
- function appendCapturePiece(raw, state, end) {
171
- const piece = raw.slice(state.start, end).trim();
172
- if (piece.length > 0) {
173
- state.pieces.push(piece);
210
+ function stripTrailingSlash(value) {
211
+ if (value.length > 1 && value.endsWith("/")) {
212
+ return value.slice(0, -1);
174
213
  }
214
+ return value;
175
215
  }
176
- function splitCaptureExpressions(raw) {
177
- const state = {
178
- escaped: false,
179
- parenDepth: 0,
180
- bracketDepth: 0,
181
- braceDepth: 0,
182
- quote: void 0,
183
- start: 0,
184
- pieces: []
185
- };
186
- for (let idx = 0; idx < raw.length; idx += 1) {
187
- const char = raw.charAt(idx);
188
- if (consumeQuotedChar(state, char)) {
189
- continue;
216
+ function normalizeRegexRootPattern(pattern) {
217
+ const withoutStartAnchor = pattern.startsWith("^") ? pattern.slice(1) : pattern;
218
+ const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
219
+ return stripTrailingSlash(withoutEndAnchor);
220
+ }
221
+ function buildFileUrlRegex(rootPattern, tail) {
222
+ const separator = rootPattern.endsWith("/") ? "" : "/";
223
+ return `^file://${rootPattern}${separator}${tail}$`;
224
+ }
225
+ function escapeRegExp(value) {
226
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
227
+ }
228
+ function normalizeRelative(file) {
229
+ return file.replaceAll(/^[./\\]+/g, "").replaceAll("\\", "/");
230
+ }
231
+ function dropExtension(file) {
232
+ const match = TS_JS_EXT_PATTERN.exec(file);
233
+ if (!match) {
234
+ return { stem: file, matchedExt: false };
235
+ }
236
+ return { stem: file.slice(0, match.index), matchedExt: true };
237
+ }
238
+ var EXT_GROUP = String.raw`\.(?:ts|js|mts|mjs|cts|cjs)`;
239
+ var OPTIONAL_EXT_GROUP = String.raw`(?:\.(?:ts|js|mts|mjs|cts|cjs))?`;
240
+ function buildBreakpointUrlRegex(input) {
241
+ const normalized = normalizeRelative(input.file);
242
+ const { stem, matchedExt } = dropExtension(normalized);
243
+ const escapedStem = escapeRegExp(stem);
244
+ const tail = matchedExt ? `${escapedStem}${EXT_GROUP}` : `${escapedStem}${OPTIONAL_EXT_GROUP}`;
245
+ switch (input.remoteRoot.kind) {
246
+ case "none": {
247
+ return `(?:^|/)${tail}$`;
190
248
  }
191
- if (isQuoteChar(char)) {
192
- state.quote = char;
193
- continue;
249
+ case "literal": {
250
+ const escapedRoot = escapeRegExp(input.remoteRoot.value);
251
+ return buildFileUrlRegex(escapedRoot, tail);
194
252
  }
195
- updateCaptureDepth(state, char);
196
- if (char === "," && isTopLevel(state)) {
197
- appendCapturePiece(raw, state, idx);
198
- state.start = idx + 1;
253
+ case "regex": {
254
+ const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
255
+ return buildFileUrlRegex(rootPattern, tail);
199
256
  }
200
257
  }
201
- appendCapturePiece(raw, state, raw.length);
202
- return state.pieces;
203
258
  }
204
259
 
205
- // src/cliOutput.ts
206
- import process from "process";
207
- function writeJson(value) {
208
- process.stdout.write(`${JSON.stringify(value, null, 2)}
209
- `);
260
+ // src/inspector/breakpoints.ts
261
+ init_types();
262
+
263
+ // src/inspector/conversions.ts
264
+ function asString(value, fallback = "") {
265
+ return typeof value === "string" ? value : fallback;
210
266
  }
211
- function writeHumanSnapshot(snapshot) {
212
- const pausedDuration = snapshot.pausedDurationMs === null ? "unknown" : `${snapshot.pausedDurationMs.toFixed(1)}ms`;
213
- const lines = [];
214
- lines.push(
215
- `Snapshot @ ${snapshot.capturedAt}`,
216
- ` reason: ${snapshot.reason}`,
217
- ` paused: ${pausedDuration}`
218
- );
219
- if (snapshot.topFrame) {
220
- const frame = snapshot.topFrame;
221
- const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
222
- const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
223
- lines.push(
224
- ` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
225
- );
226
- if (frame.scopes !== void 0) {
227
- for (const scope of frame.scopes) {
228
- lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
229
- for (const variable of scope.variables) {
230
- lines.push(` ${variable.name} = ${variable.value}`);
231
- }
232
- }
233
- }
234
- }
235
- if (snapshot.captures.length > 0) {
236
- lines.push(" captures:");
237
- for (const capture of snapshot.captures) {
238
- const detail = capture.error ?? capture.value ?? "undefined";
239
- lines.push(` ${capture.expression} = ${detail}`);
267
+ function asNumber(value, fallback = 0) {
268
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
269
+ }
270
+ function toResolvedLocations(value) {
271
+ if (!Array.isArray(value)) {
272
+ return [];
273
+ }
274
+ return value.flatMap((entry) => {
275
+ if (typeof entry !== "object" || entry === null) {
276
+ return [];
240
277
  }
278
+ const candidate = entry;
279
+ const scriptId = asString(candidate.scriptId);
280
+ if (scriptId.length === 0) {
281
+ return [];
282
+ }
283
+ const url = typeof candidate.url === "string" ? candidate.url : void 0;
284
+ const lineNumber = asNumber(candidate.lineNumber);
285
+ const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
286
+ return [result];
287
+ });
288
+ }
289
+ function toScopeChain(value) {
290
+ if (!Array.isArray(value)) {
291
+ return [];
241
292
  }
242
- process.stdout.write(`${lines.join("\n")}
243
- `);
293
+ return value.flatMap((entry) => {
294
+ if (typeof entry !== "object" || entry === null) {
295
+ return [];
296
+ }
297
+ const candidate = entry;
298
+ const type = asString(candidate.type);
299
+ if (type.length === 0) {
300
+ return [];
301
+ }
302
+ const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
303
+ const name = typeof candidate.name === "string" ? candidate.name : void 0;
304
+ const base = name === void 0 ? { type } : { type, name };
305
+ return [objectId === void 0 ? base : { ...base, objectId }];
306
+ });
244
307
  }
245
- function writeLogEvent(event, json) {
246
- if (json) {
247
- process.stdout.write(`${JSON.stringify(event)}
248
- `);
308
+ function toCallFrames(value) {
309
+ if (!Array.isArray(value)) {
310
+ return [];
311
+ }
312
+ return value.flatMap((entry) => {
313
+ if (typeof entry !== "object" || entry === null) {
314
+ return [];
315
+ }
316
+ const candidate = entry;
317
+ const callFrameId = asString(candidate.callFrameId);
318
+ if (callFrameId.length === 0) {
319
+ return [];
320
+ }
321
+ const url = typeof candidate.url === "string" ? candidate.url : void 0;
322
+ const base = {
323
+ callFrameId,
324
+ functionName: asString(candidate.functionName),
325
+ lineNumber: asNumber(candidate.location?.lineNumber),
326
+ columnNumber: asNumber(candidate.location?.columnNumber),
327
+ scopeChain: toScopeChain(candidate.scopeChain)
328
+ };
329
+ return [url === void 0 ? base : { ...base, url }];
330
+ });
331
+ }
332
+ function toPauseEvent(params, receivedAtMs) {
333
+ return {
334
+ reason: asString(params.reason),
335
+ hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
336
+ callFrames: toCallFrames(params.callFrames),
337
+ receivedAtMs
338
+ };
339
+ }
340
+ function topFrameLocation(pause) {
341
+ const top = pause.callFrames[0];
342
+ if (top === void 0) {
343
+ return "(no call frame)";
344
+ }
345
+ const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
346
+ return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
347
+ }
348
+ function pauseDetail(pause) {
349
+ return JSON.stringify({
350
+ reason: pause.reason,
351
+ hitBreakpoints: pause.hitBreakpoints,
352
+ topFrame: topFrameLocation(pause)
353
+ });
354
+ }
355
+
356
+ // src/inspector/breakpoints.ts
357
+ async function setBreakpoint(session, input) {
358
+ const remoteRoot = input.remoteRoot ?? { kind: "none" };
359
+ const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
360
+ const params = {
361
+ lineNumber: input.line - 1,
362
+ urlRegex
363
+ };
364
+ if (input.condition !== void 0 && input.condition.length > 0) {
365
+ params["condition"] = input.condition;
366
+ }
367
+ const result = await session.client.send(
368
+ "Debugger.setBreakpointByUrl",
369
+ params
370
+ );
371
+ const breakpointId = asString(result.breakpointId);
372
+ if (breakpointId.length === 0) {
373
+ throw new CfInspectorError(
374
+ "CDP_REQUEST_FAILED",
375
+ `setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
376
+ );
377
+ }
378
+ return {
379
+ breakpointId,
380
+ file: input.file,
381
+ line: input.line,
382
+ urlRegex,
383
+ resolvedLocations: toResolvedLocations(result.locations)
384
+ };
385
+ }
386
+ async function removeBreakpoint(session, breakpointId) {
387
+ await session.client.send("Debugger.removeBreakpoint", { breakpointId });
388
+ }
389
+
390
+ // src/inspector/discovery.ts
391
+ init_types();
392
+ import { request } from "http";
393
+ async function fetchJson(url, timeoutMs) {
394
+ return await new Promise((resolve, reject) => {
395
+ const req = request(url, { method: "GET" }, (res) => {
396
+ const chunks = [];
397
+ res.on("data", (chunk) => {
398
+ chunks.push(chunk);
399
+ });
400
+ res.on("end", () => {
401
+ try {
402
+ resolve(parseJsonResponse(chunks));
403
+ } catch (err) {
404
+ reject(parseDiscoveryError(url, err));
405
+ }
406
+ });
407
+ res.on("error", (err) => {
408
+ reject(newDiscoveryError(`Inspector discovery response error: ${err.message}`));
409
+ });
410
+ });
411
+ req.setTimeout(timeoutMs, () => {
412
+ req.destroy(
413
+ new CfInspectorError(
414
+ "INSPECTOR_DISCOVERY_FAILED",
415
+ `Inspector discovery at ${url} timed out after ${timeoutMs.toString()}ms`
416
+ )
417
+ );
418
+ });
419
+ req.on("error", (err) => {
420
+ reject(
421
+ err instanceof CfInspectorError ? err : new CfInspectorError(
422
+ "INSPECTOR_DISCOVERY_FAILED",
423
+ `Inspector discovery at ${url} failed: ${err.message}`
424
+ )
425
+ );
426
+ });
427
+ req.end();
428
+ });
429
+ }
430
+ function parseJsonResponse(chunks) {
431
+ const text = Buffer.concat(chunks).toString("utf8");
432
+ return JSON.parse(text);
433
+ }
434
+ function parseDiscoveryError(url, err) {
435
+ const message = err instanceof Error ? err.message : String(err);
436
+ return newDiscoveryError(`Failed to parse inspector discovery response from ${url}: ${message}`);
437
+ }
438
+ function newDiscoveryError(message) {
439
+ return new CfInspectorError("INSPECTOR_DISCOVERY_FAILED", message);
440
+ }
441
+ function toInspectorTarget(value, source) {
442
+ if (typeof value !== "object" || value === null) {
443
+ throw new CfInspectorError(
444
+ "INSPECTOR_DISCOVERY_FAILED",
445
+ `Inspector target is not an object in ${source}`
446
+ );
447
+ }
448
+ const candidate = value;
449
+ const webSocketDebuggerUrl = candidate["webSocketDebuggerUrl"];
450
+ if (typeof webSocketDebuggerUrl !== "string" || webSocketDebuggerUrl.length === 0) {
451
+ throw new CfInspectorError(
452
+ "INSPECTOR_DISCOVERY_FAILED",
453
+ `Inspector target is missing webSocketDebuggerUrl in ${source}`
454
+ );
455
+ }
456
+ return {
457
+ description: typeof candidate["description"] === "string" ? candidate["description"] : "",
458
+ id: typeof candidate["id"] === "string" ? candidate["id"] : "",
459
+ title: typeof candidate["title"] === "string" ? candidate["title"] : "",
460
+ type: typeof candidate["type"] === "string" ? candidate["type"] : "",
461
+ url: typeof candidate["url"] === "string" ? candidate["url"] : "",
462
+ webSocketDebuggerUrl,
463
+ ...typeof candidate["devtoolsFrontendUrl"] === "string" ? { devtoolsFrontendUrl: candidate["devtoolsFrontendUrl"] } : {},
464
+ ...typeof candidate["faviconUrl"] === "string" ? { faviconUrl: candidate["faviconUrl"] } : {}
465
+ };
466
+ }
467
+ async function discoverInspectorTargets(host, port, timeoutMs) {
468
+ const url = `http://${host}:${port.toString()}/json/list`;
469
+ const raw = await fetchJson(url, timeoutMs);
470
+ if (!Array.isArray(raw) || raw.length === 0) {
471
+ throw new CfInspectorError(
472
+ "INSPECTOR_DISCOVERY_FAILED",
473
+ `No inspector targets returned from ${url}`
474
+ );
475
+ }
476
+ return raw.map((entry, idx) => toInspectorTarget(entry, `${url}[${idx.toString()}]`));
477
+ }
478
+ function readVersionField(value, ...keys) {
479
+ for (const key of keys) {
480
+ const entry = value[key];
481
+ if (typeof entry === "string" && entry.length > 0) {
482
+ return entry;
483
+ }
484
+ }
485
+ return void 0;
486
+ }
487
+ async function fetchInspectorVersion(host, port, timeoutMs) {
488
+ const url = `http://${host}:${port.toString()}/json/version`;
489
+ const raw = await fetchJson(url, timeoutMs);
490
+ if (typeof raw !== "object" || raw === null) {
491
+ throw new CfInspectorError(
492
+ "INSPECTOR_DISCOVERY_FAILED",
493
+ `Unexpected /json/version response from ${url}`
494
+ );
495
+ }
496
+ const value = raw;
497
+ const browser = readVersionField(value, "Browser", "browser");
498
+ const protocolVersion = readVersionField(value, "Protocol-Version", "protocolVersion");
499
+ if (browser === void 0 || protocolVersion === void 0) {
500
+ throw new CfInspectorError(
501
+ "INSPECTOR_DISCOVERY_FAILED",
502
+ `Unexpected /json/version response from ${url}`
503
+ );
504
+ }
505
+ return { browser, protocolVersion };
506
+ }
507
+
508
+ // src/inspector/pause.ts
509
+ init_types();
510
+ import { performance } from "perf_hooks";
511
+ function pauseMatches(pause, breakpointIds) {
512
+ if (breakpointIds === void 0 || breakpointIds.length === 0) {
513
+ return true;
514
+ }
515
+ return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
516
+ }
517
+ function remainingUntil(deadlineMs) {
518
+ return Math.max(0, deadlineMs - performance.now());
519
+ }
520
+ function hasResumedSincePause(session, pause) {
521
+ const pauseAt = pause.receivedAtMs;
522
+ const resumedAt = session.debuggerState.lastResumedAtMs;
523
+ return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
524
+ }
525
+ function throwBreakpointTimeout(timeoutMs) {
526
+ throw new CfInspectorError(
527
+ "BREAKPOINT_NOT_HIT",
528
+ `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
529
+ );
530
+ }
531
+ function throwUnrelatedPauseTimeout(pause, timeoutMs) {
532
+ throw new CfInspectorError(
533
+ "UNRELATED_PAUSE_TIMEOUT",
534
+ `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
535
+ pauseDetail(pause)
536
+ );
537
+ }
538
+ async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
539
+ if (hasResumedSincePause(session, pause)) {
249
540
  return;
250
541
  }
251
- if (event.error !== void 0) {
252
- process.stdout.write(`[${event.ts}] ${event.at} !err ${event.error}
253
- `);
542
+ const remainingMs = remainingUntil(deadlineMs);
543
+ if (remainingMs <= 0) {
544
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
545
+ }
546
+ try {
547
+ await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
548
+ session.debuggerState.lastResumedAtMs = performance.now();
549
+ } catch (err) {
550
+ if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
551
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
552
+ }
553
+ throw err;
554
+ }
555
+ }
556
+ async function handleUnmatchedPause(session, pause, options, deadlineMs) {
557
+ if (options.unmatchedPausePolicy === "fail") {
558
+ throw new CfInspectorError(
559
+ "UNRELATED_PAUSE",
560
+ "Target paused before this command's breakpoint was reached",
561
+ pauseDetail(pause)
562
+ );
563
+ }
564
+ if (hasResumedSincePause(session, pause)) {
565
+ return;
566
+ }
567
+ options.onUnmatchedPause?.(pause);
568
+ await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
569
+ }
570
+ async function waitForPause(session, options) {
571
+ const deadlineMs = performance.now() + options.timeoutMs;
572
+ const buffer = session.pauseBuffer;
573
+ while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
574
+ while (buffer.length > 0) {
575
+ const buffered = buffer.shift();
576
+ if (buffered === void 0) {
577
+ continue;
578
+ }
579
+ if (pauseMatches(buffered, options.breakpointIds)) {
580
+ return buffered;
581
+ }
582
+ await handleUnmatchedPause(session, buffered, options, deadlineMs);
583
+ }
584
+ const pause = await waitForLivePause(session, options, deadlineMs);
585
+ if (pauseMatches(pause, options.breakpointIds)) {
586
+ return pause;
587
+ }
588
+ await handleUnmatchedPause(session, pause, options, deadlineMs);
589
+ }
590
+ throwBreakpointTimeout(options.timeoutMs);
591
+ }
592
+ async function waitForLivePause(session, options, deadlineMs) {
593
+ const remainingMs = remainingUntil(deadlineMs);
594
+ if (remainingMs <= 0) {
595
+ throwBreakpointTimeout(options.timeoutMs);
596
+ }
597
+ session.pauseWaitGate.active = true;
598
+ let receivedAtMs;
599
+ let params;
600
+ try {
601
+ params = await session.client.waitFor("Debugger.paused", {
602
+ timeoutMs: remainingMs,
603
+ predicate: () => {
604
+ receivedAtMs = performance.now();
605
+ return true;
606
+ }
607
+ });
608
+ } finally {
609
+ session.pauseWaitGate.active = false;
610
+ }
611
+ return toPauseEvent(params, receivedAtMs ?? performance.now());
612
+ }
613
+
614
+ // src/inspector/runtime.ts
615
+ init_types();
616
+ async function resume(session) {
617
+ await session.client.send("Debugger.resume");
618
+ }
619
+ async function evaluateOnFrame(session, callFrameId, expression) {
620
+ return await session.client.send("Debugger.evaluateOnCallFrame", {
621
+ callFrameId,
622
+ expression,
623
+ returnByValue: false,
624
+ generatePreview: true,
625
+ silent: true
626
+ });
627
+ }
628
+ async function evaluateGlobal(session, expression) {
629
+ return await session.client.send("Runtime.evaluate", {
630
+ expression,
631
+ returnByValue: false,
632
+ generatePreview: true,
633
+ silent: true
634
+ });
635
+ }
636
+ function listScripts(session) {
637
+ return [...session.scripts.values()];
638
+ }
639
+ async function validateExpression(session, expression) {
640
+ const result = await session.client.send("Runtime.compileScript", {
641
+ expression,
642
+ sourceURL: "<cf-inspector-validate>",
643
+ persistScript: false
644
+ });
645
+ if (result.exceptionDetails === void 0) {
254
646
  return;
255
647
  }
256
- process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
257
- `);
648
+ const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
649
+ throw new CfInspectorError("INVALID_EXPRESSION", description);
650
+ }
651
+ async function getProperties(session, objectId) {
652
+ const result = await session.client.send("Runtime.getProperties", {
653
+ objectId,
654
+ ownProperties: true,
655
+ accessorPropertiesOnly: false,
656
+ generatePreview: true
657
+ });
658
+ if (!Array.isArray(result.result)) {
659
+ return [];
660
+ }
661
+ return result.result;
258
662
  }
259
663
 
260
- // src/inspector.ts
261
- import { performance } from "perf_hooks";
664
+ // src/inspector/session.ts
665
+ import { performance as performance2 } from "perf_hooks";
262
666
 
263
- // src/cdp.ts
667
+ // src/cdp/client.ts
264
668
  init_types();
265
669
  import { EventEmitter } from "events";
266
670
  var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
@@ -293,23 +697,7 @@ var CdpClient = class _CdpClient {
293
697
  return;
294
698
  }
295
699
  if (typeof parsed.id === "number") {
296
- const pending = this.pending.get(parsed.id);
297
- if (!pending) {
298
- return;
299
- }
300
- this.pending.delete(parsed.id);
301
- clearTimeout(pending.timer);
302
- if (parsed.error) {
303
- pending.reject(
304
- new CfInspectorError(
305
- "CDP_REQUEST_FAILED",
306
- `CDP request ${parsed.id.toString()} failed: ${parsed.error.message}`,
307
- JSON.stringify(parsed.error)
308
- )
309
- );
310
- return;
311
- }
312
- pending.resolve(parsed.result);
700
+ this.handleResponse(parsed);
313
701
  return;
314
702
  }
315
703
  if (typeof parsed.method === "string") {
@@ -344,15 +732,7 @@ var CdpClient = class _CdpClient {
344
732
  const id = this.nextId++;
345
733
  const payload = JSON.stringify({ id, method, params });
346
734
  return await new Promise((resolve, reject) => {
347
- const timer = setTimeout(() => {
348
- this.pending.delete(id);
349
- reject(
350
- new CfInspectorError(
351
- "CDP_REQUEST_FAILED",
352
- `CDP method ${method} timed out after ${this.requestTimeoutMs.toString()}ms`
353
- )
354
- );
355
- }, this.requestTimeoutMs);
735
+ const timer = this.createRequestTimer(id, method, reject);
356
736
  this.pending.set(id, {
357
737
  resolve: (value) => {
358
738
  resolve(value);
@@ -360,14 +740,7 @@ var CdpClient = class _CdpClient {
360
740
  reject,
361
741
  timer
362
742
  });
363
- try {
364
- this.transport.send(payload);
365
- } catch (err) {
366
- clearTimeout(timer);
367
- this.pending.delete(id);
368
- const message = err instanceof Error ? err.message : String(err);
369
- reject(new CfInspectorError("CDP_REQUEST_FAILED", `Failed to send ${method}: ${message}`));
370
- }
743
+ this.sendPayload(id, method, payload, timer, reject);
371
744
  });
372
745
  }
373
746
  on(method, listener) {
@@ -389,349 +762,136 @@ var CdpClient = class _CdpClient {
389
762
  offEvent();
390
763
  offClose();
391
764
  };
765
+ const finish = (value) => {
766
+ settled = true;
767
+ cleanup();
768
+ resolve(value);
769
+ };
392
770
  const offEvent = this.on(method, (raw) => {
393
771
  if (settled) {
394
772
  return;
395
773
  }
396
774
  const params = raw;
397
775
  if (options.predicate && !options.predicate(params)) {
398
- return;
399
- }
400
- settled = true;
401
- cleanup();
402
- resolve(params);
403
- });
404
- const offClose = this.onClose((err) => {
405
- if (settled) {
406
- return;
407
- }
408
- settled = true;
409
- cleanup();
410
- reject(err);
411
- });
412
- const timer = setTimeout(() => {
413
- if (settled) {
414
- return;
415
- }
416
- settled = true;
417
- cleanup();
418
- reject(
419
- new CfInspectorError(
420
- "BREAKPOINT_NOT_HIT",
421
- `Timed out waiting for ${method} after ${options.timeoutMs.toString()}ms`
422
- )
423
- );
424
- }, options.timeoutMs);
425
- });
426
- }
427
- onClose(listener) {
428
- if (this.closed) {
429
- const reason = this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
430
- queueMicrotask(() => {
431
- listener(reason);
432
- });
433
- return () => {
434
- };
435
- }
436
- this.emitter.on("__close__", listener);
437
- return () => {
438
- this.emitter.off("__close__", listener);
439
- };
440
- }
441
- dispose() {
442
- if (this.closed) {
443
- return;
444
- }
445
- this.transport.off("message", this.handleMessage);
446
- this.transport.off("close", this.handleClose);
447
- this.transport.off("error", this.handleError);
448
- try {
449
- this.transport.close();
450
- } catch {
451
- }
452
- this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection disposed"));
453
- }
454
- get isClosed() {
455
- return this.closed;
456
- }
457
- markClosed(reason) {
458
- if (this.closed) {
459
- return;
460
- }
461
- this.closed = true;
462
- this.closeReason = reason;
463
- for (const [, pending] of this.pending) {
464
- clearTimeout(pending.timer);
465
- pending.reject(reason);
466
- }
467
- this.pending.clear();
468
- this.emitter.emit("__close__", reason);
469
- this.emitter.removeAllListeners();
470
- }
471
- };
472
-
473
- // src/inspectorDiscovery.ts
474
- init_types();
475
- import { request } from "http";
476
- async function fetchJson(url, timeoutMs) {
477
- return await new Promise((resolve, reject) => {
478
- const req = request(url, { method: "GET" }, (res) => {
479
- const chunks = [];
480
- res.on("data", (chunk) => {
481
- chunks.push(chunk);
482
- });
483
- res.on("end", () => {
484
- try {
485
- const text = Buffer.concat(chunks).toString("utf8");
486
- resolve(JSON.parse(text));
487
- } catch (err) {
488
- const message = err instanceof Error ? err.message : String(err);
489
- reject(
490
- new CfInspectorError(
491
- "INSPECTOR_DISCOVERY_FAILED",
492
- `Failed to parse inspector discovery response from ${url}: ${message}`
493
- )
494
- );
495
- }
496
- });
497
- res.on("error", (err) => {
498
- reject(
499
- new CfInspectorError(
500
- "INSPECTOR_DISCOVERY_FAILED",
501
- `Inspector discovery response error: ${err.message}`
502
- )
503
- );
504
- });
505
- });
506
- req.setTimeout(timeoutMs, () => {
507
- req.destroy(
508
- new CfInspectorError(
509
- "INSPECTOR_DISCOVERY_FAILED",
510
- `Inspector discovery at ${url} timed out after ${timeoutMs.toString()}ms`
511
- )
512
- );
513
- });
514
- req.on("error", (err) => {
515
- reject(
516
- err instanceof CfInspectorError ? err : new CfInspectorError(
517
- "INSPECTOR_DISCOVERY_FAILED",
518
- `Inspector discovery at ${url} failed: ${err.message}`
519
- )
520
- );
521
- });
522
- req.end();
523
- });
524
- }
525
- function toInspectorTarget(value, source) {
526
- if (typeof value !== "object" || value === null) {
527
- throw new CfInspectorError(
528
- "INSPECTOR_DISCOVERY_FAILED",
529
- `Inspector target is not an object in ${source}`
530
- );
531
- }
532
- const candidate = value;
533
- const webSocketDebuggerUrl = candidate["webSocketDebuggerUrl"];
534
- if (typeof webSocketDebuggerUrl !== "string" || webSocketDebuggerUrl.length === 0) {
535
- throw new CfInspectorError(
536
- "INSPECTOR_DISCOVERY_FAILED",
537
- `Inspector target is missing webSocketDebuggerUrl in ${source}`
538
- );
539
- }
540
- return {
541
- description: typeof candidate["description"] === "string" ? candidate["description"] : "",
542
- id: typeof candidate["id"] === "string" ? candidate["id"] : "",
543
- title: typeof candidate["title"] === "string" ? candidate["title"] : "",
544
- type: typeof candidate["type"] === "string" ? candidate["type"] : "",
545
- url: typeof candidate["url"] === "string" ? candidate["url"] : "",
546
- webSocketDebuggerUrl,
547
- ...typeof candidate["devtoolsFrontendUrl"] === "string" ? { devtoolsFrontendUrl: candidate["devtoolsFrontendUrl"] } : {},
548
- ...typeof candidate["faviconUrl"] === "string" ? { faviconUrl: candidate["faviconUrl"] } : {}
549
- };
550
- }
551
- async function discoverInspectorTargets(host, port, timeoutMs) {
552
- const url = `http://${host}:${port.toString()}/json/list`;
553
- const raw = await fetchJson(url, timeoutMs);
554
- if (!Array.isArray(raw) || raw.length === 0) {
555
- throw new CfInspectorError(
556
- "INSPECTOR_DISCOVERY_FAILED",
557
- `No inspector targets returned from ${url}`
558
- );
559
- }
560
- return raw.map((entry, idx) => toInspectorTarget(entry, `${url}[${idx.toString()}]`));
561
- }
562
- function readVersionField(value, ...keys) {
563
- for (const key of keys) {
564
- const entry = value[key];
565
- if (typeof entry === "string" && entry.length > 0) {
566
- return entry;
567
- }
568
- }
569
- return void 0;
570
- }
571
- async function fetchInspectorVersion(host, port, timeoutMs) {
572
- const url = `http://${host}:${port.toString()}/json/version`;
573
- const raw = await fetchJson(url, timeoutMs);
574
- if (typeof raw !== "object" || raw === null) {
575
- throw new CfInspectorError(
576
- "INSPECTOR_DISCOVERY_FAILED",
577
- `Unexpected /json/version response from ${url}`
578
- );
579
- }
580
- const value = raw;
581
- const browser = readVersionField(value, "Browser", "browser");
582
- const protocolVersion = readVersionField(value, "Protocol-Version", "protocolVersion");
583
- if (browser === void 0 || protocolVersion === void 0) {
584
- throw new CfInspectorError(
585
- "INSPECTOR_DISCOVERY_FAILED",
586
- `Unexpected /json/version response from ${url}`
587
- );
588
- }
589
- return { browser, protocolVersion };
590
- }
591
-
592
- // src/pathMapper.ts
593
- init_types();
594
- var REGEX_PREFIX = "regex:";
595
- var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
596
- var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
597
- function parseBreakpointSpec(input) {
598
- const idx = input.lastIndexOf(":");
599
- if (idx <= 0 || idx === input.length - 1) {
600
- throw new CfInspectorError(
601
- "INVALID_BREAKPOINT",
602
- `Breakpoint must be in 'file:line' form, received: "${input}"`
603
- );
604
- }
605
- const file = input.slice(0, idx).trim();
606
- const lineRaw = input.slice(idx + 1).trim();
607
- const line = Number.parseInt(lineRaw, 10);
608
- if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
609
- throw new CfInspectorError(
610
- "INVALID_BREAKPOINT",
611
- `Breakpoint line must be a positive integer, received: "${lineRaw}"`
612
- );
613
- }
614
- if (file.length === 0) {
615
- throw new CfInspectorError(
616
- "INVALID_BREAKPOINT",
617
- `Breakpoint file path is empty in "${input}"`
618
- );
619
- }
620
- return { file, line };
621
- }
622
- function parseRemoteRoot(value) {
623
- const trimmed = value?.trim();
624
- if (trimmed === void 0 || trimmed.length === 0) {
625
- return { kind: "none" };
626
- }
627
- if (trimmed.startsWith(REGEX_PREFIX)) {
628
- return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
629
- }
630
- const slashRegex = parseSlashDelimited(trimmed);
631
- if (slashRegex !== void 0) {
632
- return toRegex(slashRegex.pattern, slashRegex.flags);
633
- }
634
- return { kind: "literal", value: stripTrailingSlash(trimmed) };
635
- }
636
- function toRegex(pattern, flags) {
637
- try {
638
- const regex = new RegExp(pattern, flags);
639
- return { kind: "regex", pattern, flags, regex };
640
- } catch (err) {
641
- const message = err instanceof Error ? err.message : String(err);
642
- throw new CfInspectorError(
643
- "INVALID_REMOTE_ROOT",
644
- `Failed to compile remote-root regex "${pattern}" with flags "${flags}": ${message}`
645
- );
776
+ return;
777
+ }
778
+ finish(params);
779
+ });
780
+ const offClose = this.onClose((err) => {
781
+ if (settled) {
782
+ return;
783
+ }
784
+ settled = true;
785
+ cleanup();
786
+ reject(err);
787
+ });
788
+ const timer = setTimeout(() => {
789
+ if (settled) {
790
+ return;
791
+ }
792
+ settled = true;
793
+ cleanup();
794
+ reject(this.createWaitTimeoutError(method, options.timeoutMs));
795
+ }, options.timeoutMs);
796
+ });
646
797
  }
647
- }
648
- function parseSlashDelimited(value) {
649
- if (!value.startsWith("/")) {
650
- return void 0;
798
+ onClose(listener) {
799
+ if (this.closed) {
800
+ const reason = this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
801
+ queueMicrotask(() => {
802
+ listener(reason);
803
+ });
804
+ return () => {
805
+ };
806
+ }
807
+ this.emitter.on("__close__", listener);
808
+ return () => {
809
+ this.emitter.off("__close__", listener);
810
+ };
651
811
  }
652
- const closing = findLastUnescapedSlash(value);
653
- if (closing <= 0) {
654
- return void 0;
812
+ dispose() {
813
+ if (this.closed) {
814
+ return;
815
+ }
816
+ this.transport.off("message", this.handleMessage);
817
+ this.transport.off("close", this.handleClose);
818
+ this.transport.off("error", this.handleError);
819
+ try {
820
+ this.transport.close();
821
+ } catch {
822
+ }
823
+ this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection disposed"));
655
824
  }
656
- const flags = value.slice(closing + 1);
657
- if (flags.length === 0 || !REGEX_FLAGS_PATTERN.test(flags)) {
658
- return void 0;
825
+ get isClosed() {
826
+ return this.closed;
659
827
  }
660
- return { pattern: value.slice(1, closing), flags };
661
- }
662
- function findLastUnescapedSlash(value) {
663
- for (let i = value.length - 1; i > 0; i--) {
664
- if (value[i] === "/" && !isEscaped(value, i)) {
665
- return i;
828
+ handleResponse(parsed) {
829
+ const pending = this.pending.get(parsed.id ?? -1);
830
+ if (!pending) {
831
+ return;
666
832
  }
667
- }
668
- return -1;
669
- }
670
- function isEscaped(value, idx) {
671
- let backslashes = 0;
672
- for (let i = idx - 1; i >= 0; i--) {
673
- if (value[i] === "\\") {
674
- backslashes++;
675
- } else {
676
- break;
833
+ this.pending.delete(parsed.id ?? -1);
834
+ clearTimeout(pending.timer);
835
+ if (parsed.error) {
836
+ pending.reject(
837
+ new CfInspectorError(
838
+ "CDP_REQUEST_FAILED",
839
+ `CDP request ${(parsed.id ?? -1).toString()} failed: ${parsed.error.message}`,
840
+ JSON.stringify(parsed.error)
841
+ )
842
+ );
843
+ return;
677
844
  }
845
+ pending.resolve(parsed.result);
678
846
  }
679
- return backslashes % 2 === 1;
680
- }
681
- function stripTrailingSlash(value) {
682
- if (value.length > 1 && value.endsWith("/")) {
683
- return value.slice(0, -1);
847
+ createRequestTimer(id, method, reject) {
848
+ return setTimeout(() => {
849
+ this.pending.delete(id);
850
+ reject(
851
+ new CfInspectorError(
852
+ "CDP_REQUEST_FAILED",
853
+ `CDP method ${method} timed out after ${this.requestTimeoutMs.toString()}ms`
854
+ )
855
+ );
856
+ }, this.requestTimeoutMs);
684
857
  }
685
- return value;
686
- }
687
- function normalizeRegexRootPattern(pattern) {
688
- const withoutStartAnchor = pattern.startsWith("^") ? pattern.slice(1) : pattern;
689
- const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
690
- return stripTrailingSlash(withoutEndAnchor);
691
- }
692
- function buildFileUrlRegex(rootPattern, tail) {
693
- const separator = rootPattern.endsWith("/") ? "" : "/";
694
- return `^file://${rootPattern}${separator}${tail}$`;
695
- }
696
- function escapeRegExp(value) {
697
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
698
- }
699
- function normalizeRelative(file) {
700
- return file.replaceAll(/^[./\\]+/g, "").replaceAll("\\", "/");
701
- }
702
- function dropExtension(file) {
703
- const match = TS_JS_EXT_PATTERN.exec(file);
704
- if (!match) {
705
- return { stem: file, matchedExt: false };
858
+ createWaitTimeoutError(method, timeoutMs) {
859
+ return new CfInspectorError(
860
+ "BREAKPOINT_NOT_HIT",
861
+ `Timed out waiting for ${method} after ${timeoutMs.toString()}ms`
862
+ );
706
863
  }
707
- return { stem: file.slice(0, match.index), matchedExt: true };
708
- }
709
- var EXT_GROUP = String.raw`\.(?:ts|js|mts|mjs|cts|cjs)`;
710
- var OPTIONAL_EXT_GROUP = String.raw`(?:\.(?:ts|js|mts|mjs|cts|cjs))?`;
711
- function buildBreakpointUrlRegex(input) {
712
- const normalized = normalizeRelative(input.file);
713
- const { stem, matchedExt } = dropExtension(normalized);
714
- const escapedStem = escapeRegExp(stem);
715
- const tail = matchedExt ? `${escapedStem}${EXT_GROUP}` : `${escapedStem}${OPTIONAL_EXT_GROUP}`;
716
- switch (input.remoteRoot.kind) {
717
- case "none": {
718
- return `(?:^|/)${tail}$`;
864
+ sendPayload(id, method, payload, timer, reject) {
865
+ try {
866
+ this.transport.send(payload);
867
+ } catch (err) {
868
+ clearTimeout(timer);
869
+ this.pending.delete(id);
870
+ const message = err instanceof Error ? err.message : String(err);
871
+ reject(new CfInspectorError("CDP_REQUEST_FAILED", `Failed to send ${method}: ${message}`));
719
872
  }
720
- case "literal": {
721
- const escapedRoot = escapeRegExp(input.remoteRoot.value);
722
- return buildFileUrlRegex(escapedRoot, tail);
873
+ }
874
+ markClosed(reason) {
875
+ if (this.closed) {
876
+ return;
723
877
  }
724
- case "regex": {
725
- const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
726
- return buildFileUrlRegex(rootPattern, tail);
878
+ this.closed = true;
879
+ this.closeReason = reason;
880
+ for (const [, pending] of this.pending) {
881
+ clearTimeout(pending.timer);
882
+ pending.reject(reason);
727
883
  }
884
+ this.pending.clear();
885
+ this.emitter.emit("__close__", reason);
886
+ this.emitter.removeAllListeners();
728
887
  }
729
- }
888
+ };
730
889
 
731
- // src/inspector.ts
890
+ // src/inspector/session.ts
732
891
  init_types();
733
892
  var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
734
893
  var DEFAULT_HOST = "127.0.0.1";
894
+ var PAUSE_BUFFER_LIMIT = 32;
735
895
  async function connectInspector(options) {
736
896
  const host = options.host ?? DEFAULT_HOST;
737
897
  const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
@@ -754,7 +914,6 @@ async function connectInspector(options) {
754
914
  }
755
915
  scripts.set(scriptId, { scriptId, url });
756
916
  });
757
- const PAUSE_BUFFER_LIMIT = 32;
758
917
  const pauseBuffer = [];
759
918
  const pauseWaitGate = { active: false };
760
919
  const debuggerState = {};
@@ -763,14 +922,14 @@ async function connectInspector(options) {
763
922
  return;
764
923
  }
765
924
  const params = raw;
766
- const event = toPauseEvent(params, performance.now());
925
+ const event = toPauseEvent(params, performance2.now());
767
926
  if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
768
927
  pauseBuffer.shift();
769
928
  }
770
929
  pauseBuffer.push(event);
771
930
  });
772
931
  client.on("Debugger.resumed", () => {
773
- debuggerState.lastResumedAtMs = performance.now();
932
+ debuggerState.lastResumedAtMs = performance2.now();
774
933
  });
775
934
  await client.send("Runtime.enable");
776
935
  await client.send("Debugger.enable");
@@ -790,279 +949,254 @@ async function connectInspector(options) {
790
949
  }
791
950
  };
792
951
  }
793
- function asString(value, fallback = "") {
794
- return typeof value === "string" ? value : fallback;
795
- }
796
- function asNumber(value, fallback = 0) {
797
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
952
+
953
+ // src/cli/output.ts
954
+ import process from "process";
955
+ function writeJson(value) {
956
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
957
+ `);
798
958
  }
799
- function toResolvedLocations(value) {
800
- if (!Array.isArray(value)) {
801
- return [];
959
+ function writeHumanSnapshot(snapshot) {
960
+ const pausedDuration = snapshot.pausedDurationMs === null ? "unknown" : `${snapshot.pausedDurationMs.toFixed(1)}ms`;
961
+ const lines = [];
962
+ lines.push(
963
+ `Snapshot @ ${snapshot.capturedAt}`,
964
+ ` reason: ${snapshot.reason}`,
965
+ ` paused: ${pausedDuration}`
966
+ );
967
+ if (snapshot.topFrame) {
968
+ appendFrameLines(lines, snapshot);
802
969
  }
803
- return value.flatMap((entry) => {
804
- if (typeof entry !== "object" || entry === null) {
805
- return [];
806
- }
807
- const candidate = entry;
808
- const scriptId = asString(candidate.scriptId);
809
- if (scriptId.length === 0) {
810
- return [];
970
+ if (snapshot.captures.length > 0) {
971
+ lines.push(" captures:");
972
+ for (const capture of snapshot.captures) {
973
+ const detail = capture.error ?? capture.value ?? "undefined";
974
+ lines.push(` ${capture.expression} = ${detail}`);
811
975
  }
812
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
813
- const lineNumber = asNumber(candidate.lineNumber);
814
- const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
815
- return [result];
816
- });
817
- }
818
- async function setBreakpoint(session, input) {
819
- const remoteRoot = input.remoteRoot ?? { kind: "none" };
820
- const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
821
- const params = {
822
- lineNumber: input.line - 1,
823
- urlRegex
824
- };
825
- if (input.condition !== void 0 && input.condition.length > 0) {
826
- params["condition"] = input.condition;
827
- }
828
- const result = await session.client.send(
829
- "Debugger.setBreakpointByUrl",
830
- params
831
- );
832
- const breakpointId = asString(result.breakpointId);
833
- if (breakpointId.length === 0) {
834
- throw new CfInspectorError(
835
- "CDP_REQUEST_FAILED",
836
- `setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
837
- );
838
976
  }
839
- return {
840
- breakpointId,
841
- file: input.file,
842
- line: input.line,
843
- urlRegex,
844
- resolvedLocations: toResolvedLocations(result.locations)
845
- };
846
- }
847
- async function removeBreakpoint(session, breakpointId) {
848
- await session.client.send("Debugger.removeBreakpoint", { breakpointId });
977
+ process.stdout.write(`${lines.join("\n")}
978
+ `);
849
979
  }
850
- function toScopeChain(value) {
851
- if (!Array.isArray(value)) {
852
- return [];
980
+ function appendFrameLines(lines, snapshot) {
981
+ const frame = snapshot.topFrame;
982
+ if (frame === void 0) {
983
+ return;
853
984
  }
854
- return value.flatMap((entry) => {
855
- if (typeof entry !== "object" || entry === null) {
856
- return [];
857
- }
858
- const candidate = entry;
859
- const type = asString(candidate.type);
860
- if (type.length === 0) {
861
- return [];
862
- }
863
- const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
864
- const name = typeof candidate.name === "string" ? candidate.name : void 0;
865
- const base = name === void 0 ? { type } : { type, name };
866
- return [objectId === void 0 ? base : { ...base, objectId }];
867
- });
868
- }
869
- function toCallFrames(value) {
870
- if (!Array.isArray(value)) {
871
- return [];
985
+ const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
986
+ const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
987
+ lines.push(
988
+ ` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
989
+ );
990
+ if (frame.scopes === void 0) {
991
+ return;
992
+ }
993
+ for (const scope of frame.scopes) {
994
+ lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
995
+ for (const variable of scope.variables) {
996
+ lines.push(` ${variable.name} = ${variable.value}`);
997
+ }
872
998
  }
873
- return value.flatMap((entry) => {
874
- if (typeof entry !== "object" || entry === null) {
875
- return [];
876
- }
877
- const candidate = entry;
878
- const callFrameId = asString(candidate.callFrameId);
879
- if (callFrameId.length === 0) {
880
- return [];
881
- }
882
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
883
- const lineNumber = asNumber(candidate.location?.lineNumber);
884
- const columnNumber = asNumber(candidate.location?.columnNumber);
885
- const base = {
886
- callFrameId,
887
- functionName: asString(candidate.functionName),
888
- lineNumber,
889
- columnNumber,
890
- scopeChain: toScopeChain(candidate.scopeChain)
891
- };
892
- return [url === void 0 ? base : { ...base, url }];
893
- });
894
999
  }
895
- function pauseMatches(pause, breakpointIds) {
896
- if (breakpointIds === void 0 || breakpointIds.length === 0) {
897
- return true;
1000
+ function writeLogEvent(event, json) {
1001
+ if (json) {
1002
+ process.stdout.write(`${JSON.stringify(event)}
1003
+ `);
1004
+ return;
898
1005
  }
899
- return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
1006
+ if (event.error !== void 0) {
1007
+ process.stdout.write(`[${event.ts}] ${event.at} !err ${event.error}
1008
+ `);
1009
+ return;
1010
+ }
1011
+ process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
1012
+ `);
900
1013
  }
901
- function toPauseEvent(params, receivedAtMs) {
1014
+
1015
+ // src/cf/tunnel.ts
1016
+ import { startDebugger } from "@saptools/cf-debugger";
1017
+ async function openCfTunnel(target) {
1018
+ const opts = {
1019
+ region: target.region,
1020
+ org: target.org,
1021
+ space: target.space,
1022
+ app: target.app,
1023
+ ...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
1024
+ ...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
1025
+ ...target.verbose === void 0 ? {} : { verbose: target.verbose },
1026
+ ...target.signal === void 0 ? {} : { signal: target.signal }
1027
+ };
1028
+ const handle = await startDebugger(opts);
902
1029
  return {
903
- reason: asString(params.reason),
904
- hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
905
- callFrames: toCallFrames(params.callFrames),
906
- receivedAtMs
1030
+ localPort: handle.session.localPort,
1031
+ handle,
1032
+ dispose: async () => {
1033
+ await handle.dispose();
1034
+ }
907
1035
  };
908
1036
  }
909
- function remainingUntil(deadlineMs) {
910
- return Math.max(0, deadlineMs - performance.now());
911
- }
912
- function topFrameLocation(pause) {
913
- const top = pause.callFrames[0];
914
- if (top === void 0) {
915
- return "(no call frame)";
1037
+
1038
+ // src/cli/target.ts
1039
+ init_types();
1040
+
1041
+ // src/cli/commandTypes.ts
1042
+ var DEFAULT_BREAKPOINT_TIMEOUT_SEC = 30;
1043
+ var DEFAULT_CF_TIMEOUT_SEC = 60;
1044
+
1045
+ // src/cli/target.ts
1046
+ function parsePositiveInt(raw, label) {
1047
+ if (raw === void 0) {
1048
+ return void 0;
916
1049
  }
917
- const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
918
- return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
919
- }
920
- function pauseDetail(pause) {
921
- return JSON.stringify({
922
- reason: pause.reason,
923
- hitBreakpoints: pause.hitBreakpoints,
924
- topFrame: topFrameLocation(pause)
925
- });
926
- }
927
- function hasResumedSincePause(session, pause) {
928
- const pauseAt = pause.receivedAtMs;
929
- const resumedAt = session.debuggerState.lastResumedAtMs;
930
- return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
1050
+ const value = Number.parseInt(raw, 10);
1051
+ if (Number.isNaN(value) || value <= 0 || value.toString() !== raw.trim()) {
1052
+ throw new CfInspectorError("INVALID_ARGUMENT", `Invalid ${label}: "${raw}" \u2014 expected a positive integer`);
1053
+ }
1054
+ return value;
931
1055
  }
932
- function throwBreakpointTimeout(timeoutMs) {
1056
+ function resolveTarget(opts) {
1057
+ const port = parsePositiveInt(opts.port, "--port");
1058
+ if (port !== void 0) {
1059
+ return { kind: "port", port, host: opts.host ?? "127.0.0.1" };
1060
+ }
1061
+ if (hasCfTarget(opts)) {
1062
+ const cfTimeoutSec = parsePositiveInt(opts.cfTimeout, "--cf-timeout") ?? DEFAULT_CF_TIMEOUT_SEC;
1063
+ return {
1064
+ kind: "cf",
1065
+ region: opts.region,
1066
+ org: opts.org,
1067
+ space: opts.space,
1068
+ app: opts.app,
1069
+ cfTimeoutMs: cfTimeoutSec * 1e3
1070
+ };
1071
+ }
933
1072
  throw new CfInspectorError(
934
- "BREAKPOINT_NOT_HIT",
935
- `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
1073
+ "MISSING_TARGET",
1074
+ "Provide either --port (and optionally --host) or all of --region, --org, --space, --app."
936
1075
  );
937
1076
  }
938
- function throwUnrelatedPauseTimeout(pause, timeoutMs) {
939
- throw new CfInspectorError(
940
- "UNRELATED_PAUSE_TIMEOUT",
941
- `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
942
- pauseDetail(pause)
943
- );
1077
+ function hasCfTarget(opts) {
1078
+ return opts.region !== void 0 && opts.org !== void 0 && opts.space !== void 0 && opts.app !== void 0;
944
1079
  }
945
- async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
946
- if (hasResumedSincePause(session, pause)) {
947
- return;
948
- }
949
- const remainingMs = remainingUntil(deadlineMs);
950
- if (remainingMs <= 0) {
951
- throwUnrelatedPauseTimeout(pause, timeoutMs);
952
- }
1080
+ async function withSession(target, fn) {
1081
+ const tunnel = await openTarget(target);
1082
+ let session;
953
1083
  try {
954
- await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
955
- session.debuggerState.lastResumedAtMs = performance.now();
956
- } catch (err) {
957
- if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
958
- throwUnrelatedPauseTimeout(pause, timeoutMs);
1084
+ session = await connectInspector({ port: tunnel.port, host: tunnel.host });
1085
+ return await fn(session, tunnel.port);
1086
+ } finally {
1087
+ if (session) {
1088
+ await session.dispose();
959
1089
  }
960
- throw err;
1090
+ await tunnel.dispose();
961
1091
  }
962
1092
  }
963
- async function handleUnmatchedPause(session, pause, options, deadlineMs) {
964
- if (options.unmatchedPausePolicy === "fail") {
965
- throw new CfInspectorError(
966
- "UNRELATED_PAUSE",
967
- "Target paused before this command's breakpoint was reached",
968
- pauseDetail(pause)
969
- );
970
- }
971
- if (hasResumedSincePause(session, pause)) {
972
- return;
1093
+ async function openTarget(target) {
1094
+ if (target.kind === "port") {
1095
+ return {
1096
+ port: target.port,
1097
+ host: target.host,
1098
+ dispose: () => Promise.resolve()
1099
+ };
973
1100
  }
974
- options.onUnmatchedPause?.(pause);
975
- await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
976
- }
977
- async function waitForPause(session, options) {
978
- const deadlineMs = performance.now() + options.timeoutMs;
979
- const buffer = session.pauseBuffer;
980
- while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
981
- while (buffer.length > 0) {
982
- const buffered = buffer.shift();
983
- if (buffered === void 0) {
984
- continue;
985
- }
986
- if (pauseMatches(buffered, options.breakpointIds)) {
987
- return buffered;
988
- }
989
- await handleUnmatchedPause(session, buffered, options, deadlineMs);
990
- }
991
- const remainingMs = remainingUntil(deadlineMs);
992
- if (remainingMs <= 0) {
993
- throwBreakpointTimeout(options.timeoutMs);
994
- }
995
- session.pauseWaitGate.active = true;
996
- let receivedAtMs;
997
- let params;
998
- try {
999
- params = await session.client.waitFor("Debugger.paused", {
1000
- timeoutMs: remainingMs,
1001
- predicate: () => {
1002
- receivedAtMs = performance.now();
1003
- return true;
1004
- }
1005
- });
1006
- } finally {
1007
- session.pauseWaitGate.active = false;
1101
+ const tunnel = await openCfTunnel({
1102
+ region: target.region,
1103
+ org: target.org,
1104
+ space: target.space,
1105
+ app: target.app,
1106
+ tunnelReadyTimeoutMs: target.cfTimeoutMs
1107
+ });
1108
+ return {
1109
+ port: tunnel.localPort,
1110
+ host: "127.0.0.1",
1111
+ dispose: async () => {
1112
+ await tunnel.dispose();
1008
1113
  }
1009
- const pause = toPauseEvent(params, receivedAtMs ?? performance.now());
1010
- if (pauseMatches(pause, options.breakpointIds)) {
1011
- return pause;
1114
+ };
1115
+ }
1116
+
1117
+ // src/cli/commands/attach.ts
1118
+ async function handleAttach(opts) {
1119
+ const target = resolveTarget(opts);
1120
+ const tunnel = await openTarget(target);
1121
+ try {
1122
+ const version = await fetchInspectorVersion(tunnel.host, tunnel.port, 5e3);
1123
+ if (opts.json) {
1124
+ writeJson({ host: tunnel.host, port: tunnel.port, ...version });
1125
+ return;
1012
1126
  }
1013
- await handleUnmatchedPause(session, pause, options, deadlineMs);
1127
+ process2.stdout.write(
1128
+ `Connected to ${tunnel.host}:${tunnel.port.toString()}
1129
+ Browser: ${version.browser}
1130
+ Protocol: ${version.protocolVersion}
1131
+ `
1132
+ );
1133
+ } finally {
1134
+ await tunnel.dispose();
1014
1135
  }
1015
- throwBreakpointTimeout(options.timeoutMs);
1016
- }
1017
- async function resume(session) {
1018
- await session.client.send("Debugger.resume");
1019
- }
1020
- async function evaluateOnFrame(session, callFrameId, expression) {
1021
- return await session.client.send("Debugger.evaluateOnCallFrame", {
1022
- callFrameId,
1023
- expression,
1024
- returnByValue: false,
1025
- generatePreview: true,
1026
- silent: true
1027
- });
1028
1136
  }
1029
- async function evaluateGlobal(session, expression) {
1030
- return await session.client.send("Runtime.evaluate", {
1031
- expression,
1032
- returnByValue: false,
1033
- generatePreview: true,
1034
- silent: true
1137
+
1138
+ // src/cli/commands/eval.ts
1139
+ import process3 from "process";
1140
+ async function handleEval(opts) {
1141
+ const target = resolveTarget(opts);
1142
+ const result = await withSession(target, async (session) => {
1143
+ return await evaluateGlobal(session, opts.expr);
1035
1144
  });
1145
+ if (opts.json) {
1146
+ writeJson(result);
1147
+ if (result.exceptionDetails !== void 0) {
1148
+ process3.exitCode = 1;
1149
+ }
1150
+ return;
1151
+ }
1152
+ writeHumanEvalResult(result);
1036
1153
  }
1037
- function listScripts(session) {
1038
- return [...session.scripts.values()];
1154
+ function writeHumanEvalResult(result) {
1155
+ if (result.exceptionDetails !== void 0) {
1156
+ const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1157
+ process3.stderr.write(`${detail}
1158
+ `);
1159
+ process3.exitCode = 1;
1160
+ return;
1161
+ }
1162
+ const inner = result.result;
1163
+ if (inner === void 0) {
1164
+ process3.stdout.write("\n");
1165
+ return;
1166
+ }
1167
+ if (typeof inner.value === "string") {
1168
+ process3.stdout.write(`${inner.value}
1169
+ `);
1170
+ return;
1171
+ }
1172
+ if (typeof inner.description === "string") {
1173
+ process3.stdout.write(`${inner.description}
1174
+ `);
1175
+ return;
1176
+ }
1177
+ process3.stdout.write(`${JSON.stringify(inner.value)}
1178
+ `);
1039
1179
  }
1040
- async function validateExpression(session, expression) {
1041
- const result = await session.client.send("Runtime.compileScript", {
1042
- expression,
1043
- sourceURL: "<cf-inspector-validate>",
1044
- persistScript: false
1045
- });
1046
- if (result.exceptionDetails === void 0) {
1180
+
1181
+ // src/cli/commands/listScripts.ts
1182
+ import process4 from "process";
1183
+ async function handleListScripts(opts) {
1184
+ const target = resolveTarget(opts);
1185
+ const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
1186
+ if (opts.json) {
1187
+ writeJson(scripts);
1047
1188
  return;
1048
1189
  }
1049
- const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
1050
- throw new CfInspectorError("INVALID_EXPRESSION", description);
1051
- }
1052
- async function getProperties(session, objectId) {
1053
- const result = await session.client.send("Runtime.getProperties", {
1054
- objectId,
1055
- ownProperties: true,
1056
- accessorPropertiesOnly: false,
1057
- generatePreview: true
1058
- });
1059
- if (!Array.isArray(result.result)) {
1060
- return [];
1190
+ for (const script of scripts) {
1191
+ process4.stdout.write(`${script.scriptId} ${script.url}
1192
+ `);
1061
1193
  }
1062
- return result.result;
1063
1194
  }
1064
1195
 
1065
- // src/logpoint.ts
1196
+ // src/cli/commands/log.ts
1197
+ import process6 from "process";
1198
+
1199
+ // src/logpoint/condition.ts
1066
1200
  import { randomBytes } from "crypto";
1067
1201
  var SENTINEL_PREFIX = "__CFI_LOG_";
1068
1202
  var SENTINEL_SUFFIX = "__";
@@ -1081,6 +1215,11 @@ function buildLogpointCondition(sentinel, expression) {
1081
1215
  "})()"
1082
1216
  ].join("");
1083
1217
  }
1218
+ function generateSentinel() {
1219
+ return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1220
+ }
1221
+
1222
+ // src/logpoint/events.ts
1084
1223
  function asString2(value) {
1085
1224
  return typeof value === "string" ? value : void 0;
1086
1225
  }
@@ -1113,6 +1252,9 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
1113
1252
  if (payload.startsWith("!err:")) {
1114
1253
  return { ts, at, error: payload.slice("!err:".length) };
1115
1254
  }
1255
+ return parsePayload(ts, at, payload);
1256
+ }
1257
+ function parsePayload(ts, at, payload) {
1116
1258
  try {
1117
1259
  const parsed = JSON.parse(payload);
1118
1260
  if (typeof parsed === "string") {
@@ -1123,20 +1265,14 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
1123
1265
  return { ts, at, value: payload, raw: payload };
1124
1266
  }
1125
1267
  }
1126
- function generateSentinel() {
1127
- return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1128
- }
1268
+
1269
+ // src/logpoint/stream.ts
1129
1270
  async function streamLogpoint(session, options) {
1130
1271
  const sentinel = generateSentinel();
1131
1272
  const condition = buildLogpointCondition(sentinel, options.expression);
1132
1273
  let emitted = 0;
1133
1274
  const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1134
- const params = raw;
1135
- if (asString2(params.type) !== "log") {
1136
- return;
1137
- }
1138
- const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
1139
- const event = parseLogEvent(params.args, sentinel, options.location, ts);
1275
+ const event = toLogpointEvent(raw, sentinel, options.location);
1140
1276
  if (event === void 0) {
1141
1277
  return;
1142
1278
  }
@@ -1156,18 +1292,26 @@ async function streamLogpoint(session, options) {
1156
1292
  throw err;
1157
1293
  }
1158
1294
  options.onBreakpointSet?.(handle);
1159
- const cleanup = async () => {
1160
- offEvent();
1161
- try {
1162
- await removeBreakpoint(session, handle.breakpointId);
1163
- } catch {
1164
- }
1165
- };
1166
1295
  try {
1167
1296
  const reason = await waitForStop(session, options);
1168
1297
  return { handle, sentinel, emitted, stoppedReason: reason };
1169
1298
  } finally {
1170
- await cleanup();
1299
+ offEvent();
1300
+ await removeBreakpointBestEffort(session, handle.breakpointId);
1301
+ }
1302
+ }
1303
+ function toLogpointEvent(raw, sentinel, location) {
1304
+ const params = raw;
1305
+ if (asString2(params.type) !== "log") {
1306
+ return void 0;
1307
+ }
1308
+ const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
1309
+ return parseLogEvent(params.args, sentinel, location, ts);
1310
+ }
1311
+ async function removeBreakpointBestEffort(session, breakpointId) {
1312
+ try {
1313
+ await removeBreakpoint(session, breakpointId);
1314
+ } catch {
1171
1315
  }
1172
1316
  }
1173
1317
  async function waitForStop(session, options) {
@@ -1204,60 +1348,108 @@ async function waitForStop(session, options) {
1204
1348
  });
1205
1349
  }
1206
1350
 
1207
- // src/snapshot.ts
1351
+ // src/cli/commands/log.ts
1208
1352
  init_types();
1209
- var MAX_SCOPES = 3;
1210
- var MAX_SCOPE_VARIABLES = 20;
1211
- var MAX_CHILD_VARIABLES = 8;
1212
- var MAX_VARIABLE_DEPTH = 2;
1213
- var DEFAULT_MAX_VALUE_LENGTH = 4096;
1214
- var PRIORITY_BY_TYPE = {
1215
- local: 0,
1216
- arguments: 1,
1217
- block: 2,
1218
- closure: 3,
1219
- catch: 4,
1220
- with: 5,
1221
- module: 6,
1222
- script: 7
1223
- };
1224
- function buildDescribed(value, type, objectId) {
1225
- const base = { value };
1226
- if (type !== void 0) {
1227
- base.type = type;
1228
- }
1229
- if (objectId !== void 0) {
1230
- base.objectId = objectId;
1353
+
1354
+ // src/cli/warnings.ts
1355
+ import process5 from "process";
1356
+ function warnOnUnboundBreakpoints(handles) {
1357
+ for (const handle of handles) {
1358
+ if (handle.resolvedLocations.length === 0) {
1359
+ process5.stderr.write(
1360
+ `[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.
1361
+ `
1362
+ );
1363
+ }
1231
1364
  }
1232
- return base;
1233
1365
  }
1234
- function describeProperty(prop) {
1235
- const value = prop.value;
1236
- if (value === void 0) {
1237
- return { value: "undefined" };
1238
- }
1239
- const type = typeof value.type === "string" ? value.type : void 0;
1240
- const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
1241
- if (type === "undefined") {
1242
- return buildDescribed("undefined", type);
1243
- }
1244
- if (type === "string" && typeof value.value === "string") {
1245
- return buildDescribed(JSON.stringify(value.value), type);
1246
- }
1247
- if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
1248
- return buildDescribed(formatPrimitive(value.value), type);
1366
+ function roundDurationMs(durationMs) {
1367
+ return Math.round(durationMs * 1e3) / 1e3;
1368
+ }
1369
+ function warnOnUnmatchedPause(pause) {
1370
+ const reason = pause.reason.length > 0 ? pause.reason : "unknown";
1371
+ process5.stderr.write(
1372
+ `[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
1373
+ `
1374
+ );
1375
+ }
1376
+ function withPausedDuration(snapshot, pausedDurationMs) {
1377
+ return {
1378
+ reason: snapshot.reason,
1379
+ hitBreakpoints: snapshot.hitBreakpoints,
1380
+ capturedAt: snapshot.capturedAt,
1381
+ pausedDurationMs,
1382
+ ...snapshot.topFrame === void 0 ? {} : { topFrame: snapshot.topFrame },
1383
+ captures: snapshot.captures
1384
+ };
1385
+ }
1386
+ function formatPauseLocation(pause) {
1387
+ const top = pause.callFrames[0];
1388
+ if (top === void 0) {
1389
+ return "(no call frame)";
1249
1390
  }
1250
- if (typeof value.description === "string") {
1251
- return buildDescribed(value.description, type, objectId);
1391
+ const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
1392
+ return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
1393
+ }
1394
+
1395
+ // src/cli/commands/log.ts
1396
+ async function handleLog(opts) {
1397
+ const target = resolveTarget(opts);
1398
+ const location = parseBreakpointSpec(opts.at);
1399
+ const remoteRoot = parseRemoteRoot(opts.remoteRoot);
1400
+ const durationSec = parsePositiveInt(opts.duration, "--duration");
1401
+ const expression = opts.expr.trim();
1402
+ if (expression.length === 0) {
1403
+ throw new CfInspectorError("INVALID_BREAKPOINT", "--expr must not be empty");
1252
1404
  }
1253
- if (isPrimitive(value.value)) {
1254
- return buildDescribed(formatPrimitive(value.value), type);
1405
+ const abort = new AbortController();
1406
+ const onSig = () => {
1407
+ abort.abort();
1408
+ };
1409
+ process6.once("SIGINT", onSig);
1410
+ process6.once("SIGTERM", onSig);
1411
+ try {
1412
+ await withSession(target, async (session) => {
1413
+ await validateExpression(session, expression);
1414
+ const result = await streamLogpoint(session, {
1415
+ location,
1416
+ expression,
1417
+ remoteRoot,
1418
+ ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
1419
+ signal: abort.signal,
1420
+ onEvent: (event) => {
1421
+ writeLogEvent(event, opts.json);
1422
+ },
1423
+ onBreakpointSet: (handle) => {
1424
+ warnOnUnboundBreakpoints([handle]);
1425
+ }
1426
+ });
1427
+ writeLogSummary(result.stoppedReason, result.emitted, opts.json);
1428
+ });
1429
+ } finally {
1430
+ process6.off("SIGINT", onSig);
1431
+ process6.off("SIGTERM", onSig);
1255
1432
  }
1256
- if (objectId === void 0) {
1257
- return buildDescribed("undefined", type);
1433
+ }
1434
+ function writeLogSummary(stoppedReason, emitted, json) {
1435
+ if (json) {
1436
+ process6.stderr.write(`${JSON.stringify({ stopped: stoppedReason, emitted })}
1437
+ `);
1438
+ return;
1258
1439
  }
1259
- return buildDescribed("[object]", type, objectId);
1440
+ process6.stderr.write(
1441
+ `Stopped (${stoppedReason}); emitted ${emitted.toString()} log ${emitted === 1 ? "entry" : "entries"}.
1442
+ `
1443
+ );
1260
1444
  }
1445
+
1446
+ // src/cli/commands/snapshot.ts
1447
+ import { performance as performance3 } from "perf_hooks";
1448
+ import process7 from "process";
1449
+
1450
+ // src/snapshot/values.ts
1451
+ init_types();
1452
+ var DEFAULT_MAX_VALUE_LENGTH = 4096;
1261
1453
  function isPrimitive(value) {
1262
1454
  const t = typeof value;
1263
1455
  return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
@@ -1269,129 +1461,25 @@ function formatPrimitive(value) {
1269
1461
  if (typeof value === "bigint") {
1270
1462
  return `${value.toString()}n`;
1271
1463
  }
1272
- return String(value);
1273
- }
1274
- function resolveMaxValueLength(value) {
1275
- if (value === void 0) {
1276
- return DEFAULT_MAX_VALUE_LENGTH;
1277
- }
1278
- if (!Number.isInteger(value) || value <= 0) {
1279
- throw new CfInspectorError(
1280
- "INVALID_ARGUMENT",
1281
- `Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
1282
- );
1283
- }
1284
- return value;
1285
- }
1286
- function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1287
- if (raw.length <= maxValueLength) {
1288
- return raw;
1289
- }
1290
- return `${raw.slice(0, maxValueLength)}...`;
1291
- }
1292
- function isExpandable(type) {
1293
- return type === "object" || type === "function";
1294
- }
1295
- async function captureProperties(session, objectId, limit, depth, maxValueLength) {
1296
- const properties = await getProperties(session, objectId);
1297
- const limited = properties.slice(0, limit);
1298
- const variables = await Promise.all(
1299
- limited.map(async (prop) => {
1300
- const name = typeof prop.name === "string" ? prop.name : "?";
1301
- const described = describeProperty(prop);
1302
- let children;
1303
- if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
1304
- try {
1305
- const nested = await captureProperties(
1306
- session,
1307
- described.objectId,
1308
- MAX_CHILD_VARIABLES,
1309
- depth - 1,
1310
- maxValueLength
1311
- );
1312
- if (nested.length > 0) {
1313
- children = nested;
1314
- }
1315
- } catch {
1316
- }
1317
- }
1318
- const sanitizedValue = limitValueLength(described.value, maxValueLength);
1319
- const base = { name, value: sanitizedValue };
1320
- const withType = described.type === void 0 ? base : { ...base, type: described.type };
1321
- return children === void 0 ? withType : { ...withType, children };
1322
- })
1323
- );
1324
- return variables;
1325
- }
1326
- function selectScopes(scopeChain) {
1327
- const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
1328
- return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
1329
- }
1330
- function priorityOf(type) {
1331
- return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
1332
- }
1333
- async function captureScopes(session, frame, maxValueLength) {
1334
- const scopes = selectScopes(frame.scopeChain);
1335
- return await Promise.all(
1336
- scopes.map(async (scope) => {
1337
- const objectId = scope.objectId;
1338
- if (objectId === void 0) {
1339
- return { type: scope.type, variables: [] };
1340
- }
1341
- try {
1342
- const variables = await captureProperties(
1343
- session,
1344
- objectId,
1345
- MAX_SCOPE_VARIABLES,
1346
- MAX_VARIABLE_DEPTH,
1347
- maxValueLength
1348
- );
1349
- return { type: scope.type, variables };
1350
- } catch {
1351
- return { type: scope.type, variables: [] };
1352
- }
1353
- })
1354
- );
1355
- }
1356
- function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1357
- if (result.exceptionDetails !== void 0) {
1358
- const text = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1359
- return { expression, error: limitValueLength(text, maxValueLength) };
1360
- }
1361
- const inner = result.result;
1362
- if (!inner) {
1363
- return { expression, error: "no result returned" };
1364
- }
1365
- const type = typeof inner.type === "string" ? inner.type : void 0;
1366
- const buildCaptured = (rendered) => {
1367
- const sanitized = limitValueLength(rendered, maxValueLength);
1368
- const base = { expression, value: sanitized };
1369
- return type === void 0 ? base : { ...base, type };
1370
- };
1371
- if (type === "string" && typeof inner.value === "string") {
1372
- return buildCaptured(JSON.stringify(inner.value));
1373
- }
1374
- if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
1375
- return buildCaptured(formatPrimitive(inner.value));
1376
- }
1377
- if (typeof inner.description === "string") {
1378
- return buildCaptured(inner.description);
1379
- }
1380
- if (isPrimitive(inner.value)) {
1381
- return buildCaptured(formatPrimitive(inner.value));
1382
- }
1383
- return buildCaptured("undefined");
1464
+ return String(value);
1384
1465
  }
1385
- function objectIdFromEvalResult(result) {
1386
- const inner = result.result;
1387
- if (inner?.type !== "object") {
1388
- return void 0;
1466
+ function resolveMaxValueLength(value) {
1467
+ if (value === void 0) {
1468
+ return DEFAULT_MAX_VALUE_LENGTH;
1389
1469
  }
1390
- const objectId = inner.objectId;
1391
- if (typeof objectId !== "string" || objectId.length === 0) {
1392
- return void 0;
1470
+ if (!Number.isInteger(value) || value <= 0) {
1471
+ throw new CfInspectorError(
1472
+ "INVALID_ARGUMENT",
1473
+ `Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
1474
+ );
1393
1475
  }
1394
- return objectId;
1476
+ return value;
1477
+ }
1478
+ function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1479
+ if (raw.length <= maxValueLength) {
1480
+ return raw;
1481
+ }
1482
+ return `${raw.slice(0, maxValueLength)}...`;
1395
1483
  }
1396
1484
  function parseQuotedString(value) {
1397
1485
  try {
@@ -1459,6 +1547,134 @@ function toStructuredValue(variable) {
1459
1547
  }
1460
1548
  return out;
1461
1549
  }
1550
+
1551
+ // src/snapshot/evaluation.ts
1552
+ function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1553
+ if (result.exceptionDetails !== void 0) {
1554
+ return { expression, error: readEvalError(result, maxValueLength) };
1555
+ }
1556
+ const inner = result.result;
1557
+ if (!inner) {
1558
+ return { expression, error: "no result returned" };
1559
+ }
1560
+ const type = typeof inner.type === "string" ? inner.type : void 0;
1561
+ const buildCaptured = (rendered) => {
1562
+ const sanitized = limitValueLength(rendered, maxValueLength);
1563
+ const base = { expression, value: sanitized };
1564
+ return type === void 0 ? base : { ...base, type };
1565
+ };
1566
+ if (type === "string" && typeof inner.value === "string") {
1567
+ return buildCaptured(JSON.stringify(inner.value));
1568
+ }
1569
+ if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
1570
+ return buildCaptured(formatPrimitive(inner.value));
1571
+ }
1572
+ if (typeof inner.description === "string") {
1573
+ return buildCaptured(inner.description);
1574
+ }
1575
+ if (isPrimitive(inner.value)) {
1576
+ return buildCaptured(formatPrimitive(inner.value));
1577
+ }
1578
+ return buildCaptured("undefined");
1579
+ }
1580
+ function readEvalError(result, maxValueLength) {
1581
+ const text = typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1582
+ return limitValueLength(text, maxValueLength);
1583
+ }
1584
+
1585
+ // src/snapshot/properties.ts
1586
+ var MAX_SCOPE_VARIABLES = 20;
1587
+ var MAX_CHILD_VARIABLES = 8;
1588
+ var MAX_VARIABLE_DEPTH = 2;
1589
+ function buildDescribed(value, type, objectId) {
1590
+ const base = { value };
1591
+ if (type !== void 0) {
1592
+ base.type = type;
1593
+ }
1594
+ if (objectId !== void 0) {
1595
+ base.objectId = objectId;
1596
+ }
1597
+ return base;
1598
+ }
1599
+ function describeProperty(prop) {
1600
+ const value = prop.value;
1601
+ if (value === void 0) {
1602
+ return { value: "undefined" };
1603
+ }
1604
+ const type = typeof value.type === "string" ? value.type : void 0;
1605
+ const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
1606
+ if (type === "undefined") {
1607
+ return buildDescribed("undefined", type);
1608
+ }
1609
+ if (type === "string" && typeof value.value === "string") {
1610
+ return buildDescribed(JSON.stringify(value.value), type);
1611
+ }
1612
+ if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
1613
+ return buildDescribed(formatPrimitive(value.value), type);
1614
+ }
1615
+ if (typeof value.description === "string") {
1616
+ return buildDescribed(value.description, type, objectId);
1617
+ }
1618
+ if (isPrimitive(value.value)) {
1619
+ return buildDescribed(formatPrimitive(value.value), type);
1620
+ }
1621
+ if (objectId === void 0) {
1622
+ return buildDescribed("undefined", type);
1623
+ }
1624
+ return buildDescribed("[object]", type, objectId);
1625
+ }
1626
+ function isExpandable(type) {
1627
+ return type === "object" || type === "function";
1628
+ }
1629
+ async function captureProperties(session, objectId, limit, depth, maxValueLength) {
1630
+ const properties = await getProperties(session, objectId);
1631
+ const limited = properties.slice(0, limit);
1632
+ const variables = await Promise.all(
1633
+ limited.map(async (prop) => {
1634
+ return await captureProperty(session, prop, depth, maxValueLength);
1635
+ })
1636
+ );
1637
+ return variables;
1638
+ }
1639
+ async function captureProperty(session, prop, depth, maxValueLength) {
1640
+ const name = typeof prop.name === "string" ? prop.name : "?";
1641
+ const described = describeProperty(prop);
1642
+ const children = await capturePropertyChildren(session, described, depth, maxValueLength);
1643
+ const sanitizedValue = limitValueLength(described.value, maxValueLength);
1644
+ const base = { name, value: sanitizedValue };
1645
+ const withType = described.type === void 0 ? base : { ...base, type: described.type };
1646
+ return children === void 0 ? withType : { ...withType, children };
1647
+ }
1648
+ async function capturePropertyChildren(session, described, depth, maxValueLength) {
1649
+ if (depth <= 0 || described.objectId === void 0 || !isExpandable(described.type)) {
1650
+ return void 0;
1651
+ }
1652
+ try {
1653
+ const nested = await captureProperties(
1654
+ session,
1655
+ described.objectId,
1656
+ MAX_CHILD_VARIABLES,
1657
+ depth - 1,
1658
+ maxValueLength
1659
+ );
1660
+ return nested.length > 0 ? nested : void 0;
1661
+ } catch {
1662
+ return void 0;
1663
+ }
1664
+ }
1665
+
1666
+ // src/snapshot/objects.ts
1667
+ function objectIdFromEvalResult(result) {
1668
+ const inner = result.result;
1669
+ if (inner?.type !== "object") {
1670
+ return void 0;
1671
+ }
1672
+ const objectId = inner.objectId;
1673
+ if (typeof objectId !== "string" || objectId.length === 0) {
1674
+ return void 0;
1675
+ }
1676
+ return objectId;
1677
+ }
1462
1678
  async function renderObjectCapture(session, objectId, maxValueLength) {
1463
1679
  try {
1464
1680
  const properties = await captureProperties(
@@ -1505,6 +1721,51 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
1505
1721
  const value = limitValueLength(normalized, maxValueLength);
1506
1722
  return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
1507
1723
  }
1724
+
1725
+ // src/snapshot/scopes.ts
1726
+ var MAX_SCOPES = 3;
1727
+ var PRIORITY_BY_TYPE = {
1728
+ local: 0,
1729
+ arguments: 1,
1730
+ block: 2,
1731
+ closure: 3,
1732
+ catch: 4,
1733
+ with: 5,
1734
+ module: 6,
1735
+ script: 7
1736
+ };
1737
+ function selectScopes(scopeChain) {
1738
+ const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
1739
+ return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
1740
+ }
1741
+ function priorityOf(type) {
1742
+ return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
1743
+ }
1744
+ async function captureScopes(session, frame, maxValueLength) {
1745
+ const scopes = selectScopes(frame.scopeChain);
1746
+ return await Promise.all(
1747
+ scopes.map(async (scope) => {
1748
+ const objectId = scope.objectId;
1749
+ if (objectId === void 0) {
1750
+ return { type: scope.type, variables: [] };
1751
+ }
1752
+ try {
1753
+ const variables = await captureProperties(
1754
+ session,
1755
+ objectId,
1756
+ MAX_SCOPE_VARIABLES,
1757
+ MAX_VARIABLE_DEPTH,
1758
+ maxValueLength
1759
+ );
1760
+ return { type: scope.type, variables };
1761
+ } catch {
1762
+ return { type: scope.type, variables: [] };
1763
+ }
1764
+ })
1765
+ );
1766
+ }
1767
+
1768
+ // src/snapshot/capture.ts
1508
1769
  async function captureSnapshot(session, pause, options = {}) {
1509
1770
  const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1510
1771
  const top = pause.callFrames[0];
@@ -1521,169 +1782,131 @@ async function captureSnapshot(session, pause, options = {}) {
1521
1782
  const scopes = await captureScopes(session, top, maxValueLength);
1522
1783
  topFrame = { ...topFrame, scopes };
1523
1784
  }
1524
- if (options.captures !== void 0 && options.captures.length > 0) {
1525
- captures = await Promise.all(
1526
- options.captures.map(async (expression) => {
1527
- try {
1528
- const result = await evaluateOnFrame(session, top.callFrameId, expression);
1529
- const captured = evalResultToCaptured(expression, result, maxValueLength);
1530
- return await withSerializedObjectCapture(
1531
- session,
1532
- expression,
1533
- result,
1534
- captured,
1535
- maxValueLength
1536
- );
1537
- } catch (err) {
1538
- const message = err instanceof Error ? err.message : String(err);
1539
- return { expression, error: limitValueLength(message, maxValueLength) };
1540
- }
1541
- })
1542
- );
1543
- }
1785
+ captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
1544
1786
  }
1545
1787
  return {
1546
1788
  reason: pause.reason,
1547
1789
  hitBreakpoints: pause.hitBreakpoints,
1548
1790
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1549
1791
  ...topFrame === void 0 ? {} : { topFrame },
1550
- captures
1551
- };
1552
- }
1553
-
1554
- // src/tunnel.ts
1555
- import { startDebugger } from "@saptools/cf-debugger";
1556
- async function openCfTunnel(target) {
1557
- const opts = {
1558
- region: target.region,
1559
- org: target.org,
1560
- space: target.space,
1561
- app: target.app,
1562
- ...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
1563
- ...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
1564
- ...target.verbose === void 0 ? {} : { verbose: target.verbose },
1565
- ...target.signal === void 0 ? {} : { signal: target.signal }
1566
- };
1567
- const handle = await startDebugger(opts);
1568
- return {
1569
- localPort: handle.session.localPort,
1570
- handle,
1571
- dispose: async () => {
1572
- await handle.dispose();
1573
- }
1574
- };
1575
- }
1576
-
1577
- // src/cli.ts
1578
- init_types();
1579
- var DEFAULT_BREAKPOINT_TIMEOUT_SEC = 30;
1580
- var DEFAULT_CF_TIMEOUT_SEC = 60;
1581
- function parsePositiveInt(raw, label) {
1582
- if (raw === void 0) {
1583
- return void 0;
1584
- }
1585
- const value = Number.parseInt(raw, 10);
1586
- if (Number.isNaN(value) || value <= 0 || value.toString() !== raw.trim()) {
1587
- throw new CfInspectorError("INVALID_ARGUMENT", `Invalid ${label}: "${raw}" \u2014 expected a positive integer`);
1588
- }
1589
- return value;
1590
- }
1591
- function resolveTarget(opts) {
1592
- const port = parsePositiveInt(opts.port, "--port");
1593
- if (port !== void 0) {
1594
- return { kind: "port", port, host: opts.host ?? "127.0.0.1" };
1595
- }
1596
- if (opts.region !== void 0 && opts.org !== void 0 && opts.space !== void 0 && opts.app !== void 0) {
1597
- const cfTimeoutSec = parsePositiveInt(opts.cfTimeout, "--cf-timeout") ?? DEFAULT_CF_TIMEOUT_SEC;
1598
- return {
1599
- kind: "cf",
1600
- region: opts.region,
1601
- org: opts.org,
1602
- space: opts.space,
1603
- app: opts.app,
1604
- cfTimeoutMs: cfTimeoutSec * 1e3
1605
- };
1606
- }
1607
- throw new CfInspectorError(
1608
- "MISSING_TARGET",
1609
- "Provide either --port (and optionally --host) or all of --region, --org, --space, --app."
1610
- );
1611
- }
1612
- async function openTarget(target) {
1613
- if (target.kind === "port") {
1614
- return {
1615
- port: target.port,
1616
- host: target.host,
1617
- dispose: () => Promise.resolve()
1618
- };
1619
- }
1620
- const tunnel = await openCfTunnel({
1621
- region: target.region,
1622
- org: target.org,
1623
- space: target.space,
1624
- app: target.app,
1625
- tunnelReadyTimeoutMs: target.cfTimeoutMs
1626
- });
1627
- return {
1628
- port: tunnel.localPort,
1629
- host: "127.0.0.1",
1630
- dispose: async () => {
1631
- await tunnel.dispose();
1632
- }
1792
+ captures
1633
1793
  };
1634
1794
  }
1635
- async function withSession(target, fn) {
1636
- const tunnel = await openTarget(target);
1637
- let session;
1795
+ async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1796
+ if (captures === void 0 || captures.length === 0) {
1797
+ return [];
1798
+ }
1799
+ return await Promise.all(
1800
+ captures.map(async (expression) => {
1801
+ return await captureExpression(session, callFrameId, expression, maxValueLength);
1802
+ })
1803
+ );
1804
+ }
1805
+ async function captureExpression(session, callFrameId, expression, maxValueLength) {
1638
1806
  try {
1639
- session = await connectInspector({ port: tunnel.port, host: tunnel.host });
1640
- return await fn(session, tunnel.port);
1641
- } finally {
1642
- if (session) {
1643
- await session.dispose();
1644
- }
1645
- await tunnel.dispose();
1807
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1808
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1809
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1810
+ } catch (err) {
1811
+ const message = err instanceof Error ? err.message : String(err);
1812
+ return { expression, error: limitValueLength(message, maxValueLength) };
1646
1813
  }
1647
1814
  }
1648
- function warnOnUnboundBreakpoints(handles) {
1649
- for (const handle of handles) {
1650
- if (handle.resolvedLocations.length === 0) {
1651
- process2.stderr.write(
1652
- `[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.
1653
- `
1654
- );
1655
- }
1815
+
1816
+ // src/cli/commands/snapshot.ts
1817
+ init_types();
1818
+
1819
+ // src/cli/captureParser.ts
1820
+ function parseCaptureList(raw) {
1821
+ if (raw === void 0 || raw.trim().length === 0) {
1822
+ return [];
1656
1823
  }
1824
+ return splitCaptureExpressions(raw);
1657
1825
  }
1658
- function roundDurationMs(durationMs) {
1659
- return Math.round(durationMs * 1e3) / 1e3;
1826
+ function isQuoteChar(value) {
1827
+ return value === "'" || value === '"' || value === "`";
1660
1828
  }
1661
- function formatPauseLocation(pause) {
1662
- const top = pause.callFrames[0];
1663
- if (top === void 0) {
1664
- return "(no call frame)";
1829
+ function consumeQuotedChar(state, char) {
1830
+ if (state.quote === void 0) {
1831
+ return false;
1665
1832
  }
1666
- const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
1667
- return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
1833
+ if (state.escaped) {
1834
+ state.escaped = false;
1835
+ return true;
1836
+ }
1837
+ if (char === "\\") {
1838
+ state.escaped = true;
1839
+ return true;
1840
+ }
1841
+ if (char === state.quote) {
1842
+ state.quote = void 0;
1843
+ }
1844
+ return true;
1668
1845
  }
1669
- function warnOnUnmatchedPause(pause) {
1670
- const reason = pause.reason.length > 0 ? pause.reason : "unknown";
1671
- process2.stderr.write(
1672
- `[cf-inspector] warning: target is paused by another debugger event (${reason} at ${formatPauseLocation(pause)}); waiting for it to resume...
1673
- `
1674
- );
1846
+ function updateCaptureDepth(state, char) {
1847
+ if (char === "(") {
1848
+ state.parenDepth += 1;
1849
+ } else if (char === ")") {
1850
+ state.parenDepth = Math.max(0, state.parenDepth - 1);
1851
+ } else if (char === "[") {
1852
+ state.bracketDepth += 1;
1853
+ } else if (char === "]") {
1854
+ state.bracketDepth = Math.max(0, state.bracketDepth - 1);
1855
+ } else if (char === "{") {
1856
+ state.braceDepth += 1;
1857
+ } else if (char === "}") {
1858
+ state.braceDepth = Math.max(0, state.braceDepth - 1);
1859
+ }
1675
1860
  }
1676
- function withPausedDuration(snapshot, pausedDurationMs) {
1677
- return {
1678
- reason: snapshot.reason,
1679
- hitBreakpoints: snapshot.hitBreakpoints,
1680
- capturedAt: snapshot.capturedAt,
1681
- pausedDurationMs,
1682
- ...snapshot.topFrame === void 0 ? {} : { topFrame: snapshot.topFrame },
1683
- captures: snapshot.captures
1861
+ function isTopLevel(state) {
1862
+ return state.parenDepth === 0 && state.bracketDepth === 0 && state.braceDepth === 0;
1863
+ }
1864
+ function appendCapturePiece(raw, state, end) {
1865
+ const piece = raw.slice(state.start, end).trim();
1866
+ if (piece.length > 0) {
1867
+ state.pieces.push(piece);
1868
+ }
1869
+ }
1870
+ function splitCaptureExpressions(raw) {
1871
+ const state = {
1872
+ escaped: false,
1873
+ parenDepth: 0,
1874
+ bracketDepth: 0,
1875
+ braceDepth: 0,
1876
+ quote: void 0,
1877
+ start: 0,
1878
+ pieces: []
1684
1879
  };
1880
+ for (let idx = 0; idx < raw.length; idx += 1) {
1881
+ const char = raw.charAt(idx);
1882
+ if (consumeQuotedChar(state, char)) {
1883
+ continue;
1884
+ }
1885
+ if (isQuoteChar(char)) {
1886
+ state.quote = char;
1887
+ continue;
1888
+ }
1889
+ updateCaptureDepth(state, char);
1890
+ if (char === "," && isTopLevel(state)) {
1891
+ appendCapturePiece(raw, state, idx);
1892
+ state.start = idx + 1;
1893
+ }
1894
+ }
1895
+ appendCapturePiece(raw, state, raw.length);
1896
+ return state.pieces;
1685
1897
  }
1898
+
1899
+ // src/cli/commands/snapshot.ts
1686
1900
  async function handleSnapshot(opts) {
1901
+ const prepared = prepareSnapshotCommand(opts);
1902
+ const result = await runSnapshotCommand(prepared, opts);
1903
+ if (opts.json) {
1904
+ writeJson(result);
1905
+ } else {
1906
+ writeHumanSnapshot(result);
1907
+ }
1908
+ }
1909
+ function prepareSnapshotCommand(opts) {
1687
1910
  const target = resolveTarget(opts);
1688
1911
  if (opts.bp.length === 0) {
1689
1912
  throw new CfInspectorError(
@@ -1691,249 +1914,146 @@ async function handleSnapshot(opts) {
1691
1914
  "At least one --bp <file:line> is required."
1692
1915
  );
1693
1916
  }
1694
- const breakpoints = opts.bp.map((spec) => parseBreakpointSpec(spec));
1695
- const remoteRoot = parseRemoteRoot(opts.remoteRoot);
1696
- const captures = parseCaptureList(opts.capture);
1697
1917
  const timeoutSec = parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_BREAKPOINT_TIMEOUT_SEC;
1698
1918
  const maxValueLength = parsePositiveInt(opts.maxValueLength, "--max-value-length");
1699
- const timeoutMs = timeoutSec * 1e3;
1700
1919
  const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
1701
- const result = await withSession(target, async (session) => {
1702
- if (condition !== void 0) {
1703
- await validateExpression(session, condition);
1920
+ return {
1921
+ target,
1922
+ breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
1923
+ captures: parseCaptureList(opts.capture),
1924
+ remoteRoot: parseRemoteRoot(opts.remoteRoot),
1925
+ timeoutMs: timeoutSec * 1e3,
1926
+ ...condition === void 0 ? {} : { condition },
1927
+ ...maxValueLength === void 0 ? {} : { maxValueLength }
1928
+ };
1929
+ }
1930
+ async function runSnapshotCommand(command, opts) {
1931
+ return await withSession(command.target, async (session) => {
1932
+ if (command.condition !== void 0) {
1933
+ await validateExpression(session, command.condition);
1704
1934
  }
1705
1935
  const handles = await Promise.all(
1706
- breakpoints.map(
1936
+ command.breakpoints.map(
1707
1937
  (bp) => setBreakpoint(session, {
1708
1938
  file: bp.file,
1709
1939
  line: bp.line,
1710
- remoteRoot,
1711
- ...condition === void 0 ? {} : { condition }
1940
+ remoteRoot: command.remoteRoot,
1941
+ ...command.condition === void 0 ? {} : { condition: command.condition }
1712
1942
  })
1713
1943
  )
1714
1944
  );
1715
1945
  warnOnUnboundBreakpoints(handles);
1716
- const breakpointIds = handles.map((h) => h.breakpointId);
1717
- let warnedUnmatchedPause = false;
1718
- const pause = await waitForPause(session, {
1719
- timeoutMs,
1720
- breakpointIds,
1721
- unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
1722
- onUnmatchedPause: (unmatchedPause) => {
1723
- if (warnedUnmatchedPause || opts.failOnUnmatchedPause === true) {
1724
- return;
1725
- }
1726
- warnedUnmatchedPause = true;
1727
- warnOnUnmatchedPause(unmatchedPause);
1728
- }
1729
- });
1730
- const pausedStartedAt = pause.receivedAtMs ?? performance2.now();
1946
+ const pause = await waitForCommandPause(session, opts, handles, command.timeoutMs);
1947
+ const pausedStartedAt = pause.receivedAtMs ?? performance3.now();
1731
1948
  const snapshot = await captureSnapshot(session, pause, {
1732
- captures,
1949
+ captures: command.captures,
1733
1950
  includeScopes: opts.includeScopes === true,
1734
- ...maxValueLength === void 0 ? {} : { maxValueLength }
1951
+ ...command.maxValueLength === void 0 ? {} : { maxValueLength: command.maxValueLength }
1735
1952
  });
1736
1953
  if (opts.keepPaused === true) {
1737
1954
  return withPausedDuration(snapshot, null);
1738
1955
  }
1739
- try {
1740
- await resume(session);
1741
- return withPausedDuration(
1742
- snapshot,
1743
- roundDurationMs(performance2.now() - pausedStartedAt)
1744
- );
1745
- } catch {
1746
- process2.stderr.write(
1747
- "[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
1748
- );
1749
- return withPausedDuration(snapshot, null);
1750
- }
1751
- });
1752
- if (opts.json) {
1753
- writeJson(result);
1754
- } else {
1755
- writeHumanSnapshot(result);
1756
- }
1757
- }
1758
- async function handleEval(opts) {
1759
- const target = resolveTarget(opts);
1760
- const result = await withSession(target, async (session) => {
1761
- return await evaluateGlobal(session, opts.expr);
1956
+ return await resumeAfterSnapshot(session, snapshot, pausedStartedAt);
1762
1957
  });
1763
- if (opts.json) {
1764
- writeJson(result);
1765
- if (result.exceptionDetails !== void 0) {
1766
- process2.exitCode = 1;
1767
- }
1768
- return;
1769
- }
1770
- if (result.exceptionDetails !== void 0) {
1771
- const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1772
- process2.stderr.write(`${detail}
1773
- `);
1774
- process2.exitCode = 1;
1775
- return;
1776
- }
1777
- const inner = result.result;
1778
- if (inner === void 0) {
1779
- process2.stdout.write("\n");
1780
- return;
1781
- }
1782
- if (typeof inner.value === "string") {
1783
- process2.stdout.write(`${inner.value}
1784
- `);
1785
- return;
1786
- }
1787
- if (typeof inner.description === "string") {
1788
- process2.stdout.write(`${inner.description}
1789
- `);
1790
- return;
1791
- }
1792
- process2.stdout.write(`${JSON.stringify(inner.value)}
1793
- `);
1794
1958
  }
1795
- async function handleLog(opts) {
1796
- const target = resolveTarget(opts);
1797
- const location = parseBreakpointSpec(opts.at);
1798
- const remoteRoot = parseRemoteRoot(opts.remoteRoot);
1799
- const durationSec = parsePositiveInt(opts.duration, "--duration");
1800
- const expression = opts.expr.trim();
1801
- if (expression.length === 0) {
1802
- throw new CfInspectorError("INVALID_BREAKPOINT", "--expr must not be empty");
1803
- }
1804
- const abort = new AbortController();
1805
- const onSig = () => {
1806
- abort.abort();
1807
- };
1808
- process2.once("SIGINT", onSig);
1809
- process2.once("SIGTERM", onSig);
1810
- try {
1811
- await withSession(target, async (session) => {
1812
- await validateExpression(session, expression);
1813
- const result = await streamLogpoint(session, {
1814
- location,
1815
- expression,
1816
- remoteRoot,
1817
- ...durationSec === void 0 ? {} : { durationMs: durationSec * 1e3 },
1818
- signal: abort.signal,
1819
- onEvent: (event) => {
1820
- writeLogEvent(event, opts.json);
1821
- },
1822
- onBreakpointSet: (handle) => {
1823
- warnOnUnboundBreakpoints([handle]);
1824
- }
1825
- });
1826
- if (opts.json) {
1827
- process2.stderr.write(
1828
- `${JSON.stringify({ stopped: result.stoppedReason, emitted: result.emitted })}
1829
- `
1830
- );
1831
- } else {
1832
- process2.stderr.write(
1833
- `Stopped (${result.stoppedReason}); emitted ${result.emitted.toString()} log ${result.emitted === 1 ? "entry" : "entries"}.
1834
- `
1835
- );
1959
+ async function waitForCommandPause(session, opts, handles, timeoutMs) {
1960
+ let warnedUnmatchedPause = false;
1961
+ return await waitForPause(session, {
1962
+ timeoutMs,
1963
+ breakpointIds: handles.map((h) => h.breakpointId),
1964
+ unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
1965
+ onUnmatchedPause: (unmatchedPause) => {
1966
+ if (warnedUnmatchedPause || opts.failOnUnmatchedPause === true) {
1967
+ return;
1836
1968
  }
1837
- });
1838
- } finally {
1839
- process2.off("SIGINT", onSig);
1840
- process2.off("SIGTERM", onSig);
1841
- }
1842
- }
1843
- async function handleListScripts(opts) {
1844
- const target = resolveTarget(opts);
1845
- const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
1846
- if (opts.json) {
1847
- writeJson(scripts);
1848
- return;
1849
- }
1850
- for (const script of scripts) {
1851
- process2.stdout.write(`${script.scriptId} ${script.url}
1852
- `);
1853
- }
1969
+ warnedUnmatchedPause = true;
1970
+ warnOnUnmatchedPause(unmatchedPause);
1971
+ }
1972
+ });
1854
1973
  }
1855
- async function handleAttach(opts) {
1856
- const target = resolveTarget(opts);
1857
- const tunnel = await openTarget(target);
1974
+ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
1858
1975
  try {
1859
- const version = await fetchInspectorVersion(tunnel.host, tunnel.port, 5e3);
1860
- if (opts.json) {
1861
- writeJson({ host: tunnel.host, port: tunnel.port, ...version });
1862
- return;
1863
- }
1864
- process2.stdout.write(
1865
- `Connected to ${tunnel.host}:${tunnel.port.toString()}
1866
- Browser: ${version.browser}
1867
- Protocol: ${version.protocolVersion}
1868
- `
1976
+ await resume(session);
1977
+ return withPausedDuration(snapshot, roundDurationMs(performance3.now() - pausedStartedAt));
1978
+ } catch {
1979
+ process7.stderr.write(
1980
+ "[cf-inspector] warning: Debugger.resume failed after snapshot; pausedDurationMs is unknown.\n"
1869
1981
  );
1870
- } finally {
1871
- await tunnel.dispose();
1982
+ return withPausedDuration(snapshot, null);
1872
1983
  }
1873
1984
  }
1985
+
1986
+ // src/cli/program.ts
1874
1987
  function applyTargetOptions(cmd) {
1875
1988
  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");
1876
1989
  }
1877
1990
  async function main(argv) {
1878
1991
  const program = new Command();
1879
1992
  program.name("cf-inspector").description("Drive a Node.js inspector from the command line \u2014 set breakpoints, capture snapshots, evaluate expressions");
1993
+ registerSnapshot(program);
1994
+ registerLog(program);
1995
+ registerEval(program);
1996
+ registerListScripts(program);
1997
+ registerAttach(program);
1998
+ await program.parseAsync([...argv]);
1999
+ }
2000
+ function registerSnapshot(program) {
1880
2001
  const collectStrings = (value, prev = []) => [
1881
2002
  ...prev,
1882
2003
  value
1883
2004
  ];
1884
2005
  applyTargetOptions(
1885
2006
  program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
1886
- ).option(
1887
- "--bp <file:line>",
1888
- "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42",
1889
- collectStrings,
1890
- []
1891
- ).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(
1892
- "--condition <expr>",
1893
- "Only pause when this JS expression evaluates truthy in the paused frame"
1894
- ).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) => {
2007
+ ).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) => {
1895
2008
  await handleSnapshot(opts);
1896
2009
  });
2010
+ }
2011
+ function registerLog(program) {
1897
2012
  applyTargetOptions(
1898
2013
  program.command("log").description("Stream a non-pausing logpoint: log an expression each time a line executes")
1899
2014
  ).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) => {
1900
2015
  await handleLog(opts);
1901
2016
  });
2017
+ }
2018
+ function registerEval(program) {
1902
2019
  applyTargetOptions(
1903
2020
  program.command("eval").description("Evaluate an expression against the global Runtime")
1904
2021
  ).requiredOption("--expr <expression>", "JavaScript expression to evaluate").option("--no-json", "Print only the resulting value, not the full CDP envelope").action(async (opts) => {
1905
2022
  await handleEval(opts);
1906
2023
  });
2024
+ }
2025
+ function registerListScripts(program) {
1907
2026
  applyTargetOptions(
1908
2027
  program.command("list-scripts").description("Print the scripts the V8 instance currently knows about")
1909
2028
  ).option("--no-json", "Print scriptId<TAB>url instead of JSON").action(async (opts) => {
1910
2029
  await handleListScripts(opts);
1911
2030
  });
2031
+ }
2032
+ function registerAttach(program) {
1912
2033
  applyTargetOptions(
1913
2034
  program.command("attach").description("Connect, fetch the inspector version, and disconnect (smoke-test)")
1914
2035
  ).option("--no-json", "Print a multi-line summary instead of JSON").action(async (opts) => {
1915
2036
  await handleAttach(opts);
1916
2037
  });
1917
- await program.parseAsync([...argv]);
1918
2038
  }
2039
+
2040
+ // src/cli.ts
2041
+ init_types();
1919
2042
  try {
1920
- await main(process2.argv);
2043
+ await main(process8.argv);
1921
2044
  } catch (err) {
1922
2045
  if (err instanceof CfInspectorError) {
1923
- process2.stderr.write(`Error [${err.code}]: ${err.message}
2046
+ process8.stderr.write(`Error [${err.code}]: ${err.message}
1924
2047
  `);
1925
2048
  if (err.detail !== void 0) {
1926
- process2.stderr.write(` detail: ${err.detail}
2049
+ process8.stderr.write(` detail: ${err.detail}
1927
2050
  `);
1928
2051
  }
1929
- process2.exit(1);
2052
+ process8.exit(1);
1930
2053
  }
1931
2054
  const message = err instanceof Error ? err.message : String(err);
1932
- process2.stderr.write(`Error: ${message}
2055
+ process8.stderr.write(`Error: ${message}
1933
2056
  `);
1934
- process2.exit(1);
2057
+ process8.exit(1);
1935
2058
  }
1936
- export {
1937
- main
1938
- };
1939
2059
  //# sourceMappingURL=cli.js.map