@presto1314w/vite-devtools-browser 0.1.2 → 0.1.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/dist/browser.d.ts CHANGED
@@ -15,7 +15,10 @@ export declare function reactTree(id?: string): Promise<string>;
15
15
  export declare function svelteTree(id?: string): Promise<string>;
16
16
  export declare function viteRestart(): Promise<string>;
17
17
  export declare function viteHMR(): Promise<string>;
18
- export declare function errors(): Promise<string>;
18
+ export declare function viteRuntimeStatus(): Promise<string>;
19
+ export declare function viteHMRTrace(mode: "summary" | "trace" | "clear", limit?: number): Promise<string>;
20
+ export declare function viteModuleGraph(filter?: string, limit?: number, mode?: "snapshot" | "trace" | "clear"): Promise<string>;
21
+ export declare function errors(mapped?: boolean, inlineSource?: boolean): Promise<string>;
19
22
  export declare function logs(): Promise<string>;
20
23
  export declare function screenshot(): Promise<string>;
21
24
  export declare function evaluate(script: string): Promise<string>;
package/dist/browser.js CHANGED
@@ -6,6 +6,7 @@ import * as vueDevtools from "./vue/devtools.js";
6
6
  import * as reactDevtools from "./react/devtools.js";
7
7
  import * as svelteDevtools from "./svelte/devtools.js";
8
8
  import * as networkLog from "./network.js";
9
+ import { resolveViaSourceMap } from "./sourcemap.js";
9
10
  const extensionPath = process.env.REACT_DEVTOOLS_EXTENSION ??
10
11
  resolve(import.meta.dirname, "../../next-browser/extensions/react-devtools-chrome");
11
12
  const hasReactExtension = existsSync(join(extensionPath, "manifest.json"));
@@ -18,7 +19,10 @@ let framework = "unknown";
18
19
  let extensionModeDisabled = false;
19
20
  const consoleLogs = [];
20
21
  const MAX_LOGS = 200;
22
+ const MAX_HMR_EVENTS = 500;
21
23
  let lastReactSnapshot = [];
24
+ const hmrEvents = [];
25
+ let lastModuleGraphUrls = null;
22
26
  export async function open(url) {
23
27
  const currentPage = await ensurePage();
24
28
  if (url) {
@@ -38,6 +42,8 @@ export async function close() {
38
42
  page = null;
39
43
  framework = "unknown";
40
44
  consoleLogs.length = 0;
45
+ hmrEvents.length = 0;
46
+ lastModuleGraphUrls = null;
41
47
  networkLog.clear();
42
48
  lastReactSnapshot = [];
43
49
  }
@@ -113,8 +119,41 @@ function attachListeners(currentPage) {
113
119
  consoleLogs.push(text);
114
120
  if (consoleLogs.length > MAX_LOGS)
115
121
  consoleLogs.shift();
122
+ const viteMessage = msg.text();
123
+ if (!viteMessage.includes("[vite]"))
124
+ return;
125
+ const event = parseViteLog(viteMessage);
126
+ hmrEvents.push(event);
127
+ if (hmrEvents.length > MAX_HMR_EVENTS)
128
+ hmrEvents.shift();
116
129
  });
117
130
  }
