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