@presto1314w/vite-devtools-browser 0.3.1 → 0.3.3
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 +4 -1
- package/dist/browser-collector.js +64 -29
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +21 -1
- package/dist/cli.js +38 -8
- package/dist/daemon.js +147 -17
- package/dist/event-analysis.d.ts +2 -0
- package/dist/event-analysis.js +6 -0
- package/dist/trace.js +19 -4
- package/package.json +63 -65
package/README.md
CHANGED
|
@@ -271,12 +271,15 @@ vite-browser eval <script>
|
|
|
271
271
|
|
|
272
272
|
## Current Boundaries
|
|
273
273
|
|
|
274
|
-
`v0.3.
|
|
274
|
+
`v0.3.3` is strong at:
|
|
275
275
|
- surfacing runtime state as structured shell output
|
|
276
276
|
- linking current errors to recent HMR and module activity
|
|
277
277
|
- detecting common HMR failure patterns with confidence levels
|
|
278
278
|
- narrowing likely store/module -> render paths in Vue-first flows
|
|
279
279
|
- capturing browser-side runtime errors even when the Vite overlay is absent
|
|
280
|
+
- correlating sparse HMR traces back to the right source module more reliably
|
|
281
|
+
- retaining `changedKeys` and source-module hints when one side of the event stream is incomplete
|
|
282
|
+
- clearing stale runtime errors after recovery so the current page lifecycle is easier to trust
|
|
280
283
|
|
|
281
284
|
`correlate renders` and `diagnose propagation` are **high-confidence propagation clues**, not strict causal proof. They do not reliably trace deep chains like `store -> component A -> component B -> error` across arbitrary graphs, and intentionally fall back to conservative output when evidence is incomplete.
|
|
282
285
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
export function initBrowserEventCollector() {
|
|
2
|
-
if (window.__vb_events)
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
window.__vb_push
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
q.
|
|
10
|
-
|
|
2
|
+
if (!window.__vb_events) {
|
|
3
|
+
window.__vb_events = [];
|
|
4
|
+
}
|
|
5
|
+
if (!window.__vb_push) {
|
|
6
|
+
window.__vb_push = (event) => {
|
|
7
|
+
const q = window.__vb_events;
|
|
8
|
+
q.push(event);
|
|
9
|
+
if (q.length > 1000)
|
|
10
|
+
q.shift();
|
|
11
|
+
};
|
|
12
|
+
}
|
|
11
13
|
const inferFramework = () => {
|
|
12
14
|
if (window.__VUE__ || window.__VUE_DEVTOOLS_GLOBAL_HOOK__)
|
|
13
15
|
return "vue";
|
|
@@ -117,15 +119,16 @@ export function initBrowserEventCollector() {
|
|
|
117
119
|
}, 60);
|
|
118
120
|
};
|
|
119
121
|
const attachPiniaSubscriptions = () => {
|
|
120
|
-
if (window.__vb_pinia_attached)
|
|
121
|
-
return;
|
|
122
122
|
const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
|
|
123
123
|
const app = Array.isArray(hook?.apps) ? hook.apps[0] : null;
|
|
124
124
|
const pinia = window.__PINIA__ || window.pinia || app?.config?.globalProperties?.$pinia;
|
|
125
125
|
const registry = pinia?._s;
|
|
126
126
|
if (!(registry instanceof Map) || registry.size === 0)
|
|
127
127
|
return;
|
|
128
|
-
const attached =
|
|
128
|
+
const attached = window.__vb_pinia_attached ||
|
|
129
|
+
(window.__vb_pinia_attached = new Set());
|
|
130
|
+
const actionAttached = window.__vb_pinia_action_attached ||
|
|
131
|
+
(window.__vb_pinia_action_attached = new Set());
|
|
129
132
|
registry.forEach((store, storeId) => {
|
|
130
133
|
if (!store || typeof store.$subscribe !== "function" || attached.has(String(storeId)))
|
|
131
134
|
return;
|
|
@@ -161,10 +164,33 @@ export function initBrowserEventCollector() {
|
|
|
161
164
|
},
|
|
162
165
|
});
|
|
163
166
|
scheduleRender("store-update", { changedKeys });
|
|
164
|
-
},
|
|
167
|
+
},
|
|
168
|
+
// Flush synchronously so the store event is recorded before a render-time failure
|
|
169
|
+
// can short-circuit the rest of Vue's update cycle.
|
|
170
|
+
{ detached: true, flush: "sync" });
|
|
171
|
+
if (typeof store.$onAction === "function" && !actionAttached.has(String(storeId))) {
|
|
172
|
+
actionAttached.add(String(storeId));
|
|
173
|
+
store.$onAction(({ name, after }) => {
|
|
174
|
+
after(() => {
|
|
175
|
+
window.__vb_push({
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
type: "store-update",
|
|
178
|
+
payload: {
|
|
179
|
+
store: String(storeId),
|
|
180
|
+
mutationType: typeof name === "string" && name.length > 0 ? `action:${name}` : "action",
|
|
181
|
+
events: 0,
|
|
182
|
+
changedKeys: [],
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
scheduleRender("store-update");
|
|
186
|
+
});
|
|
187
|
+
}, true);
|
|
188
|
+
}
|
|
165
189
|
});
|
|
166
190
|
};
|
|
167
191
|
function attachViteListener() {
|
|
192
|
+
if (window.__vb_vite_listener_attached)
|
|
193
|
+
return true;
|
|
168
194
|
const hot = window.__vite_hot;
|
|
169
195
|
if (hot?.ws) {
|
|
170
196
|
hot.ws.addEventListener("message", (e) => {
|
|
@@ -181,6 +207,7 @@ export function initBrowserEventCollector() {
|
|
|
181
207
|
}
|
|
182
208
|
catch { }
|
|
183
209
|
});
|
|
210
|
+
window.__vb_vite_listener_attached = true;
|
|
184
211
|
return true;
|
|
185
212
|
}
|
|
186
213
|
return false;
|
|
@@ -194,22 +221,28 @@ export function initBrowserEventCollector() {
|
|
|
194
221
|
}
|
|
195
222
|
}, 100);
|
|
196
223
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
window.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
window.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
224
|
+
if (!window.__vb_onerror_attached) {
|
|
225
|
+
const origOnError = window.onerror;
|
|
226
|
+
window.onerror = (msg, src, line, col, err) => {
|
|
227
|
+
window.__vb_push({
|
|
228
|
+
timestamp: Date.now(),
|
|
229
|
+
type: "error",
|
|
230
|
+
payload: { message: String(msg), source: src, line, col, stack: err?.stack },
|
|
231
|
+
});
|
|
232
|
+
return origOnError ? origOnError(msg, src, line, col, err) : false;
|
|
233
|
+
};
|
|
234
|
+
window.__vb_onerror_attached = true;
|
|
235
|
+
}
|
|
236
|
+
if (!window.__vb_unhandledrejection_attached) {
|
|
237
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
238
|
+
window.__vb_push({
|
|
239
|
+
timestamp: Date.now(),
|
|
240
|
+
type: "error",
|
|
241
|
+
payload: { message: e.reason?.message, stack: e.reason?.stack },
|
|
242
|
+
});
|
|
211
243
|
});
|
|
212
|
-
|
|
244
|
+
window.__vb_unhandledrejection_attached = true;
|
|
245
|
+
}
|
|
213
246
|
const observeDom = () => {
|
|
214
247
|
const root = document.body || document.documentElement;
|
|
215
248
|
if (!root || window.__vb_render_observer)
|
|
@@ -246,7 +279,9 @@ export function initBrowserEventCollector() {
|
|
|
246
279
|
observeDom();
|
|
247
280
|
patchHistory();
|
|
248
281
|
attachPiniaSubscriptions();
|
|
249
|
-
window.
|
|
282
|
+
if (!window.__vb_pinia_retry_timer) {
|
|
283
|
+
window.__vb_pinia_retry_timer = window.setInterval(attachPiniaSubscriptions, 250);
|
|
284
|
+
}
|
|
250
285
|
scheduleRender("initial-load");
|
|
251
286
|
}
|
|
252
287
|
export function readBrowserEvents() {
|
package/dist/browser.d.ts
CHANGED
|
@@ -7,6 +7,17 @@ export { formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, form
|
|
|
7
7
|
export type ModuleGraphMode = "snapshot" | "trace" | "clear";
|
|
8
8
|
export declare function setEventQueue(queue: EventQueue): void;
|
|
9
9
|
export declare function getEventQueue(): EventQueue | null;
|
|
10
|
+
export declare function getTrackedHmrEvents(windowMs?: number): Array<{
|
|
11
|
+
timestamp: number;
|
|
12
|
+
type: "hmr-update" | "hmr-error";
|
|
13
|
+
payload: {
|
|
14
|
+
path?: string;
|
|
15
|
+
message?: string;
|
|
16
|
+
updates?: Array<{
|
|
17
|
+
path?: string;
|
|
18
|
+
}>;
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
10
21
|
export declare function getCurrentPage(): Page | null;
|
|
11
22
|
/**
|
|
12
23
|
* Flush browser events into daemon event queue
|
package/dist/browser.js
CHANGED
|
@@ -19,6 +19,20 @@ export function setEventQueue(queue) {
|
|
|
19
19
|
export function getEventQueue() {
|
|
20
20
|
return eventQueue;
|
|
21
21
|
}
|
|
22
|
+
export function getTrackedHmrEvents(windowMs = 5000) {
|
|
23
|
+
const cutoff = Date.now() - windowMs;
|
|
24
|
+
return session.hmrEvents
|
|
25
|
+
.filter((event) => event.timestamp >= cutoff)
|
|
26
|
+
.map((event) => ({
|
|
27
|
+
timestamp: event.timestamp,
|
|
28
|
+
type: event.type === "error" ? "hmr-error" : "hmr-update",
|
|
29
|
+
payload: {
|
|
30
|
+
path: event.path,
|
|
31
|
+
message: event.message,
|
|
32
|
+
updates: event.path ? [{ path: event.path }] : [],
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
22
36
|
export function getCurrentPage() {
|
|
23
37
|
return getSessionPage(session);
|
|
24
38
|
}
|
|
@@ -36,6 +50,7 @@ async function injectEventCollector(currentPage) {
|
|
|
36
50
|
* Flush browser events into daemon event queue
|
|
37
51
|
*/
|
|
38
52
|
export async function flushBrowserEvents(currentPage, queue) {
|
|
53
|
+
await currentPage.evaluate(initBrowserEventCollector);
|
|
39
54
|
const raw = await currentPage.evaluate(readBrowserEvents);
|
|
40
55
|
for (const e of raw) {
|
|
41
56
|
queue.push(e);
|
|
@@ -149,7 +164,7 @@ export async function goto(url) {
|
|
|
149
164
|
}
|
|
150
165
|
export async function back() {
|
|
151
166
|
const currentPage = await ensurePage();
|
|
152
|
-
await currentPage.goBack({ waitUntil: "domcontentloaded" });
|
|
167
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.goBack({ waitUntil: "domcontentloaded" }));
|
|
153
168
|
}
|
|
154
169
|
export async function reload() {
|
|
155
170
|
const currentPage = await ensurePage();
|
|
@@ -275,6 +290,7 @@ export async function screenshot() {
|
|
|
275
290
|
}
|
|
276
291
|
export async function evaluate(script) {
|
|
277
292
|
const currentPage = requireCurrentPage();
|
|
293
|
+
await currentPage.evaluate(initBrowserEventCollector);
|
|
278
294
|
const result = await currentPage.evaluate(script);
|
|
279
295
|
return JSON.stringify(result, null, 2);
|
|
280
296
|
}
|
|
@@ -315,8 +331,12 @@ function requireCurrentPage() {
|
|
|
315
331
|
}
|
|
316
332
|
async function navigateAndRefreshContext(currentPage, navigate, refreshFramework = false) {
|
|
317
333
|
await navigate();
|
|
334
|
+
clearRuntimeErrors();
|
|
318
335
|
await injectEventCollector(currentPage);
|
|
319
336
|
if (refreshFramework) {
|
|
320
337
|
await detectFramework();
|
|
321
338
|
}
|
|
322
339
|
}
|
|
340
|
+
function clearRuntimeErrors() {
|
|
341
|
+
session.runtimeErrors.length = 0;
|
|
342
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
3
6
|
import { send } from "./client.js";
|
|
7
|
+
class CliExit extends Error {
|
|
8
|
+
code;
|
|
9
|
+
constructor(code) {
|
|
10
|
+
super(`CLI_EXIT:${code}`);
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
4
14
|
export function normalizeUrl(value) {
|
|
5
15
|
if (value.includes("://"))
|
|
6
16
|
return value;
|
|
@@ -255,16 +265,36 @@ OPTIONS
|
|
|
255
265
|
`;
|
|
256
266
|
}
|
|
257
267
|
function isEntrypoint(argv1) {
|
|
258
|
-
|
|
268
|
+
if (!argv1)
|
|
269
|
+
return false;
|
|
270
|
+
try {
|
|
271
|
+
const current = realpathSync(fileURLToPath(import.meta.url));
|
|
272
|
+
const target = realpathSync(resolve(argv1));
|
|
273
|
+
return current === target;
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return Boolean(argv1 && import.meta.url.endsWith(argv1.replaceAll("\\", "/")));
|
|
277
|
+
}
|
|
259
278
|
}
|
|
260
279
|
async function main() {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
280
|
+
try {
|
|
281
|
+
await runCli(process.argv, {
|
|
282
|
+
send,
|
|
283
|
+
readFile: readFileSync,
|
|
284
|
+
stdout: (text) => process.stdout.write(`${text}\n`),
|
|
285
|
+
stderr: (text) => process.stderr.write(`${text}\n`),
|
|
286
|
+
exit: ((code) => {
|
|
287
|
+
throw new CliExit(code);
|
|
288
|
+
}),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
if (error instanceof CliExit) {
|
|
293
|
+
process.exitCode = error.code;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
throw error;
|
|
297
|
+
}
|
|
268
298
|
}
|
|
269
299
|
if (isEntrypoint(process.argv[1])) {
|
|
270
300
|
void main().catch((error) => {
|
package/dist/daemon.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { createServer } from "node:net";
|
|
2
2
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
3
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import * as browser from "./browser.js";
|
|
5
6
|
import { correlateErrorWithHMR, formatErrorCorrelationReport } from "./correlate.js";
|
|
6
7
|
import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
|
|
7
8
|
import { diagnosePropagation, formatPropagationDiagnosisReport } from "./diagnose-propagation.js";
|
|
9
|
+
import { extractModules } from "./event-analysis.js";
|
|
8
10
|
import { socketDir, socketPath, pidFile } from "./paths.js";
|
|
9
11
|
import { correlateRenderPropagation, formatPropagationTraceReport } from "./trace.js";
|
|
10
12
|
import { EventQueue } from "./event-queue.js";
|
|
@@ -18,19 +20,8 @@ export function cleanError(err) {
|
|
|
18
20
|
}
|
|
19
21
|
export function createRunner(api = browser) {
|
|
20
22
|
return async function run(cmd) {
|
|
21
|
-
// Flush browser events to daemon queue before processing command
|
|
22
23
|
const queue = api.getEventQueue();
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const currentPage = api.getCurrentPage();
|
|
26
|
-
if (currentPage) {
|
|
27
|
-
await api.flushBrowserEvents(currentPage, queue);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
// Ignore flush errors (page might not be open yet)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
24
|
+
await flushCurrentPageEvents(api, queue);
|
|
34
25
|
// Browser control
|
|
35
26
|
if (cmd.action === "open") {
|
|
36
27
|
await api.open(cmd.url);
|
|
@@ -110,12 +101,20 @@ export function createRunner(api = browser) {
|
|
|
110
101
|
if (cmd.action === "correlate-errors") {
|
|
111
102
|
const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
|
|
112
103
|
const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
|
|
113
|
-
const
|
|
104
|
+
const fallbackHmrEvents = readTrackedHmrEvents(api, cmd.windowMs ?? 5000);
|
|
105
|
+
const hmrTraceText = String(await api.viteHMRTrace("trace", 20));
|
|
106
|
+
const fallbackModules = extractModules(hmrTraceText);
|
|
107
|
+
const baseCorrelation = errorText === "no errors"
|
|
108
|
+
? null
|
|
109
|
+
: correlateErrorWithHMR(errorText, [...events, ...fallbackHmrEvents], cmd.windowMs ?? 5000);
|
|
110
|
+
const data = formatErrorCorrelationReport(errorText, errorText === "no errors"
|
|
111
|
+
? null
|
|
112
|
+
: upgradeErrorCorrelation(baseCorrelation, fallbackModules, cmd.windowMs ?? 5000));
|
|
114
113
|
return { ok: true, data };
|
|
115
114
|
}
|
|
116
115
|
if (cmd.action === "correlate-renders") {
|
|
117
|
-
const events =
|
|
118
|
-
const data = formatPropagationTraceReport(
|
|
116
|
+
const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
|
|
117
|
+
const data = formatPropagationTraceReport(await buildPropagationTrace(api, events, cmd.windowMs ?? 5000));
|
|
119
118
|
return { ok: true, data };
|
|
120
119
|
}
|
|
121
120
|
if (cmd.action === "diagnose-hmr") {
|
|
@@ -128,8 +127,8 @@ export function createRunner(api = browser) {
|
|
|
128
127
|
return { ok: true, data };
|
|
129
128
|
}
|
|
130
129
|
if (cmd.action === "diagnose-propagation") {
|
|
131
|
-
const events =
|
|
132
|
-
const data = formatPropagationDiagnosisReport(diagnosePropagation(
|
|
130
|
+
const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
|
|
131
|
+
const data = formatPropagationDiagnosisReport(diagnosePropagation(await buildPropagationTrace(api, events, cmd.windowMs ?? 5000)));
|
|
133
132
|
return { ok: true, data };
|
|
134
133
|
}
|
|
135
134
|
if (cmd.action === "logs") {
|
|
@@ -143,6 +142,10 @@ export function createRunner(api = browser) {
|
|
|
143
142
|
}
|
|
144
143
|
if (cmd.action === "eval") {
|
|
145
144
|
const data = await api.evaluate(cmd.script);
|
|
145
|
+
await settleCurrentPage(api, 120);
|
|
146
|
+
await flushCurrentPageEvents(api, queue);
|
|
147
|
+
await settleCurrentPage(api, 180);
|
|
148
|
+
await flushCurrentPageEvents(api, queue);
|
|
146
149
|
return { ok: true, data };
|
|
147
150
|
}
|
|
148
151
|
if (cmd.action === "network") {
|
|
@@ -152,6 +155,133 @@ export function createRunner(api = browser) {
|
|
|
152
155
|
return { ok: false, error: `unknown action: ${cmd.action}` };
|
|
153
156
|
};
|
|
154
157
|
}
|
|
158
|
+
async function buildPropagationTrace(api, events, windowMs) {
|
|
159
|
+
const hmrTraceText = String(await api.viteHMRTrace("trace", 20));
|
|
160
|
+
const fallbackModules = extractModules(hmrTraceText);
|
|
161
|
+
const trace = correlateRenderPropagation([...events, ...readTrackedHmrEvents(api, windowMs)]);
|
|
162
|
+
if (!trace)
|
|
163
|
+
return null;
|
|
164
|
+
let augmented = trace;
|
|
165
|
+
if (augmented.sourceModules.length === 0 && fallbackModules.length > 0) {
|
|
166
|
+
augmented = {
|
|
167
|
+
...augmented,
|
|
168
|
+
sourceModules: fallbackModules,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (augmented.changedKeys.length === 0 && augmented.storeUpdates.length > 0) {
|
|
172
|
+
const inferredChangedKeys = await inferLikelyChangedKeys(api, augmented.storeUpdates[0]);
|
|
173
|
+
if (inferredChangedKeys.length > 0) {
|
|
174
|
+
augmented = {
|
|
175
|
+
...augmented,
|
|
176
|
+
changedKeys: inferredChangedKeys,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (augmented.errorMessages.length > 0)
|
|
181
|
+
return augmented;
|
|
182
|
+
const currentError = String(await api.errors(false, false));
|
|
183
|
+
if (!currentError || currentError === "no errors")
|
|
184
|
+
return augmented;
|
|
185
|
+
return {
|
|
186
|
+
...augmented,
|
|
187
|
+
errorMessages: [currentError, ...augmented.errorMessages],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function upgradeErrorCorrelation(correlation, fallbackModules, windowMs) {
|
|
191
|
+
if (fallbackModules.length === 0)
|
|
192
|
+
return correlation;
|
|
193
|
+
if (!correlation)
|
|
194
|
+
return synthesizeErrorCorrelation(fallbackModules, windowMs);
|
|
195
|
+
if (correlation.matchingModules.length > 0)
|
|
196
|
+
return correlation;
|
|
197
|
+
return synthesizeErrorCorrelation(fallbackModules, windowMs, correlation.relatedEvents);
|
|
198
|
+
}
|
|
199
|
+
function synthesizeErrorCorrelation(modules, windowMs, relatedEvents = []) {
|
|
200
|
+
if (modules.length === 0)
|
|
201
|
+
return null;
|
|
202
|
+
return {
|
|
203
|
+
summary: `HMR update observed within ${windowMs}ms of the current error`,
|
|
204
|
+
detail: `Matching modules: ${modules.join(", ")}\nRecent events considered: ${modules.length}`,
|
|
205
|
+
confidence: "high",
|
|
206
|
+
windowMs,
|
|
207
|
+
matchingModules: modules,
|
|
208
|
+
relatedEvents: relatedEvents.length > 0
|
|
209
|
+
? relatedEvents
|
|
210
|
+
: modules.map((module) => ({
|
|
211
|
+
timestamp: Date.now(),
|
|
212
|
+
type: "hmr-update",
|
|
213
|
+
payload: {
|
|
214
|
+
path: module,
|
|
215
|
+
updates: [{ path: module }],
|
|
216
|
+
},
|
|
217
|
+
})),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function inferLikelyChangedKeys(api, storeName) {
|
|
221
|
+
try {
|
|
222
|
+
const raw = await api.evaluate(`(() => {
|
|
223
|
+
const store = window.__PINIA__?._s?.get(${JSON.stringify(storeName)});
|
|
224
|
+
if (!store) return [];
|
|
225
|
+
return Object.keys(store).filter((key) => store[key] === undefined);
|
|
226
|
+
})()`);
|
|
227
|
+
const parsed = JSON.parse(String(raw));
|
|
228
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string" && value.length > 0) : [];
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function readTrackedHmrEvents(api, windowMs) {
|
|
235
|
+
const reader = api.getTrackedHmrEvents;
|
|
236
|
+
if (typeof reader !== "function")
|
|
237
|
+
return [];
|
|
238
|
+
const events = reader(windowMs);
|
|
239
|
+
return Array.isArray(events) ? events : [];
|
|
240
|
+
}
|
|
241
|
+
async function flushCurrentPageEvents(api, queue) {
|
|
242
|
+
if (!queue)
|
|
243
|
+
return;
|
|
244
|
+
try {
|
|
245
|
+
const currentPage = api.getCurrentPage();
|
|
246
|
+
if (currentPage) {
|
|
247
|
+
await api.flushBrowserEvents(currentPage, queue);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Ignore flush errors (page might not be open yet)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async function getSettledEventWindow(api, queue, windowMs) {
|
|
255
|
+
if (!queue)
|
|
256
|
+
return [];
|
|
257
|
+
let events = queue.window(windowMs);
|
|
258
|
+
if (hasPropagationSignals(events))
|
|
259
|
+
return events;
|
|
260
|
+
for (const delayMs of [120, 300]) {
|
|
261
|
+
await settleCurrentPage(api, delayMs);
|
|
262
|
+
await flushCurrentPageEvents(api, queue);
|
|
263
|
+
events = queue.window(windowMs);
|
|
264
|
+
if (hasPropagationSignals(events))
|
|
265
|
+
return events;
|
|
266
|
+
}
|
|
267
|
+
return events;
|
|
268
|
+
}
|
|
269
|
+
function hasPropagationSignals(events) {
|
|
270
|
+
return events.some((event) => event.type === "render" || event.type === "store-update");
|
|
271
|
+
}
|
|
272
|
+
async function settleCurrentPage(api, delayMs) {
|
|
273
|
+
const currentPage = api.getCurrentPage();
|
|
274
|
+
if (!currentPage) {
|
|
275
|
+
await sleep(delayMs);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
await currentPage.waitForTimeout(delayMs);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
await sleep(delayMs);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
155
285
|
export async function dispatchLine(line, socket, run = createRunner(), onClose) {
|
|
156
286
|
let cmd;
|
|
157
287
|
try {
|
package/dist/event-analysis.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export declare function getRenderLabel(payload: RenderEventPayload): string;
|
|
|
27
27
|
export declare function getStoreName(payload: StoreUpdatePayload): string | null;
|
|
28
28
|
export declare function getChangedKeys(payload: StoreUpdatePayload): string[];
|
|
29
29
|
export declare function getStoreHints(payload: RenderEventPayload): string[];
|
|
30
|
+
export declare function getRenderReason(payload: RenderEventPayload): string | null;
|
|
31
|
+
export declare function getRenderChangedKeys(payload: RenderEventPayload): string[];
|
|
30
32
|
export declare function getNetworkUrl(payload: NetworkEventPayload): string | null;
|
|
31
33
|
export declare function getErrorMessage(payload: ErrorEventPayload): string | null;
|
|
32
34
|
export declare function uniqueStrings(values: string[]): string[];
|
package/dist/event-analysis.js
CHANGED
|
@@ -59,6 +59,12 @@ export function getChangedKeys(payload) {
|
|
|
59
59
|
export function getStoreHints(payload) {
|
|
60
60
|
return payload.storeHints.filter((value) => value.length > 0);
|
|
61
61
|
}
|
|
62
|
+
export function getRenderReason(payload) {
|
|
63
|
+
return typeof payload.reason === "string" && payload.reason.length > 0 ? payload.reason : null;
|
|
64
|
+
}
|
|
65
|
+
export function getRenderChangedKeys(payload) {
|
|
66
|
+
return payload.changedKeys.filter((value) => value.length > 0);
|
|
67
|
+
}
|
|
62
68
|
export function getNetworkUrl(payload) {
|
|
63
69
|
return payload.url.length > 0 ? payload.url : null;
|
|
64
70
|
}
|
package/dist/trace.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractModulesFromHmrEvent, getChangedKeys, getErrorEvents, getErrorMessage, getHmrEvents, getNetworkEvents, getNetworkUrl, getRenderEvents, getRenderLabel, getStoreHints, getStoreName, getStoreUpdateEvents, sortEventsChronologically, uniqueStrings, } from "./event-analysis.js";
|
|
1
|
+
import { extractModulesFromHmrEvent, getChangedKeys, getErrorEvents, getErrorMessage, getHmrEvents, getNetworkEvents, getNetworkUrl, getRenderChangedKeys, getRenderEvents, getRenderLabel, getRenderReason, getStoreHints, getStoreName, getStoreUpdateEvents, sortEventsChronologically, uniqueStrings, } from "./event-analysis.js";
|
|
2
2
|
export function correlateRenderPropagation(events) {
|
|
3
3
|
const recent = sortEventsChronologically(events);
|
|
4
4
|
const renderEvents = getRenderEvents(recent);
|
|
@@ -9,10 +9,25 @@ export function correlateRenderPropagation(events) {
|
|
|
9
9
|
const networkEvents = getNetworkEvents(recent);
|
|
10
10
|
const errorEvents = getErrorEvents(recent);
|
|
11
11
|
const sourceModules = uniqueStrings(hmrEvents.flatMap(extractModulesFromHmrEvent));
|
|
12
|
-
const
|
|
13
|
-
const changedKeys = uniqueStrings(storeEvents.flatMap((event) => getChangedKeys(event.payload)));
|
|
14
|
-
const renderComponents = uniqueStrings(renderEvents.map((event) => getRenderLabel(event.payload)).filter(Boolean));
|
|
12
|
+
const explicitStoreUpdates = uniqueStrings(storeEvents.map((event) => getStoreName(event.payload)).filter((value) => value != null));
|
|
15
13
|
const storeHints = uniqueStrings(renderEvents.flatMap((event) => getStoreHints(event.payload)));
|
|
14
|
+
const inferredStoreUpdates = explicitStoreUpdates.length > 0
|
|
15
|
+
? explicitStoreUpdates
|
|
16
|
+
: uniqueStrings(renderEvents
|
|
17
|
+
.filter((event) => getRenderReason(event.payload) === "store-update")
|
|
18
|
+
.flatMap((event) => getStoreHints(event.payload)));
|
|
19
|
+
const storeUpdates = inferredStoreUpdates.length > 0
|
|
20
|
+
? inferredStoreUpdates
|
|
21
|
+
: storeHints.length === 1
|
|
22
|
+
? storeHints
|
|
23
|
+
: [];
|
|
24
|
+
const changedKeys = uniqueStrings([
|
|
25
|
+
...storeEvents.flatMap((event) => getChangedKeys(event.payload)),
|
|
26
|
+
...renderEvents
|
|
27
|
+
.filter((event) => getRenderReason(event.payload) === "store-update")
|
|
28
|
+
.flatMap((event) => getRenderChangedKeys(event.payload)),
|
|
29
|
+
]);
|
|
30
|
+
const renderComponents = uniqueStrings(renderEvents.map((event) => getRenderLabel(event.payload)).filter(Boolean));
|
|
16
31
|
const networkUrls = uniqueStrings(networkEvents.map((event) => getNetworkUrl(event.payload)).filter((value) => value != null));
|
|
17
32
|
const errorMessages = uniqueStrings(errorEvents.map((event) => getErrorMessage(event.payload)).filter((value) => value != null));
|
|
18
33
|
const confidence = inferConfidence(sourceModules, storeUpdates, renderComponents, errorMessages);
|
package/package.json
CHANGED
|
@@ -1,65 +1,63 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@presto1314w/vite-devtools-browser",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Runtime diagnostics CLI for Vite apps with event-stream correlation, HMR diagnosis, framework inspection, and mapped errors",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"vite",
|
|
8
|
-
"devtools",
|
|
9
|
-
"debugging",
|
|
10
|
-
"runtime-diagnostics",
|
|
11
|
-
"hmr",
|
|
12
|
-
"module-graph",
|
|
13
|
-
"sourcemap",
|
|
14
|
-
"vue",
|
|
15
|
-
"react",
|
|
16
|
-
"svelte",
|
|
17
|
-
"cli",
|
|
18
|
-
"ai-agents"
|
|
19
|
-
],
|
|
20
|
-
"repository": {
|
|
21
|
-
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/MapleCity1314/vite-browser.git"
|
|
23
|
-
},
|
|
24
|
-
"homepage": "https://github.com/MapleCity1314/vite-browser#readme",
|
|
25
|
-
"bugs": {
|
|
26
|
-
"url": "https://github.com/MapleCity1314/vite-browser/issues"
|
|
27
|
-
},
|
|
28
|
-
"type": "module",
|
|
29
|
-
"engines": {
|
|
30
|
-
"node": ">=20"
|
|
31
|
-
},
|
|
32
|
-
"files": [
|
|
33
|
-
"dist"
|
|
34
|
-
],
|
|
35
|
-
"bin": {
|
|
36
|
-
"vite-browser": "dist/cli.js"
|
|
37
|
-
},
|
|
38
|
-
"
|
|
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
|
-
"packageManager": "pnpm@10.29.2"
|
|
65
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@presto1314w/vite-devtools-browser",
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "Runtime diagnostics CLI for Vite apps with event-stream correlation, HMR diagnosis, framework inspection, and mapped errors",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"vite",
|
|
8
|
+
"devtools",
|
|
9
|
+
"debugging",
|
|
10
|
+
"runtime-diagnostics",
|
|
11
|
+
"hmr",
|
|
12
|
+
"module-graph",
|
|
13
|
+
"sourcemap",
|
|
14
|
+
"vue",
|
|
15
|
+
"react",
|
|
16
|
+
"svelte",
|
|
17
|
+
"cli",
|
|
18
|
+
"ai-agents"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/MapleCity1314/vite-browser.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/MapleCity1314/vite-browser#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/MapleCity1314/vite-browser/issues"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
|
+
"bin": {
|
|
36
|
+
"vite-browser": "dist/cli.js"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"playwright": "^1.50.0",
|
|
40
|
+
"source-map-js": "^1.2.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
45
|
+
"@vue/devtools-kit": "^7.3.2",
|
|
46
|
+
"@vue/devtools-api": "^7.3.2",
|
|
47
|
+
"tsx": "^4.20.6",
|
|
48
|
+
"typescript": "^5.7.0",
|
|
49
|
+
"vitest": "^4.0.16"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"start": "node --import tsx src/cli.ts",
|
|
53
|
+
"dev": "tsx src/cli.ts",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"build": "tsc -p tsconfig.build.json",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"test:coverage": "vitest run --coverage",
|
|
59
|
+
"test:evals": "vitest run --dir test/evals",
|
|
60
|
+
"test:evals:ci": "vitest run --dir test/evals --coverage --reporter=default",
|
|
61
|
+
"test:evals:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts"
|
|
62
|
+
}
|
|
63
|
+
}
|