131
+ function parseViteLog(message) {
132
+ const lower = message.toLowerCase();
133
+ const event = {
134
+ timestamp: Date.now(),
135
+ type: "log",
136
+ message,
137
+ };
138
+ if (lower.includes("connecting"))
139
+ event.type = "connecting";
140
+ else if (lower.includes("connected"))
141
+ event.type = "connected";
142
+ else if (lower.includes("hot updated"))
143
+ event.type = "update";
144
+ else if (lower.includes("page reload"))
145
+ event.type = "full-reload";
146
+ else if (lower.includes("disconnected") ||
147
+ lower.includes("failed to connect") ||
148
+ lower.includes("connection lost") ||
149
+ lower.includes("error")) {
150
+ event.type = "error";
151
+ }
152
+ const hotUpdateMatch = message.match(/hot updated:\s*(.+)$/i);
153
+ if (hotUpdateMatch?.[1])
154
+ event.path = hotUpdateMatch[1].trim();
155
+ return event;
156
+ }
118
157
  export async function goto(url) {
119
158
  const currentPage = await ensurePage();
120
159
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
@@ -223,17 +262,202 @@ export async function viteRestart() {
223
262
  export async function viteHMR() {
224
263
  if (!page)
225
264
  throw new Error("browser not open");
265
+ return viteHMRTrace("summary", 20);
266
+ }
267
+ export async function viteRuntimeStatus() {
268
+ if (!page)
269
+ throw new Error("browser not open");
270
+ const runtime = await page.evaluate(() => {
271
+ const findViteClient = () => {
272
+ const scripts = Array.from(document.querySelectorAll("script[src]"));
273
+ return scripts.some((script) => script.getAttribute("src")?.includes("/@vite/client"));
274
+ };
275
+ const wsStateName = (wsState) => {
276
+ if (wsState == null)
277
+ return "unknown";
278
+ if (wsState === 0)
279
+ return "connecting";
280
+ if (wsState === 1)
281
+ return "open";
282
+ if (wsState === 2)
283
+ return "closing";
284
+ if (wsState === 3)
285
+ return "closed";
286
+ return "unknown";
287
+ };
288
+ const hot = window.__vite_hot;
289
+ const ws = hot?.ws || hot?.socket;
290
+ return {
291
+ url: location.href,
292
+ hasViteClient: findViteClient(),
293
+ wsState: wsStateName(ws?.readyState),
294
+ hasErrorOverlay: Boolean(document.querySelector("vite-error-overlay")),
295
+ timestamp: Date.now(),
296
+ };
297
+ });
298
+ const output = [];
299
+ output.push("# Vite Runtime");
300
+ output.push(`URL: ${runtime.url}`);
301
+ output.push(`Framework: ${framework}`);
302
+ output.push(`Vite Client: ${runtime.hasViteClient ? "loaded" : "not detected"}`);
303
+ output.push(`HMR Socket: ${runtime.wsState}`);
304
+ output.push(`Error Overlay: ${runtime.hasErrorOverlay ? "present" : "none"}`);
305
+ output.push(`Tracked HMR Events: ${hmrEvents.length}`);
306
+ const last = hmrEvents[hmrEvents.length - 1];
307
+ if (last) {
308
+ output.push(`Last HMR Event: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.message}`);
309
+ }
310
+ return output.join("\n");
311
+ }
312
+ export async function viteHMRTrace(mode, limit = 20) {
313
+ if (!page)
314
+ throw new Error("browser not open");
315
+ if (mode === "clear") {
316
+ hmrEvents.length = 0;
317
+ return "cleared HMR trace";
318
+ }
319
+ if (hmrEvents.length === 0) {
320
+ const fallback = await page.evaluate(() => {
321
+ const updates = window.__vite_hmr_updates || [];
322
+ return updates.slice(-20).map((u) => ({
323
+ timestamp: u.timestamp ?? Date.now(),
324
+ type: "update",
325
+ message: u.path ? `[vite] hot updated: ${u.path}` : "[vite] hot updated",
326
+ path: u.path,
327
+ }));
328
+ });
329
+ if (fallback.length > 0)
330
+ hmrEvents.push(...fallback);
331
+ }
332
+ if (hmrEvents.length === 0)
333
+ return "No HMR updates";
334
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 200) : 20;
335
+ const recent = hmrEvents.slice(-safeLimit);
336
+ if (mode === "summary") {
337
+ const counts = recent.reduce((acc, event) => {
338
+ acc[event.type] = (acc[event.type] ?? 0) + 1;
339
+ return acc;
340
+ }, {});
341
+ const lines = ["# HMR Summary"];
342
+ lines.push(`Events considered: ${recent.length}`);
343
+ lines.push(`Counts: ${Object.entries(counts)
344
+ .map(([k, v]) => `${k}=${v}`)
345
+ .join(", ")}`);
346
+ const last = recent[recent.length - 1];
347
+ lines.push(`Last: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.path ?? last.message}`);
348
+ lines.push("\nUse `vite-browser vite hmr trace --limit <n>` for timeline details.");
349
+ return lines.join("\n");
350
+ }
351
+ return [
352
+ "# HMR Trace",
353
+ ...recent.map((event) => {
354
+ const detail = event.path ? `${event.path}` : event.message;
355
+ return `[${new Date(event.timestamp).toLocaleTimeString()}] ${event.type} ${detail}`;
356
+ }),
357
+ ].join("\n");
358
+ }
359
+ export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
360
+ if (!page)
361
+ throw new Error("browser not open");
362
+ if (mode === "clear") {
363
+ lastModuleGraphUrls = null;
364
+ return "cleared module-graph baseline";
365
+ }
366
+ const moduleRows = await collectModuleRows(page);
367
+ const currentUrls = moduleRows.map((row) => row.url);
368
+ const previousUrls = lastModuleGraphUrls ? new Set(lastModuleGraphUrls) : null;
369
+ const currentSet = new Set(currentUrls);
370
+ lastModuleGraphUrls = [...currentUrls];
371
+ const normalizedFilter = filter?.trim().toLowerCase();
372
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, 500) : 200;
373
+ if (mode === "trace") {
374
+ if (!previousUrls) {
375
+ return "No module-graph baseline. Captured current snapshot; run `vite module-graph trace` again.";
376
+ }
377
+ const added = currentUrls.filter((url) => !previousUrls.has(url));
378
+ const removed = [...previousUrls].filter((url) => !currentSet.has(url));
379
+ const addedFiltered = normalizedFilter
380
+ ? added.filter((url) => url.toLowerCase().includes(normalizedFilter))
381
+ : added;
382
+ const removedFiltered = normalizedFilter
383
+ ? removed.filter((url) => url.toLowerCase().includes(normalizedFilter))
384
+ : removed;
385
+ const lines = [];
386
+ lines.push("# Vite Module Graph Trace");
387
+ lines.push(`Added: ${addedFiltered.length}, Removed: ${removedFiltered.length}`);
388
+ lines.push("");
389
+ lines.push("## Added");
390
+ if (addedFiltered.length === 0)
391
+ lines.push("(none)");
392
+ else
393
+ addedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`+ ${url}`));
394
+ lines.push("");
395
+ lines.push("## Removed");
396
+ if (removedFiltered.length === 0)
397
+ lines.push("(none)");
398
+ else
399
+ removedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`- ${url}`));
400
+ return lines.join("\n");
401
+ }
402
+ const filtered = moduleRows.filter((row) => normalizedFilter ? row.url.toLowerCase().includes(normalizedFilter) : true);
403
+ const limited = filtered.slice(0, safeLimit);
404
+ if (limited.length === 0)
405
+ return "No module resources found";
406
+ const lines = [];
407
+ lines.push("# Vite Module Graph (loaded resources)");
408
+ lines.push(`Total: ${filtered.length}${filtered.length > limited.length ? ` (showing ${limited.length})` : ""}`);
409
+ lines.push("# idx initiator ms url");
410
+ lines.push("");
411
+ limited.forEach((row, idx) => {
412
+ lines.push(`${idx} ${row.initiator} ${row.durationMs}ms ${row.url}`);
413
+ });
414
+ return lines.join("\n");
415
+ }
416
+ async function collectModuleRows(page) {
226
417
  return page.evaluate(() => {
227
- const updates = window.__vite_hmr_updates || [];
228
- if (updates.length === 0)
229
- return "No HMR updates";
230
- return updates
231
- .slice(-20)
232
- .map((u) => `${new Date(u.timestamp).toLocaleTimeString()} - ${u.path}`)
233
- .join("\n");
418
+ const isLikelyModuleUrl = (url) => {
419
+ if (!url)
420
+ return false;
421
+ if (url.includes("/@vite/"))
422
+ return true;
423
+ if (url.includes("/@id/"))
424
+ return true;
425
+ if (url.includes("/src/"))
426
+ return true;
427
+ if (url.includes("/node_modules/"))
428
+ return true;
429
+ return /\.(mjs|cjs|js|jsx|ts|tsx|vue|css)(\?|$)/.test(url);
430
+ };
431
+ const scripts = Array.from(document.querySelectorAll("script[src]")).map((node) => node.src);
432
+ const resources = performance
433
+ .getEntriesByType("resource")
434
+ .map((entry) => {
435
+ const item = entry;
436
+ return {
437
+ url: item.name,
438
+ initiator: item.initiatorType || "unknown",
439
+ durationMs: Number(item.duration.toFixed(1)),
440
+ };
441
+ })
442
+ .filter((entry) => isLikelyModuleUrl(entry.url));
443
+ const all = [
444
+ ...scripts
445
+ .filter((url) => isLikelyModuleUrl(url))
446
+ .map((url) => ({ url, initiator: "script-tag", durationMs: 0 })),
447
+ ...resources,
448
+ ];
449
+ const seen = new Set();
450
+ const unique = [];
451
+ for (const row of all) {
452
+ if (seen.has(row.url))
453
+ continue;
454
+ seen.add(row.url);
455
+ unique.push(row);
456
+ }
457
+ return unique;
234
458
  });
235
459
  }
