@presto1314w/vite-devtools-browser 0.1.2 → 0.1.4

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 CHANGED
@@ -1,14 +1,20 @@
1
1
  # vite-browser
2
2
 
3
- Agent Skill for AI coding assistants to debug Vite applications with structured access to Vue/React/Svelte runtime state. The CLI is the supporting runtime used by the skill for component trees, store/router inspection, logs, network traces, screenshots, and scripted page evaluation.
3
+ `vite-browser` is a debugging toolchain for Vite apps:
4
+ - Agent Skill: scenario-based debugging workflows for coding assistants
5
+ - CLI Runtime (`@presto1314w/vite-devtools-browser`): structured inspection of Vue/React/Svelte runtime state
4
6
 
5
- ## Skills
7
+ Current documented baseline: `v0.1.4`.
8
+
9
+ ## Install
10
+
11
+ ### Install Skill
6
12
 
7
13
  ```bash
8
14
  npx skills add MapleCity1314/vite-browser
9
15
  ```
10
16
 
11
- ## CLI Installation (Optional)
17
+ ### Install CLI
12
18
 
13
19
  ```bash
14
20
  npm install -g @presto1314w/vite-devtools-browser
@@ -18,100 +24,111 @@ npx playwright install chromium
18
24
  ## Quick Start
19
25
 
20
26
  ```bash
21
- # Start your Vite dev server
22
- cd my-vue-app
27
+ # terminal A: start Vite app
28
+ cd my-app
23
29
  npm run dev
24
30
 
25
- # In another terminal
31
+ # terminal B: inspect runtime
26
32
  vite-browser open http://localhost:5173
27
- vite-browser detect # vue@3.5.29 / react@19.x / svelte@x
28
- vite-browser vue tree # Vue component tree
29
- vite-browser react tree # React component tree
30
- vite-browser svelte tree # Svelte component tree (best effort)
31
- vite-browser screenshot # Take screenshot
32
- vite-browser logs # Console logs
33
- vite-browser network # Network requests
33
+ vite-browser detect
34
+ vite-browser vue tree
35
+ vite-browser vue pinia
36
+ vite-browser vite runtime
37
+ vite-browser vite hmr trace --limit 20
38
+ vite-browser vite module-graph trace --limit 50
39
+ vite-browser errors --mapped --inline-source
40
+ vite-browser network
34
41
  vite-browser close
35
42
  ```
36
43
 
37
- ## Features
44
+ ## Core Capabilities
38
45
 
39
- - Framework Detection: Auto-detect Vue, React, Svelte and versions (best effort)
40
- - Vue DevTools: Component tree, props, state, computed properties, source locations
41
- - React DevTools: Component tree + component inspection (props/hooks/state/context)
42
- - Svelte Support: Component tree + component detail inspection when runtime metadata is available
43
- - Pinia Integration: Inspect store state and getters
44
- - Vue Router: Current route, params, query, all routes
45
- - Network Monitoring: Track requests, headers, bodies, and response payloads
46
- - Console Logs: Capture console.log, warn, error, debug
47
- - Screenshots: Full-page PNG screenshots
48
- - JavaScript Evaluation: Run arbitrary JS in page context
49
- - Vite Integration: Error tracking, HMR monitoring
46
+ - Framework detection: Vue/React/Svelte best-effort detection and version hinting
47
+ - Vue runtime inspection: component tree/details, Pinia stores/getters, Vue Router state
48
+ - React runtime inspection: component tree/details (props/state/hooks/context/source)
49
+ - Svelte runtime inspection: component tree/details when metadata is available
50
+ - Vite runtime diagnostics:
51
+ - runtime status summary
52
+ - HMR summary/timeline/clear
53
+ - module-graph snapshot/diff/clear
54
+ - source-mapped errors with optional inline source snippet
55
+ - Debug utilities: console logs, network tracing, screenshot, page `eval`
50
56
 
51
- ## Commands
57
+ ## Command Reference
52
58
 
53
- ### Browser Control
54
- ```bash
55
- vite-browser open <url> # Launch browser and navigate
56
- vite-browser close # Close browser and daemon
57
- vite-browser goto <url> # Navigate to URL
58
- vite-browser back # Go back
59
- vite-browser reload # Reload page
60
- ```
59
+ ### Browser
61
60
 
62
- ### Framework Detection
63
61
  ```bash
