@saptools/cf-inspector 0.3.15 → 0.3.17

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