@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 +86 -69
- package/dist/browser.d.ts +37 -1
- package/dist/browser.js +291 -15
- package/dist/cli.d.ts +13 -1
- package/dist/cli.js +183 -124
- package/dist/client.d.ts +24 -0
- package/dist/client.js +37 -22
- package/dist/daemon.d.ts +57 -1
- package/dist/daemon.js +147 -118
- package/dist/react/devtools.d.ts +13 -0
- package/dist/react/devtools.js +130 -128
- package/dist/sourcemap.d.ts +14 -0
- package/dist/sourcemap.js +74 -0
- 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 +7 -2
package/README.md
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
# vite-browser
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
22
|
-
cd my-
|
|
27
|
+
# terminal A: start Vite app
|
|
28
|
+
cd my-app
|
|
23
29
|
npm run dev
|
|
24
30
|
|
|
25
|
-
#
|
|
31
|
+
# terminal B: inspect runtime
|
|
26
32
|
vite-browser open http://localhost:5173
|
|
27
|
-
vite-browser detect
|
|
28
|
-
vite-browser vue tree
|
|
29
|
-
vite-browser
|
|
30
|
-
vite-browser
|
|
31
|
-
vite-browser
|
|
32
|
-
vite-browser
|
|
33
|
-
vite-browser
|
|
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
|
-
##
|
|
44
|
+
## Core Capabilities
|
|
38
45
|
|
|
39
|
-
- Framework
|
|
40
|
-
- Vue
|
|
41
|
-
- React
|
|
42
|
-
- Svelte
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
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
|
-
##
|
|
57
|
+
## Command Reference
|
|
52
58
|
|
|
53
|
-
### Browser
|
|
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
|
|
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
|
-
###
|
|
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
|
|
79
|
-
vite-browser
|
|
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
|
-
###
|
|
80
|
+
### Vite Runtime
|
|
81
|
+
|
|
83
82
|
```bash
|
|
84
|
-
vite-browser
|
|
85
|
-
vite-browser
|
|
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
|
-
###
|
|
96
|
+
### Utilities
|
|
97
|
+
|
|
89
98
|
```bash
|
|
90
|
-
vite-browser
|
|
91
|
-
vite-browser
|
|
92
|
-
vite-browser
|
|
93
|
-
vite-browser
|
|
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
|
-
##
|
|
105
|
+
## Skill Packs
|
|
99
106
|
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|