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