@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/daemon.d.ts
CHANGED
|
@@ -1 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Socket } from "node:net";
|
|
2
|
+
import * as browser from "./browser.js";
|
|
3
|
+
export type BrowserApi = typeof browser;
|
|
4
|
+
export type Cmd = {
|
|
5
|
+
action: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
id?: string;
|
|
8
|
+
script?: string;
|
|
9
|
+
idx?: number;
|
|
10
|
+
mode?: "summary" | "trace" | "clear" | "snapshot";
|
|
11
|
+
limit?: number;
|
|
12
|
+
windowMs?: number;
|
|
13
|
+
filter?: string;
|
|
14
|
+
mapped?: boolean;
|
|
15
|
+
inlineSource?: boolean;
|
|
16
|
+
cookies?: {
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
}[];
|
|
20
|
+
domain?: string;
|
|
21
|
+
store?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function cleanError(err: unknown): string;
|
|
24
|
+
export declare function createRunner(api?: BrowserApi): (cmd: Cmd) => Promise<{
|
|
25
|
+
ok: boolean;
|
|
26
|
+
data?: undefined;
|
|
27
|
+
error?: undefined;
|
|
28
|
+
} | {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
data: number;
|
|
31
|
+
error?: undefined;
|
|
32
|
+
} | {
|
|
33
|
+
ok: boolean;
|
|
34
|
+
data: string;
|
|
35
|
+
error?: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
error: string;
|
|
39
|
+
data?: undefined;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function dispatchLine(line: string, socket: Pick<Socket, "write">, run?: (cmd: Cmd) => Promise<{
|
|
42
|
+
ok: boolean;
|
|
43
|
+
data?: undefined;
|
|
44
|
+
error?: undefined;
|
|
45
|
+
} | {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
data: number;
|
|
48
|
+
error?: undefined;
|
|
49
|
+
} | {
|
|
50
|
+
ok: boolean;
|
|
51
|
+
data: string;
|
|
52
|
+
error?: undefined;
|
|
53
|
+
} | {
|
|
54
|
+
ok: boolean;
|
|
55
|
+
error: string;
|
|
56
|
+
data?: undefined;
|
|
57
|
+
}>, onClose?: () => void): Promise<void>;
|
|
58
|
+
export declare function startDaemon(): void;
|
package/dist/daemon.js
CHANGED
|
@@ -1,144 +1,196 @@
|
|
|
1
1
|
import { createServer } from "node:net";
|
|
2
2
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import * as browser from "./browser.js";
|
|
5
|
+
import { correlateErrorWithHMR, formatErrorCorrelationReport } from "./correlate.js";
|
|
6
|
+
import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
|
|
4
7
|
import { socketDir, socketPath, pidFile } from "./paths.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let buffer = "";
|
|
11
|
-
socket.on("data", (chunk) => {
|
|
12
|
-
buffer += chunk;
|
|
13
|
-
let newline;
|
|
14
|
-
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
15
|
-
const line = buffer.slice(0, newline);
|
|
16
|
-
buffer = buffer.slice(newline + 1);
|
|
17
|
-
if (line)
|
|
18
|
-
dispatch(line, socket);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
socket.on("error", () => { });
|
|
22
|
-
});
|
|
23
|
-
server.listen(socketPath);
|
|
24
|
-
process.on("SIGINT", shutdown);
|
|
25
|
-
process.on("SIGTERM", shutdown);
|
|
26
|
-
process.on("exit", cleanup);
|
|
27
|
-
async function dispatch(line, socket) {
|
|
28
|
-
const cmd = JSON.parse(line);
|
|
29
|
-
const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) }));
|
|
30
|
-
socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n");
|
|
31
|
-
if (cmd.action === "close")
|
|
32
|
-
setImmediate(shutdown);
|
|
33
|
-
}
|
|
34
|
-
function cleanError(err) {
|
|
8
|
+
import { EventQueue } from "./event-queue.js";
|
|
9
|
+
import * as networkLog from "./network.js";
|
|
10
|
+
export function cleanError(err) {
|
|
11
|
+
if (!(err instanceof Error))
|
|
12
|
+
return String(err);
|
|
35
13
|
const msg = err.message;
|
|
36
14
|
const m = msg.match(/^page\.\w+: (?:Error: )?(.+?)(?:\n|$)/);
|
|
37
15
|
return m ? m[1] : msg;
|
|
38
16
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
17
|
+
export function createRunner(api = browser) {
|
|
18
|
+
return async function run(cmd) {
|
|
19
|
+
// Flush browser events to daemon queue before processing command
|
|
20
|
+
const queue = api.getEventQueue();
|
|
21
|
+
if (queue) {
|
|
22
|
+
try {
|
|
23
|
+
const currentPage = api.getCurrentPage();
|
|
24
|
+
if (currentPage) {
|
|
25
|
+
await api.flushBrowserEvents(currentPage, queue);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Ignore flush errors (page might not be open yet)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Browser control
|
|
33
|
+
if (cmd.action === "open") {
|
|
34
|
+
await api.open(cmd.url);
|
|
35
|
+
return { ok: true };
|
|
36
|
+
}
|
|
37
|
+
if (cmd.action === "cookies") {
|
|
38
|
+
const data = await api.cookies(cmd.cookies, cmd.domain);
|
|
39
|
+
return { ok: true, data };
|
|
40
|
+
}
|
|
41
|
+
if (cmd.action === "close") {
|
|
42
|
+
await api.close();
|
|
43
|
+
return { ok: true };
|
|
44
|
+
}
|
|
45
|
+
if (cmd.action === "goto") {
|
|
46
|
+
const data = await api.goto(cmd.url);
|
|
47
|
+
return { ok: true, data };
|
|
48
|
+
}
|
|
49
|
+
if (cmd.action === "back") {
|
|
50
|
+
await api.back();
|
|
51
|
+
return { ok: true };
|
|
52
|
+
}
|
|
53
|
+
if (cmd.action === "reload") {
|
|
54
|
+
const data = await api.reload();
|
|
55
|
+
return { ok: true, data };
|
|
56
|
+
}
|
|
57
|
+
// Framework detection
|
|
58
|
+
if (cmd.action === "detect") {
|
|
59
|
+
const data = await api.detectFramework();
|
|
60
|
+
return { ok: true, data };
|
|
61
|
+
}
|
|
62
|
+
// Vue commands
|
|
63
|
+
if (cmd.action === "vue-tree") {
|
|
64
|
+
const data = await api.vueTree(cmd.id);
|
|
65
|
+
return { ok: true, data };
|
|
66
|
+
}
|
|
67
|
+
if (cmd.action === "vue-pinia") {
|
|
68
|
+
const data = await api.vuePinia(cmd.store);
|
|
69
|
+
return { ok: true, data };
|
|
70
|
+
}
|
|
71
|
+
if (cmd.action === "vue-router") {
|
|
72
|
+
const data = await api.vueRouter();
|
|
73
|
+
return { ok: true, data };
|
|
74
|
+
}
|
|
75
|
+
// React commands
|
|
76
|
+
if (cmd.action === "react-tree") {
|
|
77
|
+
const data = await api.reactTree(cmd.id);
|
|
78
|
+
return { ok: true, data };
|
|
79
|
+
}
|
|
80
|
+
// Svelte commands
|
|
81
|
+
if (cmd.action === "svelte-tree") {
|
|
82
|
+
const data = await api.svelteTree(cmd.id);
|
|
83
|
+
return { ok: true, data };
|
|
84
|
+
}
|
|
85
|
+
// Vite commands
|
|
86
|
+
if (cmd.action === "vite-restart") {
|
|
87
|
+
const data = await api.viteRestart();
|
|
88
|
+
return { ok: true, data };
|
|
89
|
+
}
|
|
90
|
+
if (cmd.action === "vite-hmr") {
|
|
91
|
+
const hmrMode = cmd.mode === "trace" || cmd.mode === "clear" ? cmd.mode : "summary";
|
|
92
|
+
const data = await api.viteHMRTrace(hmrMode, cmd.limit ?? 20);
|
|
93
|
+
return { ok: true, data };
|
|
94
|
+
}
|
|
95
|
+
if (cmd.action === "vite-runtime") {
|
|
96
|
+
const data = await api.viteRuntimeStatus();
|
|
97
|
+
return { ok: true, data };
|
|
98
|
+
}
|
|
99
|
+
if (cmd.action === "vite-module-graph") {
|
|
100
|
+
const graphMode = cmd.mode === "trace" || cmd.mode === "clear" ? cmd.mode : "snapshot";
|
|
101
|
+
const data = await api.viteModuleGraph(cmd.filter, cmd.limit ?? 200, graphMode);
|
|
102
|
+
return { ok: true, data };
|
|
103
|
+
}
|
|
104
|
+
if (cmd.action === "errors") {
|
|
105
|
+
const data = await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource));
|
|
106
|
+
return { ok: true, data };
|
|
107
|
+
}
|
|
108
|
+
if (cmd.action === "correlate-errors") {
|
|
109
|
+
const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
|
|
110
|
+
const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
|
|
111
|
+
const data = formatErrorCorrelationReport(errorText, errorText === "no errors" ? null : correlateErrorWithHMR(errorText, events, cmd.windowMs ?? 5000));
|
|
112
|
+
return { ok: true, data };
|
|
113
|
+
}
|
|
114
|
+
if (cmd.action === "diagnose-hmr") {
|
|
115
|
+
const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
|
|
116
|
+
const runtimeText = String(await api.viteRuntimeStatus());
|
|
117
|
+
const hmrTraceText = String(await api.viteHMRTrace("trace", cmd.limit ?? 50));
|
|
118
|
+
const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
|
|
119
|
+
const correlation = errorText === "no errors" ? null : correlateErrorWithHMR(errorText, events, cmd.windowMs ?? 5000);
|
|
120
|
+
const data = formatDiagnosisReport(diagnoseHMR({ errorText, runtimeText, hmrTraceText, correlation }));
|
|
121
|
+
return { ok: true, data };
|
|
122
|
+
}
|
|
123
|
+
if (cmd.action === "logs") {
|
|
124
|
+
const data = await api.logs();
|
|
125
|
+
return { ok: true, data };
|
|
126
|
+
}
|
|
127
|
+
// Utilities
|
|
128
|
+
if (cmd.action === "screenshot") {
|
|
129
|
+
const data = await api.screenshot();
|
|
130
|
+
return { ok: true, data };
|
|
131
|
+
}
|
|
132
|
+
if (cmd.action === "eval") {
|
|
133
|
+
const data = await api.evaluate(cmd.script);
|
|
134
|
+
return { ok: true, data };
|
|
135
|
+
}
|
|
136
|
+
if (cmd.action === "network") {
|
|
137
|
+
const data = await api.network(cmd.idx);
|
|
138
|
+
return { ok: true, data };
|
|
139
|
+
}
|
|
140
|
+
return { ok: false, error: `unknown action: ${cmd.action}` };
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export async function dispatchLine(line, socket, run = createRunner(), onClose) {
|
|
144
|
+
let cmd;
|
|
145
|
+
try {
|
|
146
|
+
cmd = JSON.parse(line);
|
|
128
147
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return
|
|
148
|
+
catch {
|
|
149
|
+
socket.write(JSON.stringify({ ok: false, error: "invalid command payload" }) + "\n");
|
|
150
|
+
return;
|
|
132
151
|
}
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
process.exit(0);
|
|
152
|
+
const result = await run(cmd).catch((err) => ({ ok: false, error: cleanError(err) }));
|
|
153
|
+
socket.write(JSON.stringify({ id: cmd.id, ...result }) + "\n");
|
|
154
|
+
if (cmd.action === "close")
|
|
155
|
+
setImmediate(() => onClose?.());
|
|
138
156
|
}
|
|
139
|
-
function
|
|
157
|
+
export function startDaemon() {
|
|
158
|
+
// Initialize event queue
|
|
159
|
+
const eventQueue = new EventQueue(1000);
|
|
160
|
+
browser.setEventQueue(eventQueue);
|
|
161
|
+
networkLog.setEventQueue(eventQueue);
|
|
162
|
+
const run = createRunner();
|
|
163
|
+
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
140
164
|
removeSocketFile();
|
|
141
165
|
rmSync(pidFile, { force: true });
|
|
166
|
+
writeFileSync(pidFile, String(process.pid));
|
|
167
|
+
const server = createServer((socket) => {
|
|
168
|
+
let buffer = "";
|
|
169
|
+
socket.on("data", (chunk) => {
|
|
170
|
+
buffer += chunk;
|
|
171
|
+
let newline;
|
|
172
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
173
|
+
const line = buffer.slice(0, newline);
|
|
174
|
+
buffer = buffer.slice(newline + 1);
|
|
175
|
+
if (line) {
|
|
176
|
+
void dispatchLine(line, socket, run, shutdown);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
socket.on("error", () => { });
|
|
181
|
+
});
|
|
182
|
+
server.listen(socketPath);
|
|
183
|
+
process.on("SIGINT", shutdown);
|
|
184
|
+
process.on("SIGTERM", shutdown);
|
|
185
|
+
process.on("exit", cleanup);
|
|
186
|
+
function shutdown() {
|
|
187
|
+
cleanup();
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
function cleanup() {
|
|
191
|
+
removeSocketFile();
|
|
192
|
+
rmSync(pidFile, { force: true });
|
|
193
|
+
}
|
|
142
194
|
}
|
|
143
195
|
function removeSocketFile() {
|
|
144
196
|
// Windows named pipes are not filesystem entries, so unlinking them fails with EPERM.
|
|
@@ -146,3 +198,6 @@ function removeSocketFile() {
|
|
|
146
198
|
return;
|
|
147
199
|
rmSync(socketPath, { force: true });
|
|
148
200
|
}
|
|
201
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
202
|
+
startDaemon();
|
|
203
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ErrorCorrelation } from "./correlate.js";
|
|
2
|
+
export type DiagnosisStatus = "pass" | "warn" | "fail";
|
|
3
|
+
export type DiagnosisConfidence = "high" | "medium" | "low";
|
|
4
|
+
export type DiagnosisResult = {
|
|
5
|
+
code: "circular-dependency" | "missing-module" | "hmr-websocket-closed" | "repeated-full-reload";
|
|
6
|
+
status: DiagnosisStatus;
|
|
7
|
+
confidence: DiagnosisConfidence;
|
|
8
|
+
summary: string;
|
|
9
|
+
detail: string;
|
|
10
|
+
suggestion: string;
|
|
11
|
+
};
|
|
12
|
+
export type DiagnoseInput = {
|
|
13
|
+
errorText: string;
|
|
14
|
+
runtimeText: string;
|
|
15
|
+
hmrTraceText: string;
|
|
16
|
+
correlation: ErrorCorrelation | null;
|
|
17
|
+
};
|
|
18
|
+
export declare function diagnoseHMR(input: DiagnoseInput): DiagnosisResult[];
|
|
19
|
+
export declare function formatDiagnosisReport(results: DiagnosisResult[]): string;
|
package/dist/diagnose.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function diagnoseHMR(input) {
|
|
2
|
+
const results = [
|
|
3
|
+
detectCircularDependency(input),
|
|
4
|
+
detectMissingModule(input),
|
|
5
|
+
detectClosedWebsocket(input),
|
|
6
|
+
detectRepeatedFullReload(input),
|
|
7
|
+
].filter((result) => result !== null);
|
|
8
|
+
if (results.length > 0)
|
|
9
|
+
return results;
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
code: "hmr-websocket-closed",
|
|
13
|
+
status: "pass",
|
|
14
|
+
confidence: "low",
|
|
15
|
+
summary: "No obvious HMR failure pattern detected",
|
|
16
|
+
detail: "Runtime, current error text, and HMR trace did not match any built-in failure rules.",
|
|
17
|
+
suggestion: "If symptoms persist, inspect `vite hmr trace`, `network`, and `correlate errors` output together.",
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
export function formatDiagnosisReport(results) {
|
|
22
|
+
const lines = ["# HMR Diagnosis", ""];
|
|
23
|
+
for (const result of results) {
|
|
24
|
+
lines.push(`## ${result.code}`, `Status: ${result.status}`, `Confidence: ${result.confidence}`, result.summary, result.detail, `Suggestion: ${result.suggestion}`, "");
|
|
25
|
+
}
|
|
26
|
+
return lines.join("\n").trimEnd();
|
|
27
|
+
}
|
|
28
|
+
function detectCircularDependency(input) {
|
|
29
|
+
const text = `${input.errorText}\n${input.hmrTraceText}`;
|
|
30
|
+
if (!/circular (dependency|import)|import cycle/i.test(text))
|
|
31
|
+
return null;
|
|
32
|
+
const moduleText = input.correlation?.matchingModules.length
|
|
33
|
+
? `Likely modules: ${input.correlation.matchingModules.join(", ")}.`
|
|
34
|
+
: "The error text points to a circular import/dependency chain.";
|
|
35
|
+
return {
|
|
36
|
+
code: "circular-dependency",
|
|
37
|
+
status: "fail",
|
|
38
|
+
confidence: input.correlation?.matchingModules.length ? "high" : "medium",
|
|
39
|
+
summary: "HMR is likely breaking because of a circular dependency.",
|
|
40
|
+
detail: `${moduleText} Circular imports often prevent safe hot replacement and force stale state or reload loops.`,
|
|
41
|
+
suggestion: "Break the import cycle by extracting shared code into a leaf module or switching one edge to a lazy import.",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function detectMissingModule(input) {
|
|
45
|
+
const text = input.errorText;
|
|
46
|
+
if (!/failed to resolve import|cannot find module|could not resolve|does the file exist/i.test(text)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
code: "missing-module",
|
|
51
|
+
status: "fail",
|
|
52
|
+
confidence: "high",
|
|
53
|
+
summary: "A module import failed to resolve during HMR.",
|
|
54
|
+
detail: input.correlation?.matchingModules.length
|
|
55
|
+
? `The current error overlaps with recent updates to ${input.correlation.matchingModules.join(", ")}.`
|
|
56
|
+
: "The current error text matches a missing or unresolved import pattern.",
|
|
57
|
+
suggestion: "Verify the import path, file extension, alias configuration, and whether the module exists on disk.",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function detectClosedWebsocket(input) {
|
|
61
|
+
const runtimeClosed = /HMR Socket:\s*(closed|closing|unknown)/i.test(input.runtimeText);
|
|
62
|
+
const traceClosed = /disconnected|failed to connect|connection lost|ws closed/i.test(input.hmrTraceText);
|
|
63
|
+
if (!runtimeClosed && !traceClosed)
|
|
64
|
+
return null;
|
|
65
|
+
return {
|
|
66
|
+
code: "hmr-websocket-closed",
|
|
67
|
+
status: "fail",
|
|
68
|
+
confidence: runtimeClosed && traceClosed ? "high" : "medium",
|
|
69
|
+
summary: "The HMR websocket is not healthy.",
|
|
70
|
+
detail: runtimeClosed
|
|
71
|
+
? "Runtime status reports the HMR socket as closed, closing, or unknown."
|
|
72
|
+
: "HMR trace contains disconnect or websocket failure messages.",
|
|
73
|
+
suggestion: "Check the dev server is running, the page is connected to the correct origin, and no proxy/firewall is blocking the websocket.",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function detectRepeatedFullReload(input) {
|
|
77
|
+
const matches = input.hmrTraceText.match(/full-reload|page reload/gi) ?? [];
|
|
78
|
+
if (matches.length < 2)
|
|
79
|
+
return null;
|
|
80
|
+
return {
|
|
81
|
+
code: "repeated-full-reload",
|
|
82
|
+
status: "warn",
|
|
83
|
+
confidence: matches.length >= 3 ? "high" : "medium",
|
|
84
|
+
summary: "Vite is repeatedly falling back to full page reloads.",
|
|
85
|
+
detail: `Observed ${matches.length} full-reload events in the recent HMR trace.`,
|
|
86
|
+
suggestion: "Check whether the changed module is outside HMR boundaries, introduces side effects, or triggers a circular dependency.",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type VBEventType = 'hmr-update' | 'hmr-error' | 'module-change' | 'network' | 'error' | 'render';
|
|
2
|
+
export interface VBEvent {
|
|
3
|
+
timestamp: number;
|
|
4
|
+
type: VBEventType;
|
|
5
|
+
payload: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare class EventQueue {
|
|
8
|
+
private events;
|
|
9
|
+
private readonly maxSize;
|
|
10
|
+
constructor(maxSize?: number);
|
|
11
|
+
push(event: VBEvent): void;
|
|
12
|
+
/**
|
|
13
|
+
* Return all events within the last `ms` milliseconds before `before`
|
|
14
|
+
*/
|
|
15
|
+
window(ms: number, before?: number): VBEvent[];
|
|
16
|
+
/**
|
|
17
|
+
* Return all events of a given type
|
|
18
|
+
*/
|
|
19
|
+
ofType(type: VBEventType): VBEvent[];
|
|
20
|
+
all(): VBEvent[];
|
|
21
|
+
clear(): void;
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class EventQueue {
|
|
2
|
+
events = [];
|
|
3
|
+
maxSize;
|
|
4
|
+
constructor(maxSize = 1000) {
|
|
5
|
+
this.maxSize = maxSize;
|
|
6
|
+
}
|
|
7
|
+
push(event) {
|
|
8
|
+
this.events.push(event);
|
|
9
|
+
if (this.events.length > this.maxSize) {
|
|
10
|
+
this.events.shift();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Return all events within the last `ms` milliseconds before `before`
|
|
15
|
+
*/
|
|
16
|
+
window(ms, before = Date.now()) {
|
|
17
|
+
const start = before - ms;
|
|
18
|
+
return this.events.filter((e) => e.timestamp >= start && e.timestamp <= before);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Return all events of a given type
|
|
22
|
+
*/
|
|
23
|
+
ofType(type) {
|
|
24
|
+
return this.events.filter((e) => e.type === type);
|
|
25
|
+
}
|
|
26
|
+
all() {
|
|
27
|
+
return [...this.events];
|
|
28
|
+
}
|
|
29
|
+
clear() {
|
|
30
|
+
this.events = [];
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/network.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
|
+
import type { EventQueue } from "./event-queue.js";
|
|
3
|
+
export declare function setEventQueue(queue: EventQueue): void;
|
|
2
4
|
export declare function attach(page: Page): void;
|
|
3
5
|
export declare function clear(): void;
|
|
4
6
|
export declare function detail(idx: number): Promise<string>;
|
package/dist/network.js
CHANGED
|
@@ -4,6 +4,10 @@ import { join } from "node:path";
|
|
|
4
4
|
const BODY_INLINE_LIMIT = 4000;
|
|
5
5
|
let entries = [];
|
|
6
6
|
let startTime = new Map();
|
|
7
|
+
let eventQueue = null;
|
|
8
|
+
export function setEventQueue(queue) {
|
|
9
|
+
eventQueue = queue;
|
|
10
|
+
}
|
|
7
11
|
export function attach(page) {
|
|
8
12
|
page.on("request", (req) => {
|
|
9
13
|
if (req.resourceType() === "document" && req.frame() === page.mainFrame()) {
|
|
@@ -16,7 +20,21 @@ export function attach(page) {
|
|
|
16
20
|
const t0 = startTime.get(req);
|
|
17
21
|
if (t0 == null)
|
|
18
22
|
return;
|
|
19
|
-
|
|
23
|
+
const ms = Date.now() - t0;
|
|
24
|
+
entries.push({ req, res, ms });
|
|
25
|
+
// Push to event queue if available
|
|
26
|
+
if (eventQueue) {
|
|
27
|
+
eventQueue.push({
|
|
28
|
+
timestamp: Date.now(),
|
|
29
|
+
type: 'network',
|
|
30
|
+
payload: {
|
|
31
|
+
url: req.url(),
|
|
32
|
+
method: req.method(),
|
|
33
|
+
status: res.status(),
|
|
34
|
+
ms
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
20
38
|
});
|
|
21
39
|
page.on("requestfailed", (req) => {
|
|
22
40
|
if (!startTime.has(req))
|
package/dist/react/devtools.d.ts
CHANGED
|
@@ -14,3 +14,16 @@ export declare function snapshot(page: Page): Promise<ReactNode[]>;
|
|
|
14
14
|
export declare function inspect(page: Page, id: number): Promise<ReactInspection>;
|
|
15
15
|
export declare function format(nodes: ReactNode[]): string;
|
|
16
16
|
export declare function path(nodes: ReactNode[], id: number): string;
|
|
17
|
+
export declare function typeName(type: number): string;
|
|
18
|
+
export declare function decodeOperations(ops: number[]): ReactNode[];
|
|
19
|
+
export declare function skipOperation(op: number, ops: number[], i: number): number;
|
|
20
|
+
export declare function rectCount(n: number): number;
|
|
21
|
+
export declare function suspenseSkip(ops: number[], i: number): number;
|
|
22
|
+
export declare function previewValue(v: unknown): string;
|
|
23
|
+
export declare function formatHookLine(h: {
|
|
24
|
+
id: number | null;
|
|
25
|
+
name: string;
|
|
26
|
+
value: unknown;
|
|
27
|
+
subHooks?: unknown[];
|
|
28
|
+
}): string;
|
|
29
|
+
export declare function formatInspectionResult(name: string, id: number, value: any): ReactInspection;
|