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