236
- export async function errors() {
460
+ export async function errors(mapped = false, inlineSource = false) {
237
461
  if (!page)
238
462
  throw new Error("browser not open");
239
463
  const errorInfo = await page.evaluate(() => {
@@ -246,7 +470,12 @@ export async function errors() {
246
470
  });
247
471
  if (!errorInfo)
248
472
  return "no errors";
249
- return `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
473
+ const raw = `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
474
+ if (!mapped)
475
+ return raw;
476
+ const origin = new URL(page.url()).origin;
477
+ const mappedStack = await mapStackTrace(raw, origin, inlineSource);
478
+ return mappedStack;
250
479
  }
251
480
  export async function logs() {
252
481
  if (consoleLogs.length === 0)
@@ -271,3 +500,27 @@ export async function network(idx) {
271
500
  return networkLog.format();
272
501
  return networkLog.detail(idx);
273
502
  }
503
+ async function mapStackTrace(stack, origin, inlineSource = false) {
504
+ const locationRegex = /(https?:\/\/[^\s)]+):(\d+):(\d+)/g;
505
+ const matches = Array.from(stack.matchAll(locationRegex));
506
+ if (matches.length === 0)
507
+ return stack;
508
+ const mappedLines = [];
509
+ for (const match of matches) {
510
+ const fileUrl = match[1];
511
+ const line = Number.parseInt(match[2], 10);
512
+ const column = Number.parseInt(match[3], 10);
513
+ if (!Number.isFinite(line) || !Number.isFinite(column))
514
+ continue;
515
+ const mapped = await resolveViaSourceMap(origin, fileUrl, line, column, inlineSource);
516
+ if (!mapped)
517
+ continue;
518
+ mappedLines.push(`- ${fileUrl}:${line}:${column} -> ${mapped.file}:${mapped.line}:${mapped.column}`);
519
+ if (inlineSource && mapped.snippet) {
520
+ mappedLines.push(` ${mapped.snippet}`);
521
+ }
522
+ }
523
+ if (mappedLines.length === 0)
524
+ return stack;
525
+ return `${stack}\n\n# Mapped Stack\n${mappedLines.join("\n")}`;
526
+ }
package/dist/cli.js CHANGED
@@ -85,11 +85,45 @@ if (cmd === "vite" && arg === "restart") {
85
85
  exit(res, res.ok && res.data ? String(res.data) : "restarted");
86
86
  }
87
87
  if (cmd === "vite" && arg === "hmr") {
88
- const res = await send("vite-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 });
89
121
  exit(res, res.ok && res.data ? String(res.data) : "");
90
122
  }
91
123
  if (cmd === "errors") {
92
- const res = await send("errors");
124
+ const mapped = args.includes("--mapped");
125
+ const inlineSource = args.includes("--inline-source");
126
+ const res = await send("errors", { mapped, inlineSource });
93
127
  exit(res, res.ok && res.data ? String(res.data) : "no errors");
94
128
  }
95
129
  if (cmd === "logs") {
@@ -154,8 +188,18 @@ SVELTE COMMANDS
154
188
 
155
189
  VITE COMMANDS
156
190
  vite restart Restart Vite dev server
157
- vite hmr Show HMR status
191
+ vite hmr Show HMR summary
192
+ vite hmr trace [--limit <n>] Show HMR timeline
193
+ vite hmr clear Clear tracked HMR timeline
194
+ vite runtime Show Vite runtime status
195
+ vite module-graph [--filter <txt>] [--limit <n>]
196
+ Show loaded Vite module resources
197
+ vite module-graph trace [--filter <txt>] [--limit <n>]
198
+ Show module additions/removals since baseline
199
+ vite module-graph clear Clear module-graph baseline
158
200
  errors Show build/runtime errors
201
+ errors --mapped Show errors with source-map mapping
202
+ errors --mapped --inline-source Include mapped source snippets
159
203
  logs Show dev server logs
160
204
 
161
205
  UTILITIES
package/dist/daemon.js CHANGED
@@ -96,11 +96,21 @@ async function run(cmd) {
96
96
  return { ok: true, data };
97
97
  }
98
98
  if (cmd.action === "vite-hmr") {
99
- const data = await browser.viteHMR();
99
+ const hmrMode = cmd.mode === "trace" || cmd.mode === "clear" ? cmd.mode : "summary";
100
+ const data = await browser.viteHMRTrace(hmrMode, cmd.limit ?? 20);
101
+ return { ok: true, data };
102
+ }
103
+ if (cmd.action === "vite-runtime") {
104
+ const data = await browser.viteRuntimeStatus();
105
+ return { ok: true, data };
106
+ }
107
+ if (cmd.action === "vite-module-graph") {
108
+ const graphMode = cmd.mode === "trace" || cmd.mode === "clear" ? cmd.mode : "snapshot";
109
+ const data = await browser.viteModuleGraph(cmd.filter, cmd.limit ?? 200, graphMode);
100
110
  return { ok: true, data };
101
111
  }
102
112
  if (cmd.action === "errors") {
103
- const data = await browser.errors();
113
+ const data = await browser.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource));
104
114
  return { ok: true, data };
105
115
  }
106
116
  if (cmd.action === "logs") {
@@ -0,0 +1,8 @@
1
+ type MappedLocation = {
2
+ file: string;
3
+ line: number;
4
+ column: number;
5
+ snippet?: string;
6
+ };
7
+ export declare function resolveViaSourceMap(origin: string, fileUrl: string, line: number, column: number, includeSnippet?: boolean): Promise<MappedLocation | null>;
8
+ export {};
@@ -0,0 +1,71 @@
1
+ import { SourceMapConsumer } from "source-map-js";
2
+ const consumers = new Map();
3
+ export async function resolveViaSourceMap(origin, fileUrl, line, column, includeSnippet = false) {
4
+ const consumer = await loadConsumer(origin, fileUrl);
5
+ if (!consumer)
6
+ return null;
7
+ const position = consumer.originalPositionFor({
8
+ line,
9
+ column: Math.max(0, column - 1),
10
+ });
11
+ if (!position.source || position.line == null || position.column == null)
12
+ return null;
13
+ const mapped = {
14
+ file: cleanSource(position.source),
15
+ line: position.line,
16
+ column: position.column + 1,
17
+ };
18
+ if (includeSnippet) {
19
+ mapped.snippet = snippetFor(consumer, position.source, position.line);
20
+ }
21
+ return mapped;
22
+ }
23
+ async function loadConsumer(origin, fileUrl) {
24
+ const candidates = buildMapCandidates(origin, fileUrl);
25
+ for (const url of candidates) {
26
+ if (consumers.has(url)) {
27
+ const cached = consumers.get(url);
28
+ if (cached)
29
+ return cached;
30
+ continue;
31
+ }
32
+ const response = await fetch(url, { signal: AbortSignal.timeout(5000) }).catch(() => null);
33
+ if (!response?.ok) {
34
+ consumers.set(url, null);
35
+ continue;
36
+ }
37
+ const map = await response.json().catch(() => null);
38
+ if (!map) {
39
+ consumers.set(url, null);
40
+ continue;
41
+ }
42
+ const consumer = new SourceMapConsumer(map);
43
+ consumers.set(url, consumer);
44
+ return consumer;
45
+ }
46
+ return null;
47
+ }
48
+ function buildMapCandidates(origin, fileUrl) {
49
+ const url = new URL(fileUrl, origin);
50
+ const basePath = `${url.pathname}.map`;
51
+ const withSearch = url.search ? `${basePath}${url.search}` : basePath;
52
+ // Try preserving query first (Vite often keeps cache-busting query params).
53
+ return [`${origin}${withSearch}`, `${origin}${basePath}`];
54
+ }
55
+ function cleanSource(source) {
56
+ const decoded = decodeURIComponent(source.replace(/^file:\/\//, ""));
57
+ const nodeModulesIndex = decoded.lastIndexOf("/node_modules/");
58
+ if (nodeModulesIndex >= 0)
59
+ return decoded.slice(nodeModulesIndex + 1);
60
+ return decoded;
61
+ }
62
+ function snippetFor(consumer, source, line) {
63
+ const content = consumer.sourceContentFor(source, true);
64
+ if (!content)
65
+ return undefined;
66
+ const lines = content.split(/\r?\n/);
67
+ const sourceLine = lines[line - 1];
68
+ if (!sourceLine)
69
+ return undefined;
70
+ return `${line} | ${sourceLine.trim()}`;
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@presto1314w/vite-devtools-browser",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "CLI for programmatic access to Vue/React DevTools in Vite applications",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,11 @@
28
28
  "build": "tsc -p tsconfig.build.json",
29
29
  "prepack": "pnpm build",
30
30
  "test": "vitest run",
31
- "test:watch": "vitest"
31
+ "test:watch": "vitest",
32
+ "test:coverage": "vitest run --coverage",
33
+ "test:evals": "vitest run --dir test/evals",
34
+ "test:evals:ci": "vitest run --dir test/evals --coverage --reporter=default",
35
+ "test:evals:e2e": "pnpm build && vitest run --config vitest.e2e.config.ts"
32
36
  },
33
37
  "dependencies": {
34
38
  "playwright": "^1.50.0",
@@ -36,6 +40,7 @@
36
40
  },
37
41
  "devDependencies": {
38
42
  "@types/node": "^22.0.0",
43
+ "@vitest/coverage-v8": "^4.0.18",
39
44
  "@vue/devtools-kit": "^7.3.2",
40
45
  "@vue/devtools-api": "^7.3.2",
41
46
  "tsx": "^4.20.6",