@saptools/cf-inspector 0.3.16 → 0.3.18

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