@presto1314w/vite-devtools-browser 0.3.3 → 0.3.6
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/LICENSE +21 -21
- package/README.md +135 -322
- package/dist/browser-collector.js +7 -6
- package/dist/browser-frameworks.d.ts +10 -0
- package/dist/browser-frameworks.js +63 -18
- package/dist/browser-session.d.ts +13 -1
- package/dist/browser-session.js +58 -41
- package/dist/browser.d.ts +6 -0
- package/dist/browser.js +39 -0
- package/dist/cli.js +104 -57
- package/dist/client.js +2 -2
- package/dist/daemon.js +27 -8
- package/dist/event-queue.d.ts +10 -1
- package/dist/event-queue.js +52 -2
- package/dist/paths.d.ts +25 -0
- package/dist/paths.js +46 -4
- package/dist/react/devtools.d.ts +10 -0
- package/dist/react/devtools.js +18 -2
- package/dist/react/hook-manager.d.ts +29 -0
- package/dist/react/hook-manager.js +75 -0
- package/dist/react/hook.d.ts +1 -0
- package/dist/react/hook.js +255 -0
- package/dist/react/profiler.d.ts +38 -0
- package/dist/react/profiler.js +195 -0
- package/dist/react/zustand.d.ts +31 -0
- package/dist/react/zustand.js +113 -0
- package/dist/vue/devtools.d.ts +1 -1
- package/dist/vue/devtools.js +41 -3
- package/package.json +21 -15
|
@@ -20,7 +20,6 @@ export type BrowserSessionState = {
|
|
|
20
20
|
context: BrowserContext | null;
|
|
21
21
|
page: Page | null;
|
|
22
22
|
framework: BrowserFramework;
|
|
23
|
-
extensionModeDisabled: boolean;
|
|
24
23
|
collectorInstalled: boolean;
|
|
25
24
|
consoleLogs: string[];
|
|
26
25
|
hmrEvents: HmrEvent[];
|
|
@@ -35,3 +34,16 @@ export declare function ensureBrowserPage(state: BrowserSessionState, onPageRead
|
|
|
35
34
|
export declare function closeBrowserSession(state: BrowserSessionState): Promise<void>;
|
|
36
35
|
export declare function contextUsable(current: Pick<BrowserContext, "pages"> | null): current is Pick<BrowserContext, "pages">;
|
|
37
36
|
export declare function isClosedTargetError(error: unknown): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Build platform-aware Chromium launch arguments.
|
|
39
|
+
*
|
|
40
|
+
* On Linux (especially Docker / CI without a display server) we add
|
|
41
|
+
* `--no-sandbox` and `--disable-dev-shm-usage` so that Chromium can
|
|
42
|
+
* start reliably in headless-shell containers.
|
|
43
|
+
*
|
|
44
|
+
* On Windows, `--disable-gpu` is added to work around occasional GPU
|
|
45
|
+
* process crashes on older drivers.
|
|
46
|
+
*/
|
|
47
|
+
export declare function platformChromiumArgs(extra?: string[]): string[];
|
|
48
|
+
export declare function resolveChromiumExecutablePath(env?: NodeJS.ProcessEnv): string | undefined;
|
|
49
|
+
export declare function resolveBrowserHeadless(env?: NodeJS.ProcessEnv): boolean;
|
package/dist/browser-session.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
import { existsSync
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
3
2
|
import { chromium } from "playwright";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const hasReactExtension = existsSync(join(extensionPath, "manifest.json"));
|
|
7
|
-
const installHook = hasReactExtension
|
|
8
|
-
? readFileSync(join(extensionPath, "build", "installHook.js"), "utf-8")
|
|
9
|
-
: null;
|
|
3
|
+
import { getHookSource } from "./react/hook-manager.js";
|
|
4
|
+
import { isWindows, isLinux } from "./paths.js";
|
|
10
5
|
export function createBrowserSessionState() {
|
|
11
6
|
return {
|
|
12
7
|
context: null,
|
|
13
8
|
page: null,
|
|
14
9
|
framework: "unknown",
|
|
15
|
-
extensionModeDisabled: false,
|
|
16
10
|
collectorInstalled: false,
|
|
17
11
|
consoleLogs: [],
|
|
18
12
|
hmrEvents: [],
|
|
@@ -42,9 +36,7 @@ export function resetBrowserSessionState(state) {
|
|
|
42
36
|
export async function ensureBrowserPage(state, onPageReady) {
|
|
43
37
|
if (!contextUsable(state.context)) {
|
|
44
38
|
await closeBrowserSession(state);
|
|
45
|
-
|
|
46
|
-
state.context = launched.context;
|
|
47
|
-
state.extensionModeDisabled = launched.extensionModeDisabled;
|
|
39
|
+
state.context = await launchBrowserContext();
|
|
48
40
|
}
|
|
49
41
|
if (!state.context)
|
|
50
42
|
throw new Error("browser not open");
|
|
@@ -56,10 +48,7 @@ export async function ensureBrowserPage(state, onPageReady) {
|
|
|
56
48
|
if (!isClosedTargetError(error))
|
|
57
49
|
throw error;
|
|
58
50
|
await closeBrowserSession(state);
|
|
59
|
-
state.
|
|
60
|
-
const launched = await launchBrowserContext(state.extensionModeDisabled);
|
|
61
|
-
state.context = launched.context;
|
|
62
|
-
state.extensionModeDisabled = launched.extensionModeDisabled;
|
|
51
|
+
state.context = await launchBrowserContext();
|
|
63
52
|
state.page = state.context.pages()[0] ?? (await state.context.newPage());
|
|
64
53
|
}
|
|
65
54
|
onPageReady(state.page);
|
|
@@ -86,32 +75,60 @@ export function isClosedTargetError(error) {
|
|
|
86
75
|
return false;
|
|
87
76
|
return /Target page, context or browser has been closed/i.test(error.message);
|
|
88
77
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return { context, extensionModeDisabled };
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
extensionModeDisabled = true;
|
|
107
|
-
}
|
|
78
|
+
/**
|
|
79
|
+
* Build platform-aware Chromium launch arguments.
|
|
80
|
+
*
|
|
81
|
+
* On Linux (especially Docker / CI without a display server) we add
|
|
82
|
+
* `--no-sandbox` and `--disable-dev-shm-usage` so that Chromium can
|
|
83
|
+
* start reliably in headless-shell containers.
|
|
84
|
+
*
|
|
85
|
+
* On Windows, `--disable-gpu` is added to work around occasional GPU
|
|
86
|
+
* process crashes on older drivers.
|
|
87
|
+
*/
|
|
88
|
+
export function platformChromiumArgs(extra = []) {
|
|
89
|
+
const args = ["--auto-open-devtools-for-tabs", ...extra];
|
|
90
|
+
if (isLinux) {
|
|
91
|
+
args.push("--no-sandbox", "--disable-dev-shm-usage");
|
|
108
92
|
}
|
|
93
|
+
else if (isWindows) {
|
|
94
|
+
args.push("--disable-gpu");
|
|
95
|
+
}
|
|
96
|
+
return args;
|
|
97
|
+
}
|
|
98
|
+
export function resolveChromiumExecutablePath(env = process.env) {
|
|
99
|
+
const explicit = env.VITE_BROWSER_EXECUTABLE_PATH || env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
|
|
100
|
+
if (explicit && existsSync(explicit))
|
|
101
|
+
return explicit;
|
|
102
|
+
const candidates = isWindows
|
|
103
|
+
? [
|
|
104
|
+
`${env.PROGRAMFILES ?? "C:\\Program Files"}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
105
|
+
`${env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)"}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
106
|
+
`${env.LOCALAPPDATA ?? ""}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
107
|
+
]
|
|
108
|
+
: isLinux
|
|
109
|
+
? [
|
|
110
|
+
"/usr/bin/google-chrome",
|
|
111
|
+
"/usr/bin/chromium",
|
|
112
|
+
"/usr/bin/chromium-browser",
|
|
113
|
+
"/snap/bin/chromium",
|
|
114
|
+
]
|
|
115
|
+
: [
|
|
116
|
+
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
117
|
+
"/Applications/Chromium.app/Contents/MacOS/Chromium",
|
|
118
|
+
];
|
|
119
|
+
return candidates.find((candidate) => Boolean(candidate) && existsSync(candidate));
|
|
120
|
+
}
|
|
121
|
+
export function resolveBrowserHeadless(env = process.env) {
|
|
122
|
+
return /^(1|true|yes)$/i.test(env.VITE_BROWSER_HEADLESS || "");
|
|
123
|
+
}
|
|
124
|
+
async function launchBrowserContext() {
|
|
109
125
|
const browser = await chromium.launch({
|
|
110
|
-
headless:
|
|
111
|
-
|
|
126
|
+
headless: resolveBrowserHeadless(),
|
|
127
|
+
executablePath: resolveChromiumExecutablePath(),
|
|
128
|
+
args: platformChromiumArgs(),
|
|
112
129
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
130
|
+
const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
131
|
+
// Inject bundled React DevTools hook before any page loads
|
|
132
|
+
await context.addInitScript(getHookSource());
|
|
133
|
+
return context;
|
|
117
134
|
}
|
package/dist/browser.d.ts
CHANGED
|
@@ -40,6 +40,12 @@ export declare function vueTree(id?: string): Promise<string>;
|
|
|
40
40
|
export declare function vuePinia(store?: string): Promise<string>;
|
|
41
41
|
export declare function vueRouter(): Promise<string>;
|
|
42
42
|
export declare function reactTree(id?: string): Promise<string>;
|
|
43
|
+
export declare function reactStoreList(): Promise<string>;
|
|
44
|
+
export declare function reactStoreInspect(store: string): Promise<string>;
|
|
45
|
+
export declare function reactHookHealth(): Promise<string>;
|
|
46
|
+
export declare function reactHookInject(): Promise<string>;
|
|
47
|
+
export declare function reactCommits(limit?: number): Promise<string>;
|
|
48
|
+
export declare function reactCommitsClear(): Promise<string>;
|
|
43
49
|
export declare function svelteTree(id?: string): Promise<string>;
|
|
44
50
|
export declare function viteRestart(): Promise<string>;
|
|
45
51
|
export declare function viteHMR(): Promise<string>;
|
package/dist/browser.js
CHANGED
|
@@ -5,6 +5,9 @@ import { detectBrowserFramework, inspectReactTree, inspectSvelteTree, inspectVue
|
|
|
5
5
|
import { closeBrowserSession, createBrowserSessionState, ensureBrowserPage, getCurrentPage as getSessionPage, } from "./browser-session.js";
|
|
6
6
|
import { collectModuleRows, formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, formatRuntimeStatus, readOverlayError, readRuntimeSnapshot, } from "./browser-vite.js";
|
|
7
7
|
import * as networkLog from "./network.js";
|
|
8
|
+
import { checkHookHealth, formatHookHealth, injectHook } from "./react/hook-manager.js";
|
|
9
|
+
import { clearRenderHistory, formatRenderInfo, getRecentRenders, installRenderTracking } from "./react/profiler.js";
|
|
10
|
+
import { formatStoreInspection, formatStoreList, inspectStore, listStores } from "./react/zustand.js";
|
|
8
11
|
import { resolveViaSourceMap } from "./sourcemap.js";
|
|
9
12
|
export { contextUsable, isClosedTargetError } from "./browser-session.js";
|
|
10
13
|
export { formatHmrTrace, formatModuleGraphSnapshot, formatModuleGraphTrace, formatRuntimeStatus, normalizeLimit, } from "./browser-vite.js";
|
|
@@ -189,6 +192,36 @@ export async function vueRouter() {
|
|
|
189
192
|
export async function reactTree(id) {
|
|
190
193
|
return inspectReactTree(session, requireCurrentPage(), id);
|
|
191
194
|
}
|
|
195
|
+
export async function reactStoreList() {
|
|
196
|
+
return formatStoreList(await listStores(requireCurrentPage()));
|
|
197
|
+
}
|
|
198
|
+
export async function reactStoreInspect(store) {
|
|
199
|
+
const result = await inspectStore(requireCurrentPage(), store);
|
|
200
|
+
if (!result) {
|
|
201
|
+
throw new Error(`zustand store not found: ${store}`);
|
|
202
|
+
}
|
|
203
|
+
return formatStoreInspection(result);
|
|
204
|
+
}
|
|
205
|
+
export async function reactHookHealth() {
|
|
206
|
+
return formatHookHealth(await checkHookHealth(requireCurrentPage()));
|
|
207
|
+
}
|
|
208
|
+
export async function reactHookInject() {
|
|
209
|
+
const currentPage = requireCurrentPage();
|
|
210
|
+
const injected = await injectHook(currentPage);
|
|
211
|
+
const status = await checkHookHealth(currentPage);
|
|
212
|
+
const prefix = injected ? "React hook injected.\n\n" : "React hook already installed.\n\n";
|
|
213
|
+
return `${prefix}${formatHookHealth(status)}`;
|
|
214
|
+
}
|
|
215
|
+
export async function reactCommits(limit = 20) {
|
|
216
|
+
const currentPage = requireCurrentPage();
|
|
217
|
+
await installRenderTracking(currentPage);
|
|
218
|
+
return formatRenderInfo(await getRecentRenders(currentPage, limit));
|
|
219
|
+
}
|
|
220
|
+
export async function reactCommitsClear() {
|
|
221
|
+
const currentPage = requireCurrentPage();
|
|
222
|
+
await clearRenderHistory(currentPage);
|
|
223
|
+
return "cleared React commit history";
|
|
224
|
+
}
|
|
192
225
|
export async function svelteTree(id) {
|
|
193
226
|
return inspectSvelteTree(requireCurrentPage(), id);
|
|
194
227
|
}
|
|
@@ -210,6 +243,10 @@ export async function viteHMR() {
|
|
|
210
243
|
}
|
|
211
244
|
export async function viteRuntimeStatus() {
|
|
212
245
|
const currentPage = requireCurrentPage();
|
|
246
|
+
if (session.framework === "unknown") {
|
|
247
|
+
const result = await detectBrowserFramework(currentPage);
|
|
248
|
+
session.framework = result.framework;
|
|
249
|
+
}
|
|
213
250
|
const runtime = await readRuntimeSnapshot(currentPage);
|
|
214
251
|
return formatRuntimeStatus(runtime, session.framework, session.hmrEvents);
|
|
215
252
|
}
|
|
@@ -334,6 +371,8 @@ async function navigateAndRefreshContext(currentPage, navigate, refreshFramework
|
|
|
334
371
|
clearRuntimeErrors();
|
|
335
372
|
await injectEventCollector(currentPage);
|
|
336
373
|
if (refreshFramework) {
|
|
374
|
+
await currentPage.waitForLoadState?.("networkidle", { timeout: 1_000 }).catch(() => { });
|
|
375
|
+
await currentPage.waitForTimeout?.(100).catch(() => { });
|
|
337
376
|
await detectFramework();
|
|
338
377
|
}
|
|
339
378
|
}
|
package/dist/cli.js
CHANGED
|
@@ -97,6 +97,47 @@ export async function runCli(argv, io) {
|
|
|
97
97
|
const res = await io.send("react-tree", { id });
|
|
98
98
|
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
99
99
|
}
|
|
100
|
+
if (cmd === "react" && arg === "store") {
|
|
101
|
+
const sub = args[2];
|
|
102
|
+
if (sub === "list") {
|
|
103
|
+
const res = await io.send("react-store-list");
|
|
104
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
105
|
+
}
|
|
106
|
+
if (sub === "inspect") {
|
|
107
|
+
const store = args[3];
|
|
108
|
+
if (!store) {
|
|
109
|
+
io.stderr("usage: vite-browser react store inspect <name>");
|
|
110
|
+
io.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const res = await io.send("react-store-inspect", { store });
|
|
113
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
114
|
+
}
|
|
115
|
+
io.stderr("usage: vite-browser react store <list|inspect> [name]");
|
|
116
|
+
io.exit(1);
|
|
117
|
+
}
|
|
118
|
+
if (cmd === "react" && arg === "hook") {
|
|
119
|
+
const sub = args[2];
|
|
120
|
+
if (sub === "health") {
|
|
121
|
+
const res = await io.send("react-hook-health");
|
|
122
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
123
|
+
}
|
|
124
|
+
if (sub === "inject") {
|
|
125
|
+
const res = await io.send("react-hook-inject");
|
|
126
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
127
|
+
}
|
|
128
|
+
io.stderr("usage: vite-browser react hook <health|inject>");
|
|
129
|
+
io.exit(1);
|
|
130
|
+
}
|
|
131
|
+
if (cmd === "react" && arg === "commits") {
|
|
132
|
+
const sub = args[2];
|
|
133
|
+
if (sub === "clear") {
|
|
134
|
+
const res = await io.send("react-commits-clear");
|
|
135
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "cleared React commit history");
|
|
136
|
+
}
|
|
137
|
+
const limit = parseNumberFlag(args, "--limit", 20);
|
|
138
|
+
const res = await io.send("react-commits", { limit });
|
|
139
|
+
exit(io, res, res.ok && res.data ? String(res.data) : "");
|
|
140
|
+
}
|
|
100
141
|
if (cmd === "svelte" && arg === "tree") {
|
|
101
142
|
const id = args[2];
|
|
102
143
|
const res = await io.send("svelte-tree", { id });
|
|
@@ -205,63 +246,69 @@ export function exit(io, res, msg) {
|
|
|
205
246
|
io.exit(0);
|
|
206
247
|
}
|
|
207
248
|
export function printUsage() {
|
|
208
|
-
return `
|
|
209
|
-
vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
|
|
210
|
-
|
|
211
|
-
USAGE
|
|
212
|
-
vite-browser <command> [options]
|
|
213
|
-
|
|
214
|
-
BROWSER CONTROL
|
|
215
|
-
open <url> [--cookies-json <file>] Launch browser and navigate
|
|
216
|
-
close Close browser and daemon
|
|
217
|
-
goto <url> Full-page navigation
|
|
218
|
-
back Go back in history
|
|
219
|
-
reload Reload current page
|
|
220
|
-
|
|
221
|
-
FRAMEWORK DETECTION
|
|
222
|
-
detect Detect framework (vue/react/svelte)
|
|
223
|
-
|
|
224
|
-
VUE COMMANDS
|
|
225
|
-
vue tree [id] Show Vue component tree or inspect component
|
|
226
|
-
vue pinia [store] Show Pinia stores or inspect specific store
|
|
227
|
-
vue router Show Vue Router information
|
|
228
|
-
|
|
229
|
-
REACT COMMANDS
|
|
230
|
-
react tree [id] Show React component tree or inspect component
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
vite
|
|
244
|
-
|
|
245
|
-
vite
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
249
|
+
return `
|
|
250
|
+
vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
|
|
251
|
+
|
|
252
|
+
USAGE
|
|
253
|
+
vite-browser <command> [options]
|
|
254
|
+
|
|
255
|
+
BROWSER CONTROL
|
|
256
|
+
open <url> [--cookies-json <file>] Launch browser and navigate
|
|
257
|
+
close Close browser and daemon
|
|
258
|
+
goto <url> Full-page navigation
|
|
259
|
+
back Go back in history
|
|
260
|
+
reload Reload current page
|
|
261
|
+
|
|
262
|
+
FRAMEWORK DETECTION
|
|
263
|
+
detect Detect framework (vue/react/svelte)
|
|
264
|
+
|
|
265
|
+
VUE COMMANDS
|
|
266
|
+
vue tree [id] Show Vue component tree or inspect component
|
|
267
|
+
vue pinia [store] Show Pinia stores or inspect specific store
|
|
268
|
+
vue router Show Vue Router information
|
|
269
|
+
|
|
270
|
+
REACT COMMANDS
|
|
271
|
+
react tree [id] Show React component tree or inspect component
|
|
272
|
+
react store list List detected Zustand stores
|
|
273
|
+
react store inspect <name> Show Zustand store state and actions
|
|
274
|
+
react hook health Show bundled React hook status
|
|
275
|
+
react hook inject Inject bundled React hook into current page
|
|
276
|
+
react commits [--limit <n>] Show recent React commit records
|
|
277
|
+
react commits clear Clear recorded React commit history
|
|
278
|
+
|
|
279
|
+
SVELTE COMMANDS
|
|
280
|
+
svelte tree [id] Show Svelte component tree or inspect component
|
|
281
|
+
|
|
282
|
+
VITE COMMANDS
|
|
283
|
+
vite restart Restart Vite dev server
|
|
284
|
+
vite hmr Show HMR summary
|
|
285
|
+
vite hmr trace [--limit <n>] Show HMR timeline
|
|
286
|
+
vite hmr clear Clear tracked HMR timeline
|
|
287
|
+
vite runtime Show Vite runtime status
|
|
288
|
+
vite module-graph [--filter <txt>] [--limit <n>]
|
|
289
|
+
Show loaded Vite module resources
|
|
290
|
+
vite module-graph trace [--filter <txt>] [--limit <n>]
|
|
291
|
+
Show module additions/removals since baseline
|
|
292
|
+
vite module-graph clear Clear module-graph baseline
|
|
293
|
+
errors Show build/runtime errors
|
|
294
|
+
errors --mapped Show errors with source-map mapping
|
|
295
|
+
errors --mapped --inline-source Include mapped source snippets
|
|
296
|
+
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
297
|
+
correlate renders [--window <ms>] Summarize recent render/update propagation evidence
|
|
298
|
+
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
299
|
+
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
300
|
+
diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
|
|
301
|
+
diagnose propagation [--window <ms>]
|
|
302
|
+
Diagnose likely update -> render -> error propagation
|
|
303
|
+
logs Show dev server logs
|
|
304
|
+
|
|
305
|
+
UTILITIES
|
|
306
|
+
screenshot Save screenshot to temp file
|
|
307
|
+
eval <script> Evaluate JavaScript in page context
|
|
308
|
+
network [idx] List network requests or inspect one
|
|
309
|
+
|
|
310
|
+
OPTIONS
|
|
311
|
+
-h, --help Show this help message
|
|
265
312
|
`;
|
|
266
313
|
}
|
|
267
314
|
function isEntrypoint(argv1) {
|
package/dist/client.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync, existsSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { socketPath, pidFile } from "./paths.js";
|
|
6
|
+
import { socketPath, pidFile, isWindows } from "./paths.js";
|
|
7
7
|
export function createClientDeps() {
|
|
8
8
|
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
9
9
|
const daemonPath = fileURLToPath(new URL(`./daemon${ext}`, import.meta.url));
|
|
@@ -86,7 +86,7 @@ export function no() {
|
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
export function removeSocketFile(path, removeFile = rmSync) {
|
|
89
|
-
if (
|
|
89
|
+
if (isWindows)
|
|
90
90
|
return;
|
|
91
91
|
removeFile(path, { force: true });
|
|
92
92
|
}
|
package/dist/daemon.js
CHANGED
|
@@ -8,6 +8,7 @@ import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
|
|
|
8
8
|
import { diagnosePropagation, formatPropagationDiagnosisReport } from "./diagnose-propagation.js";
|
|
9
9
|
import { extractModules } from "./event-analysis.js";
|
|
10
10
|
import { socketDir, socketPath, pidFile } from "./paths.js";
|
|
11
|
+
import { removeSocketFile } from "./client.js";
|
|
11
12
|
import { correlateRenderPropagation, formatPropagationTraceReport } from "./trace.js";
|
|
12
13
|
import { EventQueue } from "./event-queue.js";
|
|
13
14
|
import * as networkLog from "./network.js";
|
|
@@ -70,6 +71,30 @@ export function createRunner(api = browser) {
|
|
|
70
71
|
const data = await api.reactTree(cmd.id);
|
|
71
72
|
return { ok: true, data };
|
|
72
73
|
}
|
|
74
|
+
if (cmd.action === "react-store-list") {
|
|
75
|
+
const data = await api.reactStoreList();
|
|
76
|
+
return { ok: true, data };
|
|
77
|
+
}
|
|
78
|
+
if (cmd.action === "react-store-inspect") {
|
|
79
|
+
const data = await api.reactStoreInspect(cmd.store);
|
|
80
|
+
return { ok: true, data };
|
|
81
|
+
}
|
|
82
|
+
if (cmd.action === "react-hook-health") {
|
|
83
|
+
const data = await api.reactHookHealth();
|
|
84
|
+
return { ok: true, data };
|
|
85
|
+
}
|
|
86
|
+
if (cmd.action === "react-hook-inject") {
|
|
87
|
+
const data = await api.reactHookInject();
|
|
88
|
+
return { ok: true, data };
|
|
89
|
+
}
|
|
90
|
+
if (cmd.action === "react-commits") {
|
|
91
|
+
const data = await api.reactCommits(cmd.limit ?? 20);
|
|
92
|
+
return { ok: true, data };
|
|
93
|
+
}
|
|
94
|
+
if (cmd.action === "react-commits-clear") {
|
|
95
|
+
const data = await api.reactCommitsClear();
|
|
96
|
+
return { ok: true, data };
|
|
97
|
+
}
|
|
73
98
|
// Svelte commands
|
|
74
99
|
if (cmd.action === "svelte-tree") {
|
|
75
100
|
const data = await api.svelteTree(cmd.id);
|
|
@@ -303,7 +328,7 @@ export function startDaemon() {
|
|
|
303
328
|
networkLog.setEventQueue(eventQueue);
|
|
304
329
|
const run = createRunner();
|
|
305
330
|
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
306
|
-
removeSocketFile();
|
|
331
|
+
removeSocketFile(socketPath);
|
|
307
332
|
rmSync(pidFile, { force: true });
|
|
308
333
|
writeFileSync(pidFile, String(process.pid));
|
|
309
334
|
const server = createServer((socket) => {
|
|
@@ -330,16 +355,10 @@ export function startDaemon() {
|
|
|
330
355
|
process.exit(0);
|
|
331
356
|
}
|
|
332
357
|
function cleanup() {
|
|
333
|
-
removeSocketFile();
|
|
358
|
+
removeSocketFile(socketPath);
|
|
334
359
|
rmSync(pidFile, { force: true });
|
|
335
360
|
}
|
|
336
361
|
}
|
|
337
|
-
function removeSocketFile() {
|
|
338
|
-
// Windows named pipes are not filesystem entries, so unlinking them fails with EPERM.
|
|
339
|
-
if (process.platform === "win32")
|
|
340
|
-
return;
|
|
341
|
-
rmSync(socketPath, { force: true });
|
|
342
|
-
}
|
|
343
362
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
344
363
|
startDaemon();
|
|
345
364
|
}
|
package/dist/event-queue.d.ts
CHANGED
|
@@ -74,8 +74,15 @@ export declare class EventQueue {
|
|
|
74
74
|
private readonly maxSize;
|
|
75
75
|
constructor(maxSize?: number);
|
|
76
76
|
push(event: VBEvent): void;
|
|
77
|
+
/** Bubble the element at `idx` leftward to restore sorted order. */
|
|
78
|
+
private _insertionSort;
|
|
77
79
|
/**
|
|
78
|
-
* Return all events within the last `ms` milliseconds before `before
|
|
80
|
+
* Return all events within the last `ms` milliseconds before `before`.
|
|
81
|
+
*
|
|
82
|
+
* Uses binary search to find the start index (O(log n)) then slices,
|
|
83
|
+
* which is significantly faster than a full linear scan for large queues.
|
|
84
|
+
* Events are maintained in insertion order, which for timestamped pushes
|
|
85
|
+
* is monotonically non-decreasing.
|
|
79
86
|
*/
|
|
80
87
|
window(ms: number, before?: number): VBEvent[];
|
|
81
88
|
/**
|
|
@@ -83,5 +90,7 @@ export declare class EventQueue {
|
|
|
83
90
|
*/
|
|
84
91
|
ofType(type: VBEventType): VBEvent[];
|
|
85
92
|
all(): VBEvent[];
|
|
93
|
+
/** Number of events currently stored */
|
|
94
|
+
get size(): number;
|
|
86
95
|
clear(): void;
|
|
87
96
|
}
|
package/dist/event-queue.js
CHANGED
|
@@ -9,13 +9,59 @@ export class EventQueue {
|
|
|
9
9
|
if (this.events.length > this.maxSize) {
|
|
10
10
|
this.events.shift();
|
|
11
11
|
}
|
|
12
|
+
// Maintain sorted order by timestamp. Events almost always arrive
|
|
13
|
+
// in order, so the fast path (no swap) costs a single comparison.
|
|
14
|
+
const len = this.events.length;
|
|
15
|
+
if (len >= 2 && this.events[len - 1].timestamp < this.events[len - 2].timestamp) {
|
|
16
|
+
this._insertionSort(len - 1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Bubble the element at `idx` leftward to restore sorted order. */
|
|
20
|
+
_insertionSort(idx) {
|
|
21
|
+
const events = this.events;
|
|
22
|
+
const item = events[idx];
|
|
23
|
+
let j = idx - 1;
|
|
24
|
+
while (j >= 0 && events[j].timestamp > item.timestamp) {
|
|
25
|
+
events[j + 1] = events[j];
|
|
26
|
+
j--;
|
|
27
|
+
}
|
|
28
|
+
events[j + 1] = item;
|
|
12
29
|
}
|
|
13
30
|
/**
|
|
14
|
-
* Return all events within the last `ms` milliseconds before `before
|
|
31
|
+
* Return all events within the last `ms` milliseconds before `before`.
|
|
32
|
+
*
|
|
33
|
+
* Uses binary search to find the start index (O(log n)) then slices,
|
|
34
|
+
* which is significantly faster than a full linear scan for large queues.
|
|
35
|
+
* Events are maintained in insertion order, which for timestamped pushes
|
|
36
|
+
* is monotonically non-decreasing.
|
|
15
37
|
*/
|
|
16
38
|
window(ms, before = Date.now()) {
|
|
17
39
|
const start = before - ms;
|
|
18
|
-
|
|
40
|
+
const events = this.events;
|
|
41
|
+
const len = events.length;
|
|
42
|
+
if (len === 0)
|
|
43
|
+
return [];
|
|
44
|
+
// Binary search for the first event with timestamp >= start
|
|
45
|
+
let lo = 0;
|
|
46
|
+
let hi = len;
|
|
47
|
+
while (lo < hi) {
|
|
48
|
+
const mid = (lo + hi) >>> 1;
|
|
49
|
+
if (events[mid].timestamp < start) {
|
|
50
|
+
lo = mid + 1;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
hi = mid;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Linear scan from lo for events <= before
|
|
57
|
+
// (in practice, `before` is usually Date.now() so most events qualify)
|
|
58
|
+
const result = [];
|
|
59
|
+
for (let i = lo; i < len; i++) {
|
|
60
|
+
if (events[i].timestamp > before)
|
|
61
|
+
break;
|
|
62
|
+
result.push(events[i]);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
19
65
|
}
|
|
20
66
|
/**
|
|
21
67
|
* Return all events of a given type
|
|
@@ -26,6 +72,10 @@ export class EventQueue {
|
|
|
26
72
|
all() {
|
|
27
73
|
return [...this.events];
|
|
28
74
|
}
|
|
75
|
+
/** Number of events currently stored */
|
|
76
|
+
get size() {
|
|
77
|
+
return this.events.length;
|
|
78
|
+
}
|
|
29
79
|
clear() {
|
|
30
80
|
this.events = [];
|
|
31
81
|
}
|
package/dist/paths.d.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
export declare const isWindows: boolean;
|
|
2
|
+
export declare const isLinux: boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Sanitize a session name for safe use in file paths and pipe names.
|
|
5
|
+
*/
|
|
6
|
+
export declare function sanitizeSession(name: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the base directory for vite-browser runtime files.
|
|
9
|
+
*
|
|
10
|
+
* Uses `~/.vite-browser` on all platforms.
|
|
11
|
+
* Falls back to `$TMPDIR/vite-browser-<uid>` when the home directory
|
|
12
|
+
* is not writable (e.g. some CI/container environments).
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveSocketDir(): string;
|
|
1
15
|
export declare const socketDir: string;
|
|
16
|
+
/**
|
|
17
|
+
* Socket path for the daemon.
|
|
18
|
+
*
|
|
19
|
+
* - Windows: uses a named pipe `\\.\pipe\vite-browser-<session>`
|
|
20
|
+
* - Unix: uses a Unix domain socket file in socketDir
|
|
21
|
+
*
|
|
22
|
+
* Note: Unix socket paths have a ~104-char limit on macOS and ~108 on
|
|
23
|
+
* Linux. The `~/.vite-browser/<session>.sock` path is well within
|
|
24
|
+
* that range. The tmpdir fallback may produce longer paths; we keep
|
|
25
|
+
* them short by using numeric uid.
|
|
26
|
+
*/
|
|
2
27
|
export declare const socketPath: string;
|
|
3
28
|
export declare const pidFile: string;
|