64
- vite-browser detect # Detect framework and version
62
+ vite-browser open <url> [--cookies-json <file>]
63
+ vite-browser close
64
+ vite-browser goto <url>
65
+ vite-browser back
66
+ vite-browser reload
65
67
  ```
66
68
 
67
- ### Vue DevTools
68
- ```bash
69
- vite-browser vue tree # Show component tree
70
- vite-browser vue tree <id> # Inspect component details
71
- vite-browser vue pinia # List all Pinia stores
72
- vite-browser vue pinia <store> # Inspect specific store
73
- vite-browser vue router # Show router information
74
- ```
69
+ ### Framework
75
70
 
76
- ### React DevTools
77
71
  ```bash
78
- vite-browser react tree # Show React component tree
79
- vite-browser react tree <id> # Inspect props/hooks/state/context/source
72
+ vite-browser detect
73
+ vite-browser vue tree [id]
74
+ vite-browser vue pinia [store]
75
+ vite-browser vue router
76
+ vite-browser react tree [id]
77
+ vite-browser svelte tree [id]
80
78
  ```
81
79
 
82
- ### Svelte
80
+ ### Vite Runtime
81
+
83
82
  ```bash
84
- vite-browser svelte tree # Show Svelte component tree
85
- vite-browser svelte tree <id> # Inspect Svelte component details
83
+ vite-browser vite restart
84
+ vite-browser vite runtime
85
+ vite-browser vite hmr
86
+ vite-browser vite hmr trace [--limit <n>]
87
+ vite-browser vite hmr clear
88
+ vite-browser vite module-graph [--filter <txt>] [--limit <n>]
89
+ vite-browser vite module-graph trace [--filter <txt>] [--limit <n>]
90
+ vite-browser vite module-graph clear
91
+ vite-browser errors
92
+ vite-browser errors --mapped
93
+ vite-browser errors --mapped --inline-source
86
94
  ```
87
95
 
88
- ### Debugging
96
+ ### Utilities
97
+
89
98
  ```bash
