@presto1314w/vite-devtools-browser 0.1.3 → 0.2.0
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/README.md +238 -65
- package/dist/browser.d.ts +42 -1
- package/dist/browser.js +211 -96
- package/dist/cli.d.ts +13 -1
- package/dist/cli.js +191 -157
- package/dist/client.d.ts +24 -0
- package/dist/client.js +37 -22
- package/dist/correlate.d.ts +20 -0
- package/dist/correlate.js +110 -0
- package/dist/daemon.d.ts +58 -1
- package/dist/daemon.js +183 -128
- package/dist/diagnose.d.ts +19 -0
- package/dist/diagnose.js +88 -0
- package/dist/event-queue.d.ts +22 -0
- package/dist/event-queue.js +32 -0
- package/dist/network.d.ts +2 -0
- package/dist/network.js +19 -1
- package/dist/react/devtools.d.ts +13 -0
- package/dist/react/devtools.js +130 -128
- package/dist/sourcemap.d.ts +7 -1
- package/dist/sourcemap.js +10 -7
- package/dist/svelte/devtools.d.ts +9 -0
- package/dist/svelte/devtools.js +110 -167
- package/dist/vue/devtools.d.ts +4 -0
- package/dist/vue/devtools.js +218 -236
- package/package.json +17 -3
package/dist/cli.js
CHANGED
|
@@ -1,165 +1,191 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { send } from "./client.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
printUsage();
|
|
9
|
-
process.exit(0);
|
|
10
|
-
}
|
|
11
|
-
if (cmd === "open") {
|
|
12
|
-
if (!arg) {
|
|
13
|
-
console.error("usage: vite-browser open <url> [--cookies-json <file>]");
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
const url = normalizeUrl(arg);
|
|
17
|
-
const cookieIdx = args.indexOf("--cookies-json");
|
|
18
|
-
const cookieFile = cookieIdx >= 0 ? args[cookieIdx + 1] : undefined;
|
|
19
|
-
if (cookieFile) {
|
|
20
|
-
const res = await send("open");
|
|
21
|
-
if (!res.ok)
|
|
22
|
-
exit(res, "");
|
|
23
|
-
const raw = readFileSync(cookieFile, "utf-8");
|
|
24
|
-
const cookies = JSON.parse(raw);
|
|
25
|
-
const domain = new URL(url).hostname;
|
|
26
|
-
const cRes = await send("cookies", { cookies, domain });
|
|
27
|
-
if (!cRes.ok)
|
|
28
|
-
exit(cRes, "");
|
|
29
|
-
await send("goto", { url });
|
|
30
|
-
exit(res, `opened -> ${url} (${cookies.length} cookies for ${domain})`);
|
|
31
|
-
}
|
|
32
|
-
const res = await send("open", { url });
|
|
33
|
-
exit(res, `opened -> ${url}`);
|
|
34
|
-
}
|
|
35
|
-
if (cmd === "close") {
|
|
36
|
-
const res = await send("close");
|
|
37
|
-
exit(res, "closed");
|
|
4
|
+
export function normalizeUrl(value) {
|
|
5
|
+
if (value.includes("://"))
|
|
6
|
+
return value;
|
|
7
|
+
return `http://${value}`;
|
|
38
8
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
9
|
+
export function parseNumberFlag(args, name, fallback) {
|
|
10
|
+
const idx = args.indexOf(name);
|
|
11
|
+
if (idx < 0)
|
|
12
|
+
return fallback;
|
|
13
|
+
return Number.parseInt(args[idx + 1] ?? String(fallback), 10);
|
|
14
|
+
}
|
|
15
|
+
export async function runCli(argv, io) {
|
|
16
|
+
const args = argv.slice(2);
|
|
17
|
+
const cmd = args[0];
|
|
18
|
+
const arg = args[1];
|
|
19
|
+
if (cmd === "--help" || cmd === "-h" || !cmd) {
|
|
20
|
+
io.stdout(printUsage());
|
|
21
|
+
io.exit(0);
|
|
43
22
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
68
|
-
}
|
|
69
|
-
if (cmd === "vue" && arg === "router") {
|
|
70
|
-
const res = await send("vue-router");
|
|
71
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
72
|
-
}
|
|
73
|
-
if (cmd === "react" && arg === "tree") {
|
|
74
|
-
const id = args[2];
|
|
75
|
-
const res = await send("react-tree", { id });
|
|
76
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
77
|
-
}
|
|
78
|
-
if (cmd === "svelte" && arg === "tree") {
|
|
79
|
-
const id = args[2];
|
|
80
|
-
const res = await send("svelte-tree", { id });
|
|
81
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
82
|
-
}
|
|
83
|
-
if (cmd === "vite" && arg === "restart") {
|
|
84
|
-
const res = await send("vite-restart");
|
|
85
|
-
exit(res, res.ok && res.data ? String(res.data) : "restarted");
|
|
86
|
-
}
|
|
87
|
-
if (cmd === "vite" && arg === "hmr") {
|
|
88
|
-
const sub = args[2];
|
|
89
|
-
if (sub === "clear") {
|
|
90
|
-
const res = await send("vite-hmr", { mode: "clear" });
|
|
91
|
-
exit(res, res.ok && res.data ? String(res.data) : "cleared HMR trace");
|
|
92
|
-
}
|
|
93
|
-
if (sub === "trace") {
|
|
94
|
-
const limitIdx = args.indexOf("--limit");
|
|
95
|
-
const limit = limitIdx >= 0 ? Number.parseInt(args[limitIdx + 1] ?? "20", 10) : 20;
|
|
96
|
-
const res = await send("vite-hmr", { mode: "trace", limit });
|
|
97
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
98
|
-
}
|
|
99
|
-
const res = await send("vite-hmr", { mode: "summary", limit: 20 });
|
|
100
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
101
|
-
}
|
|
102
|
-
if (cmd === "vite" && arg === "runtime") {
|
|
103
|
-
const res = await send("vite-runtime");
|
|
104
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
105
|
-
}
|
|
106
|
-
if (cmd === "vite" && arg === "module-graph") {
|
|
107
|
-
const sub = args[2];
|
|
108
|
-
const filterIdx = args.indexOf("--filter");
|
|
109
|
-
const limitIdx = args.indexOf("--limit");
|
|
110
|
-
const filter = filterIdx >= 0 ? args[filterIdx + 1] : undefined;
|
|
111
|
-
const limit = limitIdx >= 0 ? Number.parseInt(args[limitIdx + 1] ?? "200", 10) : 200;
|
|
112
|
-
if (sub === "clear") {
|
|
113
|
-
const res = await send("vite-module-graph", { mode: "clear" });
|
|
114
|
-
exit(res, res.ok && res.data ? String(res.data) : "cleared module-graph baseline");
|
|
115
|
-
}
|
|
116
|
-
if (sub === "trace") {
|
|
117
|
-
const res = await send("vite-module-graph", { mode: "trace", filter, limit });
|
|
118
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
119
|
-
}
|
|
120
|
-
const res = await send("vite-module-graph", { mode: "snapshot", filter, limit });
|
|
121
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
122
|
-
}
|
|
123
|
-
if (cmd === "errors") {
|
|
124
|
-
const mapped = args.includes("--mapped");
|
|
125
|
-
const inlineSource = args.includes("--inline-source");
|
|
126
|
-
const res = await send("errors", { mapped, inlineSource });
|
|
127
|
-
exit(res, res.ok && res.data ? String(res.data) : "no errors");
|
|
128
|
-
}
|
|
129
|
-
if (cmd === "logs") {
|
|
130
|
-
const res = await send("logs");
|
|
131
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
132
|
-
}
|
|
133
|
-
if (cmd === "screenshot") {
|
|
134
|
-
const res = await send("screenshot");
|
|
135
|
-
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
136
|
-
}
|
|
137
|
-
if (cmd === "eval") {
|
|
138
|
-
if (!arg) {
|
|
139
|
-
console.error("usage: vite-browser eval <script>");
|
|
140
|
-
process.exit(1);
|
|
23
|
+
if (cmd === "open") {
|
|
24
|
+
if (!arg) {
|
|
25
|
+
io.stderr("usage: vite-browser open <url> [--cookies-json <file>]");
|
|
26
|
+
io.exit(1);
|
|
27
|
+
}
|
|
28
|
+
const url = normalizeUrl(arg);
|
|
29
|
+
const cookieIdx = args.indexOf("--cookies-json");
|
|
30
|
+
const cookieFile = cookieIdx >= 0 ? args[cookieIdx + 1] : undefined;
|
|
31
|
+
if (cookieFile) {
|
|
32
|
+
const res = await io.send("open");
|
|
33
|
+
if (!res.ok)
|
|
34
|
+
exit(io, res, "");
|
|
35
|
+
const raw = io.readFile(cookieFile, "utf-8");
|
|
36
|
+
const cookies = JSON.parse(raw);
|
|
37
|
+
const domain = new URL(url).hostname;
|
|
38
|
+
const cRes = await io.send("cookies", { cookies, domain });
|
|
39
|
+
if (!cRes.ok)
|
|
40
|
+
exit(io, cRes, "");
|
|
41
|
+
await io.send("goto", { url });
|
|
42
|
+
exit(io, res, `opened -> ${url} (${cookies.length} cookies for ${domain})`);
|
|
43
|
+
}
|
|
44
|
+
const res = await io.send("open", { url });
|
|
45
|
+
exit(io, res, `opened -> ${url}`);
|
|
141
46
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
47
|
+
if (cmd === "close") {
|
|
48
|
+
const res = await io.send("close");
|
|
49
|
+
exit(io, res, "closed");
|
|
50
|
+
}
|
|
51
|
+
if (cmd === "goto") {
|
|
52
|
+
if (!arg) {
|
|
53
|
+
io.stderr("usage: vite-browser goto <url>");
|
|
54
|
+
io.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const res = await io.send("goto", { url: normalizeUrl(arg) });
|
|
57
|
+
exit(io, res, res.ok ? `-> ${res.data}` : "");
|
|
58
|
+
}
|
|
59
|
+
if (cmd === "back") {
|
|
60
|
+
const res = await io.send("back");
|
|
61
|
+
exit(io, res, "back");
|
|
62
|
+
}
|
|
63
|
+
if (cmd === "reload") {
|
|
64
|
+
const res = await io.send("reload");
|
|
65
|
+
exit(io, res, res.ok ? `reloaded -> ${res.data}` : "");
|
|
66
|
+
}
|
|
67
|
+
if (cmd === "detect") {
|
|
68
|
+
const res = await io.send("detect");
|
|
69
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
70
|
+
}
|
|
71
|
+
if (cmd === "vue" && arg === "tree") {
|
|
72
|
+
const id = args[2];
|
|
73
|
+
const res = await io.send("vue-tree", { id });
|
|
74
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
75
|
+
}
|
|
76
|
+
if (cmd === "vue" && arg === "pinia") {
|
|
77
|
+
const store = args[2];
|
|
78
|
+
const res = await io.send("vue-pinia", { store });
|
|
79
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
80
|
+
}
|
|
81
|
+
if (cmd === "vue" && arg === "router") {
|
|
82
|
+
const res = await io.send("vue-router");
|
|
83
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
84
|
+
}
|
|
85
|
+
if (cmd === "react" && arg === "tree") {
|
|
86
|
+
const id = args[2];
|
|
87
|
+
const res = await io.send("react-tree", { id });
|
|
88
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
89
|
+
}
|
|
90
|
+
if (cmd === "svelte" && arg === "tree") {
|
|
91
|
+
const id = args[2];
|
|
92
|
+
const res = await io.send("svelte-tree", { id });
|
|
93
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
94
|
+
}
|
|
95
|
+
if (cmd === "vite" && arg === "restart") {
|
|
96
|
+
const res = await io.send("vite-restart");
|
|
97
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "restarted");
|
|
98
|
+
}
|
|
99
|
+
if (cmd === "vite" && arg === "hmr") {
|
|
100
|
+
const sub = args[2];
|
|
101
|
+
if (sub === "clear") {
|
|
102
|
+
const res = await io.send("vite-hmr", { mode: "clear" });
|
|
103
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "cleared HMR trace");
|
|
104
|
+
}
|
|
105
|
+
if (sub === "trace") {
|
|
106
|
+
const limit = parseNumberFlag(args, "--limit", 20);
|
|
107
|
+
const res = await io.send("vite-hmr", { mode: "trace", limit });
|
|
108
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
109
|
+
}
|
|
110
|
+
const res = await io.send("vite-hmr", { mode: "summary", limit: 20 });
|
|
111
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
112
|
+
}
|
|
113
|
+
if (cmd === "vite" && arg === "runtime") {
|
|
114
|
+
const res = await io.send("vite-runtime");
|
|
115
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
116
|
+
}
|
|
117
|
+
if (cmd === "vite" && arg === "module-graph") {
|
|
118
|
+
const sub = args[2];
|
|
119
|
+
const filterIdx = args.indexOf("--filter");
|
|
120
|
+
const filter = filterIdx >= 0 ? args[filterIdx + 1] : undefined;
|
|
121
|
+
const limit = parseNumberFlag(args, "--limit", 200);
|
|
122
|
+
if (sub === "clear") {
|
|
123
|
+
const res = await io.send("vite-module-graph", { mode: "clear" });
|
|
124
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "cleared module-graph baseline");
|
|
125
|
+
}
|
|
126
|
+
if (sub === "trace") {
|
|
127
|
+
const res = await io.send("vite-module-graph", { mode: "trace", filter, limit });
|
|
128
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
129
|
+
}
|
|
130
|
+
const res = await io.send("vite-module-graph", { mode: "snapshot", filter, limit });
|
|
131
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
132
|
+
}
|
|
133
|
+
if (cmd === "errors") {
|
|
134
|
+
const mapped = args.includes("--mapped");
|
|
135
|
+
const inlineSource = args.includes("--inline-source");
|
|
136
|
+
const res = await io.send("errors", { mapped, inlineSource });
|
|
137
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "no errors");
|
|
138
|
+
}
|
|
139
|
+
if (cmd === "correlate" && arg === "errors") {
|
|
140
|
+
const mapped = args.includes("--mapped");
|
|
141
|
+
const inlineSource = args.includes("--inline-source");
|
|
142
|
+
const windowMs = parseNumberFlag(args, "--window", 5000);
|
|
143
|
+
const res = await io.send("correlate-errors", { mapped, inlineSource, windowMs });
|
|
144
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
145
|
+
}
|
|
146
|
+
if (cmd === "diagnose" && arg === "hmr") {
|
|
147
|
+
const mapped = args.includes("--mapped");
|
|
148
|
+
const inlineSource = args.includes("--inline-source");
|
|
149
|
+
const windowMs = parseNumberFlag(args, "--window", 5000);
|
|
150
|
+
const limit = parseNumberFlag(args, "--limit", 50);
|
|
151
|
+
const res = await io.send("diagnose-hmr", { mapped, inlineSource, windowMs, limit });
|
|
152
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
153
|
+
}
|
|
154
|
+
if (cmd === "logs") {
|
|
155
|
+
const res = await io.send("logs");
|
|
156
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
157
|
+
}
|
|
158
|
+
if (cmd === "screenshot") {
|
|
159
|
+
const res = await io.send("screenshot");
|
|
160
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
161
|
+
}
|
|
162
|
+
if (cmd === "eval") {
|
|
163
|
+
if (!arg) {
|
|
164
|
+
io.stderr("usage: vite-browser eval <script>");
|
|
165
|
+
io.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const res = await io.send("eval", { script: arg });
|
|
168
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
169
|
+
}
|
|
170
|
+
if (cmd === "network") {
|
|
171
|
+
const idx = arg ? parseInt(arg, 10) : undefined;
|
|
172
|
+
const res = await io.send("network", { idx });
|
|
173
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
174
|
+
}
|
|
175
|
+
io.stderr(`unknown command: ${cmd}`);
|
|
176
|
+
io.exit(1);
|
|
149
177
|
}
|
|
150
|
-
|
|
151
|
-
process.exit(1);
|
|
152
|
-
function exit(res, msg) {
|
|
178
|
+
export function exit(io, res, msg) {
|
|
153
179
|
if (!res.ok) {
|
|
154
|
-
|
|
155
|
-
|
|
180
|
+
io.stderr(res.error || "error");
|
|
181
|
+
io.exit(1);
|
|
156
182
|
}
|
|
157
183
|
if (msg)
|
|
158
|
-
|
|
159
|
-
|
|
184
|
+
io.stdout(msg);
|
|
185
|
+
io.exit(0);
|
|
160
186
|
}
|
|
161
|
-
function printUsage() {
|
|
162
|
-
|
|
187
|
+
export function printUsage() {
|
|
188
|
+
return `
|
|
163
189
|
vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
|
|
164
190
|
|
|
165
191
|
USAGE
|
|
@@ -200,6 +226,10 @@ VITE COMMANDS
|
|
|
200
226
|
errors Show build/runtime errors
|
|
201
227
|
errors --mapped Show errors with source-map mapping
|
|
202
228
|
errors --mapped --inline-source Include mapped source snippets
|
|
229
|
+
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
230
|
+
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
231
|
+
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
232
|
+
diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
|
|
203
233
|
logs Show dev server logs
|
|
204
234
|
|
|
205
235
|
UTILITIES
|
|
@@ -209,10 +239,14 @@ UTILITIES
|
|
|
209
239
|
|
|
210
240
|
OPTIONS
|
|
211
241
|
-h, --help Show this help message
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
244
|
+
if (process.argv[1] && import.meta.url.endsWith(process.argv[1].replaceAll("\\", "/"))) {
|
|
245
|
+
await runCli(process.argv, {
|
|
246
|
+
send,
|
|
247
|
+
readFile: readFileSync,
|
|
248
|
+
stdout: (text) => console.log(text),
|
|
249
|
+
stderr: (text) => console.error(text),
|
|
250
|
+
exit: (code) => process.exit(code),
|
|
251
|
+
});
|
|
218
252
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { type Socket } from "node:net";
|
|
2
|
+
import { readFileSync, existsSync, rmSync } from "node:fs";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
1
5
|
export type Response = {
|
|
2
6
|
ok: true;
|
|
3
7
|
data?: unknown;
|
|
@@ -5,4 +9,24 @@ export type Response = {
|
|
|
5
9
|
ok: false;
|
|
6
10
|
error: string;
|
|
7
11
|
};
|
|
12
|
+
export type ClientDeps = {
|
|
13
|
+
socketPath: string;
|
|
14
|
+
pidFile: string;
|
|
15
|
+
existsSync: typeof existsSync;
|
|
16
|
+
readFileSync: typeof readFileSync;
|
|
17
|
+
rmSync: typeof rmSync;
|
|
18
|
+
processKill: typeof process.kill;
|
|
19
|
+
spawn: typeof spawn;
|
|
20
|
+
connect: () => Promise<Socket>;
|
|
21
|
+
sleep: typeof sleep;
|
|
22
|
+
daemonPath: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function createClientDeps(): ClientDeps;
|
|
8
25
|
export declare function send(action: string, payload?: Record<string, unknown>): Promise<Response>;
|
|
26
|
+
export declare function ensureDaemon(deps: ClientDeps): Promise<void>;
|
|
27
|
+
export declare function daemonAlive(deps: Pick<ClientDeps, "existsSync" | "pidFile" | "readFileSync" | "processKill" | "rmSync" | "socketPath">): boolean;
|
|
28
|
+
export declare function connect(): Promise<Socket>;
|
|
29
|
+
export declare function readLine(socket: Pick<Socket, "on">): Promise<string>;
|
|
30
|
+
export declare function ok(s: Pick<Socket, "destroy">): boolean;
|
|
31
|
+
export declare function no(): boolean;
|
|
32
|
+
export declare function removeSocketFile(path: string, removeFile?: typeof rmSync): void;
|
package/dist/client.js
CHANGED
|
@@ -4,54 +4,69 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { socketPath, pidFile } from "./paths.js";
|
|
7
|
+
export function createClientDeps() {
|
|
8
|
+
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
9
|
+
const daemonPath = fileURLToPath(new URL(`./daemon${ext}`, import.meta.url));
|
|
10
|
+
return {
|
|
11
|
+
socketPath,
|
|
12
|
+
pidFile,
|
|
13
|
+
existsSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
processKill: process.kill.bind(process),
|
|
17
|
+
spawn,
|
|
18
|
+
connect,
|
|
19
|
+
sleep,
|
|
20
|
+
daemonPath,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
7
23
|
export async function send(action, payload = {}) {
|
|
8
|
-
|
|
9
|
-
|
|
24
|
+
const deps = createClientDeps();
|
|
25
|
+
await ensureDaemon(deps);
|
|
26
|
+
const socket = await deps.connect();
|
|
10
27
|
const id = String(Date.now());
|
|
11
28
|
socket.write(JSON.stringify({ id, action, ...payload }) + "\n");
|
|
12
29
|
const line = await readLine(socket);
|
|
13
30
|
socket.end();
|
|
14
31
|
return JSON.parse(line);
|
|
15
32
|
}
|
|
16
|
-
async function ensureDaemon() {
|
|
17
|
-
if (daemonAlive() && (await connect().then(ok, no)))
|
|
33
|
+
export async function ensureDaemon(deps) {
|
|
34
|
+
if (daemonAlive(deps) && (await deps.connect().then(ok, no)))
|
|
18
35
|
return;
|
|
19
|
-
const
|
|
20
|
-
const daemon = fileURLToPath(new URL(`./daemon${ext}`, import.meta.url));
|
|
21
|
-
const child = spawn(process.execPath, [daemon], {
|
|
36
|
+
const child = deps.spawn(process.execPath, [deps.daemonPath], {
|
|
22
37
|
detached: true,
|
|
23
38
|
stdio: "ignore",
|
|
24
39
|
});
|
|
25
40
|
child.unref();
|
|
26
41
|
for (let i = 0; i < 50; i++) {
|
|
27
|
-
if (await connect().then(ok, no))
|
|
42
|
+
if (await deps.connect().then(ok, no))
|
|
28
43
|
return;
|
|
29
|
-
await sleep(100);
|
|
44
|
+
await deps.sleep(100);
|
|
30
45
|
}
|
|
31
|
-
throw new Error(`daemon failed to start (${socketPath})`);
|
|
46
|
+
throw new Error(`daemon failed to start (${deps.socketPath})`);
|
|
32
47
|
}
|
|
33
|
-
function daemonAlive() {
|
|
34
|
-
if (!existsSync(pidFile))
|
|
48
|
+
export function daemonAlive(deps) {
|
|
49
|
+
if (!deps.existsSync(deps.pidFile))
|
|
35
50
|
return false;
|
|
36
|
-
const pid = Number(readFileSync(pidFile, "utf-8"));
|
|
51
|
+
const pid = Number(deps.readFileSync(deps.pidFile, "utf-8"));
|
|
37
52
|
try {
|
|
38
|
-
|
|
53
|
+
deps.processKill(pid, 0);
|
|
39
54
|
return true;
|
|
40
55
|
}
|
|
41
56
|
catch {
|
|
42
|
-
rmSync(pidFile, { force: true });
|
|
43
|
-
removeSocketFile();
|
|
57
|
+
deps.rmSync(deps.pidFile, { force: true });
|
|
58
|
+
removeSocketFile(deps.socketPath, deps.rmSync);
|
|
44
59
|
return false;
|
|
45
60
|
}
|
|
46
61
|
}
|
|
47
|
-
function connect() {
|
|
62
|
+
export function connect() {
|
|
48
63
|
return new Promise((resolve, reject) => {
|
|
49
64
|
const socket = netConnect(socketPath);
|
|
50
65
|
socket.once("connect", () => resolve(socket));
|
|
51
66
|
socket.once("error", reject);
|
|
52
67
|
});
|
|
53
68
|
}
|
|
54
|
-
function readLine(socket) {
|
|
69
|
+
export function readLine(socket) {
|
|
55
70
|
return new Promise((resolve, reject) => {
|
|
56
71
|
let buffer = "";
|
|
57
72
|
socket.on("data", (chunk) => {
|
|
@@ -63,15 +78,15 @@ function readLine(socket) {
|
|
|
63
78
|
socket.on("error", reject);
|
|
64
79
|
});
|
|
65
80
|
}
|
|
66
|
-
function ok(s) {
|
|
81
|
+
export function ok(s) {
|
|
67
82
|
s.destroy();
|
|
68
83
|
return true;
|
|
69
84
|
}
|
|
70
|
-
function no() {
|
|
85
|
+
export function no() {
|
|
71
86
|
return false;
|
|
72
87
|
}
|
|
73
|
-
function removeSocketFile() {
|
|
88
|
+
export function removeSocketFile(path, removeFile = rmSync) {
|
|
74
89
|
if (process.platform === "win32")
|
|
75
90
|
return;
|
|
76
|
-
|
|
91
|
+
removeFile(path, { force: true });
|
|
77
92
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { VBEvent } from "./event-queue.js";
|
|
2
|
+
export type CorrelationConfidence = "high" | "medium" | "low";
|
|
3
|
+
export type ErrorCorrelation = {
|
|
4
|
+
summary: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
confidence: CorrelationConfidence;
|
|
7
|
+
windowMs: number;
|
|
8
|
+
matchingModules: string[];
|
|
9
|
+
relatedEvents: VBEvent[];
|
|
10
|
+
};
|
|
11
|
+
export type RenderNetworkCorrelation = {
|
|
12
|
+
summary: string;
|
|
13
|
+
detail: string;
|
|
14
|
+
confidence: CorrelationConfidence;
|
|
15
|
+
requestCount: number;
|
|
16
|
+
urls: string[];
|
|
17
|
+
};
|
|
18
|
+
export declare function correlateErrorWithHMR(errorText: string, events: VBEvent[], windowMs?: number): ErrorCorrelation | null;
|
|
19
|
+
export declare function correlateRenderWithNetwork(events: VBEvent[], requestThreshold?: number): RenderNetworkCorrelation | null;
|
|
20
|
+
export declare function formatErrorCorrelationReport(errorText: string, correlation: ErrorCorrelation | null): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const MODULE_PATTERNS = [
|
|
2
|
+
/\/src\/[^\s"'`):]+/g,
|
|
3
|
+
/\/@fs\/[^\s"'`):]+/g,
|
|
4
|
+
/[A-Za-z]:\\[^:\n]+/g,
|
|
5
|
+
];
|
|
6
|
+
export function correlateErrorWithHMR(errorText, events, windowMs = 5000) {
|
|
7
|
+
const recentEvents = events.filter((event) => event.type === "hmr-update" || event.type === "hmr-error");
|
|
8
|
+
if (recentEvents.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
const errorModules = extractModules(errorText);
|
|
11
|
+
const matchedEvents = recentEvents.filter((event) => {
|
|
12
|
+
const modules = extractModulesFromEvent(event);
|
|
13
|
+
if (errorModules.length === 0)
|
|
14
|
+
return event.type === "hmr-error";
|
|
15
|
+
return modules.some((module) => errorModules.includes(module));
|
|
16
|
+
});
|
|
17
|
+
const relatedEvents = matchedEvents.length > 0 ? matchedEvents : recentEvents;
|
|
18
|
+
const matchingModules = unique(relatedEvents.flatMap((event) => extractModulesFromEvent(event)).filter((module) => errorModules.includes(module)));
|
|
19
|
+
const confidence = inferConfidence(errorModules, matchingModules, relatedEvents);
|
|
20
|
+
const eventKind = relatedEvents.some((event) => event.type === "hmr-error") ? "HMR error" : "HMR update";
|
|
21
|
+
const moduleText = matchingModules.length > 0
|
|
22
|
+
? `Matching modules: ${matchingModules.join(", ")}`
|
|
23
|
+
: errorModules.length > 0
|
|
24
|
+
? `Error modules: ${errorModules.join(", ")}`
|
|
25
|
+
: "No module path overlap; correlation is time-window based.";
|
|
26
|
+
return {
|
|
27
|
+
summary: `${eventKind} observed within ${windowMs}ms of the current error`,
|
|
28
|
+
detail: `${moduleText}\nRecent events considered: ${relatedEvents.length}`,
|
|
29
|
+
confidence,
|
|
30
|
+
windowMs,
|
|
31
|
+
matchingModules,
|
|
32
|
+
relatedEvents,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function correlateRenderWithNetwork(events, requestThreshold = 3) {
|
|
36
|
+
const renderEvents = events.filter((event) => event.type === "render");
|
|
37
|
+
const networkEvents = events.filter((event) => event.type === "network");
|
|
38
|
+
if (renderEvents.length === 0 || networkEvents.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
const latestRender = renderEvents[renderEvents.length - 1];
|
|
41
|
+
const start = latestRender.timestamp - 1000;
|
|
42
|
+
const end = latestRender.timestamp + 1000;
|
|
43
|
+
const overlappingNetwork = networkEvents.filter((event) => event.timestamp >= start && event.timestamp <= end);
|
|
44
|
+
const urls = unique(overlappingNetwork
|
|
45
|
+
.map((event) => event.payload.url)
|
|
46
|
+
.filter((url) => typeof url === "string"));
|
|
47
|
+
if (overlappingNetwork.length < requestThreshold)
|
|
48
|
+
return null;
|
|
49
|
+
return {
|
|
50
|
+
summary: "Repeated network activity detected around a render event",
|
|
51
|
+
detail: `Observed ${overlappingNetwork.length} network requests near the latest render. URLs: ${urls.join(", ")}`,
|
|
52
|
+
confidence: overlappingNetwork.length >= requestThreshold + 2 ? "high" : "medium",
|
|
53
|
+
requestCount: overlappingNetwork.length,
|
|
54
|
+
urls,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function formatErrorCorrelationReport(errorText, correlation) {
|
|
58
|
+
const lines = ["# Error Correlation", "", "## Current Error", errorText.trim() || "(empty error)"];
|
|
59
|
+
if (!correlation) {
|
|
60
|
+
lines.push("", "## Correlation", "No recent HMR events correlated with the current error.");
|
|
61
|
+
return lines.join("\n");
|
|
62
|
+
}
|
|
63
|
+
lines.push("", "## Correlation", `Confidence: ${correlation.confidence}`, correlation.summary, correlation.detail, "", "## Related Events", ...correlation.relatedEvents.map((event) => formatEventLine(event)));
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
function formatEventLine(event) {
|
|
67
|
+
const payload = event.payload;
|
|
68
|
+
const path = payload.path;
|
|
69
|
+
const message = payload.message;
|
|
70
|
+
if (typeof path === "string")
|
|
71
|
+
return `- ${event.type}: ${path}`;
|
|
72
|
+
if (typeof message === "string")
|
|
73
|
+
return `- ${event.type}: ${message}`;
|
|
74
|
+
return `- ${event.type}: ${JSON.stringify(payload)}`;
|
|
75
|
+
}
|
|
76
|
+
function inferConfidence(errorModules, matchingModules, events) {
|
|
77
|
+
if (matchingModules.length > 0)
|
|
78
|
+
return "high";
|
|
79
|
+
if (events.some((event) => event.type === "hmr-error"))
|
|
80
|
+
return "medium";
|
|
81
|
+
if (errorModules.length === 0 && events.length > 0)
|
|
82
|
+
return "low";
|
|
83
|
+
return "medium";
|
|
84
|
+
}
|
|
85
|
+
function extractModulesFromEvent(event) {
|
|
86
|
+
const payload = event.payload;
|
|
87
|
+
const candidates = [];
|
|
88
|
+
if (typeof payload.path === "string")
|
|
89
|
+
candidates.push(payload.path);
|
|
90
|
+
if (typeof payload.message === "string")
|
|
91
|
+
candidates.push(payload.message);
|
|
92
|
+
if (Array.isArray(payload.updates)) {
|
|
93
|
+
for (const update of payload.updates) {
|
|
94
|
+
if (update && typeof update === "object" && typeof update.path === "string") {
|
|
95
|
+
candidates.push(update.path);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return unique(candidates.flatMap((candidate) => extractModules(candidate)));
|
|
100
|
+
}
|
|
101
|
+
function extractModules(text) {
|
|
102
|
+
const matches = MODULE_PATTERNS.flatMap((pattern) => text.match(pattern) ?? []);
|
|
103
|
+
return unique(matches.map(normalizeModulePath).filter(Boolean));
|
|
104
|
+
}
|
|
105
|
+
function normalizeModulePath(value) {
|
|
106
|
+
return value.replace(/[),.:]+$/, "");
|
|
107
|
+
}
|
|
108
|
+
function unique(values) {
|
|
109
|
+
return [...new Set(values)];
|
|
110
|
+
}
|