90
- vite-browser screenshot # Take full-page screenshot
91
- vite-browser eval <script> # Run JavaScript in page
92
- vite-browser logs # Show console logs
93
- vite-browser errors # Show Vite errors
94
- vite-browser network # List network requests
95
- vite-browser network <idx> # Inspect specific request
99
+ vite-browser logs
100
+ vite-browser network [idx]
101
+ vite-browser screenshot
102
+ vite-browser eval <script>
96
103
  ```
97
104
 
98
- ## Architecture
105
+ ## Skill Packs
99
106
 
100
- - Daemon + Socket: Background process with socket communication
101
- - Playwright: Headed Chromium browser
102
- - One Browser, One Page: Single persistent browser instance
103
- - Auto-start: Daemon starts automatically on first command
107
+ The entry skill routes to specialized workflows:
104
108
 
105
- ## Requirements
109
+ - `skills/vite-browser-core-debug/SKILL.md`
110
+ - `skills/vite-browser-runtime-diagnostics/SKILL.md`
111
+ - `skills/vite-browser-network-regression/SKILL.md`
112
+ - `skills/vite-browser-release-smoke/SKILL.md`
106
113
 
107
- - Node.js 20+
108
- - Chromium (via Playwright)
109
- - Vite dev server running
110
- - Vue/React/Svelte app for framework-specific commands
114
+ Router definition: [skills/SKILL.md](./skills/SKILL.md)
111
115
 
112
- ## Documentation
116
+ ## Local Development
117
+
118
+ ```bash
119
+ pnpm install
120
+ pnpm build
121
+ pnpm test
122
+ pnpm test:coverage
123
+ pnpm test:evals
124
+ pnpm test:evals:e2e
125
+ ```
126
+
127
+ ## Requirements
113
128
 
114
- See [SKILL.md](./skills/SKILL.md) for complete command reference and usage examples.
129
+ - Node.js `>=20`
130
+ - Chromium installed via Playwright
131
+ - Running Vite dev server
115
132
 
116
133
  ## License
117
134
 
package/dist/browser.d.ts CHANGED
@@ -1,9 +1,40 @@
1
+ import { type BrowserContext } from "playwright";
2
+ import { resolveViaSourceMap } from "./sourcemap.js";
3
+ type HmrEventType = "connecting" | "connected" | "update" | "full-reload" | "error" | "log";
4
+ export type HmrEvent = {
5
+ timestamp: number;
6
+ type: HmrEventType;
7
+ message: string;
8
+ path?: string;
9
+ };
10
+ export type RuntimeSnapshot = {
11
+ url: string;
12
+ hasViteClient: boolean;
13
+ wsState: string;
14
+ hasErrorOverlay: boolean;
15
+ timestamp: number;
16
+ };
17
+ export type ModuleRow = {
18
+ url: string;
19
+ initiator: string;
20
+ durationMs: number;
21
+ };
22
+ export type ModuleGraphMode = "snapshot" | "trace" | "clear";
1
23
  export declare function open(url: string | undefined): Promise<void>;
2
24
  export declare function cookies(cookies: {
3
25
  name: string;
4
26
  value: string;
5
27
  }[], domain: string): Promise<number>;
6
28
  export declare function close(): Promise<void>;
29
+ export declare function contextUsable(current: Pick<BrowserContext, "pages"> | null): current is Pick<BrowserContext, "pages">;
30
+ export declare function isClosedTargetError(error: unknown): boolean;
31
+ export declare function recordConsoleMessage(logs: string[], events: HmrEvent[], type: string, message: string, maxLogs?: number, maxEvents?: number): void;
32
+ export declare function parseViteLog(message: string): HmrEvent;
33
+ export declare function normalizeLimit(limit: number, fallback: number, max: number): number;
34
+ export declare function formatRuntimeStatus(runtime: RuntimeSnapshot, currentFramework: string, events: HmrEvent[]): string;
35
+ export declare function formatHmrTrace(mode: "summary" | "trace", events: HmrEvent[], limit: number): string;
36
+ export declare function formatModuleGraphSnapshot(rows: ModuleRow[], filter?: string, limit?: number): string;
37
+ export declare function formatModuleGraphTrace(currentUrls: string[], previousUrls: Set<string> | null, filter?: string, limit?: number): string;
7
38
  export declare function goto(url: string): Promise<string>;
8
39
  export declare function back(): Promise<void>;
9
40
  export declare function reload(): Promise<string>;
@@ -15,8 +46,13 @@ export declare function reactTree(id?: string): Promise<string>;
15
46
  export declare function svelteTree(id?: string): Promise<string>;
16
47
  export declare function viteRestart(): Promise<string>;
17
48
  export declare function viteHMR(): Promise<string>;
18
- export declare function errors(): Promise<string>;
49
+ export declare function viteRuntimeStatus(): Promise<string>;
50
+ export declare function viteHMRTrace(mode: "summary" | "trace" | "clear", limit?: number): Promise<string>;
51
+ export declare function viteModuleGraph(filter?: string, limit?: number, mode?: ModuleGraphMode): Promise<string>;
52
+ export declare function errors(mapped?: boolean, inlineSource?: boolean): Promise<string>;
19
53
  export declare function logs(): Promise<string>;
20
54
  export declare function screenshot(): Promise<string>;
21
55
  export declare function evaluate(script: string): Promise<string>;
22
56
  export declare function network(idx?: number): Promise<string>;
57
+ export declare function mapStackTrace(stack: string, origin: string, inlineSource?: boolean, resolver?: typeof resolveViaSourceMap): Promise<string>;
58
+ export {};
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
  }
@@ -65,7 +71,7 @@ async function ensurePage() {
65
71
  }
66
72
  return page;
67
73
  }
68
- function contextUsable(current) {
74
+ export function contextUsable(current) {
69
75
  if (!current)
70
76
  return false;
71
77
  try {
@@ -76,7 +82,7 @@ function contextUsable(current) {
76
82
  return false;
77
83
  }
78
84
  }
79
- function isClosedTargetError(error) {
85
+ export function isClosedTargetError(error) {
80
86
  if (!(error instanceof Error))
81
87
  return false;
82
88
  return /Target page, context or browser has been closed/i.test(error.message);
@@ -109,12 +115,144 @@ async function launch() {
109
115
  }
110
116
  function attachListeners(currentPage) {
111
117
  currentPage.on("console", (msg) => {
112
- const text = `[${msg.type()}] ${msg.text()}`;
113
- consoleLogs.push(text);
114
- if (consoleLogs.length > MAX_LOGS)
115
- consoleLogs.shift();
118
+ recordConsoleMessage(consoleLogs, hmrEvents, msg.type(), msg.text());
116
119
  });
117
120
  }
121
+ export function recordConsoleMessage(logs, events, type, message, maxLogs = MAX_LOGS, maxEvents = MAX_HMR_EVENTS) {
122
+ const text = `[${type}] ${message}`;
123
+ logs.push(text);
124
+ if (logs.length > maxLogs)
125
+ logs.shift();
126
+ if (!message.includes("[vite]"))
127
+ return;
128
+ const event = parseViteLog(message);
129
+ events.push(event);
130
+ if (events.length > maxEvents)
131
+ events.shift();
132
+ }
133
+ export function parseViteLog(message) {
134
+ const lower = message.toLowerCase();
135
+ const event = {
136
+ timestamp: Date.now(),
137
+ type: "log",
138
+ message,
139
+ };
140
+ if (lower.includes("connecting"))
141
+ event.type = "connecting";
142
+ else if (lower.includes("connected"))
143
+ event.type = "connected";
144
+ else if (lower.includes("hot updated"))
145
+ event.type = "update";
146
+ else if (lower.includes("page reload"))
147
+ event.type = "full-reload";
148
+ else if (lower.includes("disconnected") ||
149
+ lower.includes("failed to connect") ||
150
+ lower.includes("connection lost") ||
151
+ lower.includes("error")) {
152
+ event.type = "error";
153
+ }
154
+ const hotUpdateMatch = message.match(/hot updated:\s*(.+)$/i);
155
+ if (hotUpdateMatch?.[1])
156
+ event.path = hotUpdateMatch[1].trim();
157
+ return event;
158
+ }
159
+ export function normalizeLimit(limit, fallback, max) {
160
+ if (!Number.isFinite(limit) || limit <= 0)
161
+ return fallback;
162
+ return Math.min(limit, max);
163
+ }
164
+ export function formatRuntimeStatus(runtime, currentFramework, events) {
165
+ const output = [];
166
+ output.push("# Vite Runtime");
167
+ output.push(`URL: ${runtime.url}`);
168
+ output.push(`Framework: ${currentFramework}`);
169
+ output.push(`Vite Client: ${runtime.hasViteClient ? "loaded" : "not detected"}`);
170
+ output.push(`HMR Socket: ${runtime.wsState}`);
171
+ output.push(`Error Overlay: ${runtime.hasErrorOverlay ? "present" : "none"}`);
172
+ output.push(`Tracked HMR Events: ${events.length}`);
173
+ const last = events[events.length - 1];
174
+ if (last) {
175
+ output.push(`Last HMR Event: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.message}`);
176
+ }
177
+ return output.join("\n");
178
+ }
179
+ export function formatHmrTrace(mode, events, limit) {
180
+ if (events.length === 0)
181
+ return "No HMR updates";
182
+ const safeLimit = normalizeLimit(limit, 20, 200);
183
+ const recent = events.slice(-safeLimit);
184
+ if (mode === "summary") {
185
+ const counts = recent.reduce((acc, event) => {
186
+ acc[event.type] = (acc[event.type] ?? 0) + 1;
187
+ return acc;
188
+ }, {});
189
+ const lines = ["# HMR Summary"];
190
+ lines.push(`Events considered: ${recent.length}`);
191
+ lines.push(`Counts: ${Object.entries(counts)
192
+ .map(([k, v]) => `${k}=${v}`)
193
+ .join(", ")}`);
194
+ const last = recent[recent.length - 1];
195
+ lines.push(`Last: ${new Date(last.timestamp).toLocaleTimeString()} [${last.type}] ${last.path ?? last.message}`);
196
+ lines.push("\nUse `vite-browser vite hmr trace --limit <n>` for timeline details.");
197
+ return lines.join("\n");
198
+ }
199
+ return [
200
+ "# HMR Trace",
201
+ ...recent.map((event) => {
202
+ const detail = event.path ? `${event.path}` : event.message;
203
+ return `[${new Date(event.timestamp).toLocaleTimeString()}] ${event.type} ${detail}`;
204
+ }),
205
+ ].join("\n");
206
+ }
207
+ export function formatModuleGraphSnapshot(rows, filter, limit = 200) {
208
+ const normalizedFilter = filter?.trim().toLowerCase();
209
+ const safeLimit = normalizeLimit(limit, 200, 500);
210
+ const filtered = rows.filter((row) => normalizedFilter ? row.url.toLowerCase().includes(normalizedFilter) : true);
211
+ const limited = filtered.slice(0, safeLimit);
212
+ if (limited.length === 0)
213
+ return "No module resources found";
214
+ const lines = [];
215
+ lines.push("# Vite Module Graph (loaded resources)");
216
+ lines.push(`Total: ${filtered.length}${filtered.length > limited.length ? ` (showing ${limited.length})` : ""}`);
217
+ lines.push("# idx initiator ms url");
218
+ lines.push("");
219
+ limited.forEach((row, idx) => {
220
+ lines.push(`${idx} ${row.initiator} ${row.durationMs}ms ${row.url}`);
221
+ });
222
+ return lines.join("\n");
223
+ }
224
+ export function formatModuleGraphTrace(currentUrls, previousUrls, filter, limit = 200) {
225
+ if (!previousUrls) {
226
+ return "No module-graph baseline. Captured current snapshot; run `vite module-graph trace` again.";
227
+ }
228
+ const currentSet = new Set(currentUrls);
229
+ const added = currentUrls.filter((url) => !previousUrls.has(url));
230
+ const removed = [...previousUrls].filter((url) => !currentSet.has(url));
231
+ const normalizedFilter = filter?.trim().toLowerCase();
232
+ const safeLimit = normalizeLimit(limit, 200, 500);
233
+ const addedFiltered = normalizedFilter
234
+ ? added.filter((url) => url.toLowerCase().includes(normalizedFilter))
235
+ : added;
236
+ const removedFiltered = normalizedFilter
237
+ ? removed.filter((url) => url.toLowerCase().includes(normalizedFilter))
238
+ : removed;
239
+ const lines = [];
240
+ lines.push("# Vite Module Graph Trace");
241
+ lines.push(`Added: ${addedFiltered.length}, Removed: ${removedFiltered.length}`);
242
+ lines.push("");
243
+ lines.push("## Added");
244
+ if (addedFiltered.length === 0)
245
+ lines.push("(none)");
246
+ else
247
+ addedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`+ ${url}`));
248
+ lines.push("");
249
+ lines.push("## Removed");
250
+ if (removedFiltered.length === 0)
251
+ lines.push("(none)");
252
+ else
253
+ removedFiltered.slice(0, safeLimit).forEach((url) => lines.push(`- ${url}`));
254
+ return lines.join("\n");
255
+ }
118
256
  export async function goto(url) {
119
257
  const currentPage = await ensurePage();
120
258
  await currentPage.goto(url, { waitUntil: "domcontentloaded" });
@@ -223,17 +361,126 @@ export async function viteRestart() {
223
361
  export async function viteHMR() {
224
362
  if (!page)
225
363
  throw new Error("browser not open");
364
+ return viteHMRTrace("summary", 20);
365
+ }
366
+ export async function viteRuntimeStatus() {
367
+ if (!page)
368
+ throw new Error("browser not open");
369
+ const runtime = await page.evaluate(() => {
370
+ const findViteClient = () => {
371
+ const scripts = Array.from(document.querySelectorAll("script[src]"));
372
+ return scripts.some((script) => script.getAttribute("src")?.includes("/@vite/client"));
373
+ };
374
+ const wsStateName = (wsState) => {
375
+ if (wsState == null)
376
+ return "unknown";
377
+ if (wsState === 0)
378
+ return "connecting";
379
+ if (wsState === 1)
380
+ return "open";
381
+ if (wsState === 2)
382
+ return "closing";
383
+ if (wsState === 3)
384
+ return "closed";
385
+ return "unknown";
386
+ };
387
+ const hot = window.__vite_hot;
388
+ const ws = hot?.ws || hot?.socket;
389
+ return {
390
+ url: location.href,
391
+ hasViteClient: findViteClient(),
392
+ wsState: wsStateName(ws?.readyState),
393
+ hasErrorOverlay: Boolean(document.querySelector("vite-error-overlay")),
394
+ timestamp: Date.now(),
395
+ };
396
+ });
397
+ return formatRuntimeStatus(runtime, framework, hmrEvents);
398
+ }
399
+ export async function viteHMRTrace(mode, limit = 20) {
400
+ if (!page)
401
+ throw new Error("browser not open");
402
+ if (mode === "clear") {
403
+ hmrEvents.length = 0;
404
+ return "cleared HMR trace";
405
+ }
406
+ if (hmrEvents.length === 0) {
407
+ const fallback = await page.evaluate(() => {
408
+ const updates = window.__vite_hmr_updates || [];
409
+ return updates.slice(-20).map((u) => ({
410
+ timestamp: u.timestamp ?? Date.now(),
411
+ type: "update",
412
+ message: u.path ? `[vite] hot updated: ${u.path}` : "[vite] hot updated",
413
+ path: u.path,
414
+ }));
415
+ });
416
+ if (fallback.length > 0)
417
+ hmrEvents.push(...fallback);
418
+ }
419
+ if (hmrEvents.length === 0)
420
+ return "No HMR updates";
421
+ return formatHmrTrace(mode, hmrEvents, limit);
422
+ }
423
+ export async function viteModuleGraph(filter, limit = 200, mode = "snapshot") {
424
+ if (!page)
425
+ throw new Error("browser not open");
426
+ if (mode === "clear") {
427
+ lastModuleGraphUrls = null;
428
+ return "cleared module-graph baseline";
429
+ }
430
+ const moduleRows = await collectModuleRows(page);
431
+ const currentUrls = moduleRows.map((row) => row.url);
432
+ const previousUrls = lastModuleGraphUrls ? new Set(lastModuleGraphUrls) : null;
433
+ lastModuleGraphUrls = [...currentUrls];
434
+ if (mode === "trace") {
435
+ return formatModuleGraphTrace(currentUrls, previousUrls, filter, limit);
436
+ }
437
+ return formatModuleGraphSnapshot(moduleRows, filter, limit);
438
+ }
439
+ async function collectModuleRows(page) {
226
440
  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");
441
+ const isLikelyModuleUrl = (url) => {
442
+ if (!url)
443
+ return false;
444
+ if (url.includes("/@vite/"))
445
+ return true;
446
+ if (url.includes("/@id/"))
447
+ return true;
448
+ if (url.includes("/src/"))
449
+ return true;
450
+ if (url.includes("/node_modules/"))
451
+ return true;
452
+ return /\.(mjs|cjs|js|jsx|ts|tsx|vue|css)(\?|$)/.test(url);
453
+ };
454
+ const scripts = Array.from(document.querySelectorAll("script[src]")).map((node) => node.src);
455
+ const resources = performance
456
+ .getEntriesByType("resource")
457
+ .map((entry) => {
458
+ const item = entry;
459
+ return {
460
+ url: item.name,
461
+ initiator: item.initiatorType || "unknown",
462
+ durationMs: Number(item.duration.toFixed(1)),
463
+ };
464
+ })
465
+ .filter((entry) => isLikelyModuleUrl(entry.url));
466
+ const all = [
467
+ ...scripts
468
+ .filter((url) => isLikelyModuleUrl(url))
469
+ .map((url) => ({ url, initiator: "script-tag", durationMs: 0 })),
470
+ ...resources,
471
+ ];
472
+ const seen = new Set();
473
+ const unique = [];
474
+ for (const row of all) {
475
+ if (seen.has(row.url))
476
+ continue;
477
+ seen.add(row.url);
478
+ unique.push(row);
479
+ }
480
+ return unique;
234
481
  });
235
482
  }
236
- export async function errors() {
483
+ export async function errors(mapped = false, inlineSource = false) {
237
484
  if (!page)
238
485
  throw new Error("browser not open");
239
486
  const errorInfo = await page.evaluate(() => {
@@ -246,7 +493,12 @@ export async function errors() {
246
493
  });
247
494
  if (!errorInfo)
248
495
  return "no errors";
249
- return `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
496
+ const raw = `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
497
+ if (!mapped)
498
+ return raw;
499
+ const origin = new URL(page.url()).origin;
500
+ const mappedStack = await mapStackTrace(raw, origin, inlineSource);
501
+ return mappedStack;
250
502
  }
251
503
  export async function logs() {
252
504
  if (consoleLogs.length === 0)
@@ -271,3 +523,27 @@ export async function network(idx) {
271
523
  return networkLog.format();
272
524
  return networkLog.detail(idx);
273
525
  }
526
+ export async function mapStackTrace(stack, origin, inlineSource = false, resolver = resolveViaSourceMap) {
527
+ const locationRegex = /(https?:\/\/[^\s)]+):(\d+):(\d+)/g;
528
+ const matches = Array.from(stack.matchAll(locationRegex));
529
+ if (matches.length === 0)
530
+ return stack;
531
+ const mappedLines = [];
532
+ for (const match of matches) {
533
+ const fileUrl = match[1];
534
+ const line = Number.parseInt(match[2], 10);
535
+ const column = Number.parseInt(match[3], 10);
536
+ if (!Number.isFinite(line) || !Number.isFinite(column))
537
+ continue;
538
+ const mapped = await resolver(origin, fileUrl, line, column, inlineSource);
539
+ if (!mapped)
540
+ continue;
541
+ mappedLines.push(`- ${fileUrl}:${line}:${column} -> ${mapped.file}:${mapped.line}:${mapped.column}`);
542
+ if (inlineSource && mapped.snippet) {
543
+ mappedLines.push(` ${mapped.snippet}`);
544
+ }
545
+ }
546
+ if (mappedLines.length === 0)
547
+ return stack;
548
+ return `${stack}\n\n# Mapped Stack\n${mappedLines.join("\n")}`;
549
+ }
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,14 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import { send, type Response } from "./client.js";
3
+ export type CliIo = {
4
+ send: typeof send;
5
+ readFile: (path: string, encoding: BufferEncoding) => string;
6
+ stdout: (text: string) => void;
7
+ stderr: (text: string) => void;
8
+ exit: (code: number) => never;
9
+ };
10
+ export declare function normalizeUrl(value: string): string;
11
+ export declare function parseNumberFlag(args: string[], name: string, fallback: number): number;
12
+ export declare function runCli(argv: string[], io: CliIo): Promise<void>;
13
+ export declare function exit(io: Pick<CliIo, "stdout" | "stderr" | "exit">, res: Response, msg: string): never;
14
+ export declare function printUsage(): string;