@presto1314w/vite-devtools-browser 0.3.2 → 0.3.5
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 -320
- package/dist/browser-session.d.ts +11 -1
- package/dist/browser-session.js +29 -40
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +19 -1
- package/dist/cli.js +57 -57
- package/dist/client.js +2 -2
- package/dist/daemon.js +91 -18
- 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 +42 -4
- 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 +227 -0
- package/dist/react/profiler.d.ts +55 -0
- package/dist/react/profiler.js +166 -0
- package/dist/react/zustand.d.ts +31 -0
- package/dist/react/zustand.js +113 -0
- package/dist/trace.js +5 -4
- package/dist/vue/devtools.d.ts +1 -1
- package/dist/vue/devtools.js +41 -3
- package/package.json +69 -65
package/dist/browser.js
CHANGED
|
@@ -19,6 +19,20 @@ export function setEventQueue(queue) {
|
|
|
19
19
|
export function getEventQueue() {
|
|
20
20
|
return eventQueue;
|
|
21
21
|
}
|
|
22
|
+
export function getTrackedHmrEvents(windowMs = 5000) {
|
|
23
|
+
const cutoff = Date.now() - windowMs;
|
|
24
|
+
return session.hmrEvents
|
|
25
|
+
.filter((event) => event.timestamp >= cutoff)
|
|
26
|
+
.map((event) => ({
|
|
27
|
+
timestamp: event.timestamp,
|
|
28
|
+
type: event.type === "error" ? "hmr-error" : "hmr-update",
|
|
29
|
+
payload: {
|
|
30
|
+
path: event.path,
|
|
31
|
+
message: event.message,
|
|
32
|
+
updates: event.path ? [{ path: event.path }] : [],
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
22
36
|
export function getCurrentPage() {
|
|
23
37
|
return getSessionPage(session);
|
|
24
38
|
}
|
|
@@ -150,7 +164,7 @@ export async function goto(url) {
|
|
|
150
164
|
}
|
|
151
165
|
export async function back() {
|
|
152
166
|
const currentPage = await ensurePage();
|
|
153
|
-
await currentPage.goBack({ waitUntil: "domcontentloaded" });
|
|
167
|
+
await navigateAndRefreshContext(currentPage, () => currentPage.goBack({ waitUntil: "domcontentloaded" }));
|
|
154
168
|
}
|
|
155
169
|
export async function reload() {
|
|
156
170
|
const currentPage = await ensurePage();
|
|
@@ -317,8 +331,12 @@ function requireCurrentPage() {
|
|
|
317
331
|
}
|
|
318
332
|
async function navigateAndRefreshContext(currentPage, navigate, refreshFramework = false) {
|
|
319
333
|
await navigate();
|
|
334
|
+
clearRuntimeErrors();
|
|
320
335
|
await injectEventCollector(currentPage);
|
|
321
336
|
if (refreshFramework) {
|
|
322
337
|
await detectFramework();
|
|
323
338
|
}
|
|
324
339
|
}
|
|
340
|
+
function clearRuntimeErrors() {
|
|
341
|
+
session.runtimeErrors.length = 0;
|
|
342
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -205,63 +205,63 @@ export function exit(io, res, msg) {
|
|
|
205
205
|
io.exit(0);
|
|
206
206
|
}
|
|
207
207
|
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
|
-
SVELTE COMMANDS
|
|
233
|
-
svelte tree [id] Show Svelte component tree or inspect component
|
|
234
|
-
|
|
235
|
-
VITE COMMANDS
|
|
236
|
-
vite restart Restart Vite dev server
|
|
237
|
-
vite hmr Show HMR summary
|
|
238
|
-
vite hmr trace [--limit <n>] Show HMR timeline
|
|
239
|
-
vite hmr clear Clear tracked HMR timeline
|
|
240
|
-
vite runtime Show Vite runtime status
|
|
241
|
-
vite module-graph [--filter <txt>] [--limit <n>]
|
|
242
|
-
Show loaded Vite module resources
|
|
243
|
-
vite module-graph trace [--filter <txt>] [--limit <n>]
|
|
244
|
-
Show module additions/removals since baseline
|
|
245
|
-
vite module-graph clear Clear module-graph baseline
|
|
246
|
-
errors Show build/runtime errors
|
|
247
|
-
errors --mapped Show errors with source-map mapping
|
|
248
|
-
errors --mapped --inline-source Include mapped source snippets
|
|
249
|
-
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
250
|
-
correlate renders [--window <ms>] Summarize recent render/update propagation evidence
|
|
251
|
-
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
252
|
-
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
253
|
-
diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
|
|
254
|
-
diagnose propagation [--window <ms>]
|
|
255
|
-
Diagnose likely update -> render -> error propagation
|
|
256
|
-
logs Show dev server logs
|
|
257
|
-
|
|
258
|
-
UTILITIES
|
|
259
|
-
screenshot Save screenshot to temp file
|
|
260
|
-
eval <script> Evaluate JavaScript in page context
|
|
261
|
-
network [idx] List network requests or inspect one
|
|
262
|
-
|
|
263
|
-
OPTIONS
|
|
264
|
-
-h, --help Show this help message
|
|
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
|
+
SVELTE COMMANDS
|
|
233
|
+
svelte tree [id] Show Svelte component tree or inspect component
|
|
234
|
+
|
|
235
|
+
VITE COMMANDS
|
|
236
|
+
vite restart Restart Vite dev server
|
|
237
|
+
vite hmr Show HMR summary
|
|
238
|
+
vite hmr trace [--limit <n>] Show HMR timeline
|
|
239
|
+
vite hmr clear Clear tracked HMR timeline
|
|
240
|
+
vite runtime Show Vite runtime status
|
|
241
|
+
vite module-graph [--filter <txt>] [--limit <n>]
|
|
242
|
+
Show loaded Vite module resources
|
|
243
|
+
vite module-graph trace [--filter <txt>] [--limit <n>]
|
|
244
|
+
Show module additions/removals since baseline
|
|
245
|
+
vite module-graph clear Clear module-graph baseline
|
|
246
|
+
errors Show build/runtime errors
|
|
247
|
+
errors --mapped Show errors with source-map mapping
|
|
248
|
+
errors --mapped --inline-source Include mapped source snippets
|
|
249
|
+
correlate errors [--window <ms>] Correlate current errors with recent HMR events
|
|
250
|
+
correlate renders [--window <ms>] Summarize recent render/update propagation evidence
|
|
251
|
+
correlate errors --mapped Correlate mapped errors with recent HMR events
|
|
252
|
+
diagnose hmr [--window <ms>] Diagnose HMR failures from runtime, errors, and trace data
|
|
253
|
+
diagnose hmr [--limit <n>] Control how many recent HMR trace entries are inspected
|
|
254
|
+
diagnose propagation [--window <ms>]
|
|
255
|
+
Diagnose likely update -> render -> error propagation
|
|
256
|
+
logs Show dev server logs
|
|
257
|
+
|
|
258
|
+
UTILITIES
|
|
259
|
+
screenshot Save screenshot to temp file
|
|
260
|
+
eval <script> Evaluate JavaScript in page context
|
|
261
|
+
network [idx] List network requests or inspect one
|
|
262
|
+
|
|
263
|
+
OPTIONS
|
|
264
|
+
-h, --help Show this help message
|
|
265
265
|
`;
|
|
266
266
|
}
|
|
267
267
|
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
|
@@ -6,7 +6,9 @@ import * as browser from "./browser.js";
|
|
|
6
6
|
import { correlateErrorWithHMR, formatErrorCorrelationReport } from "./correlate.js";
|
|
7
7
|
import { diagnoseHMR, formatDiagnosisReport } from "./diagnose.js";
|
|
8
8
|
import { diagnosePropagation, formatPropagationDiagnosisReport } from "./diagnose-propagation.js";
|
|
9
|
+
import { extractModules } from "./event-analysis.js";
|
|
9
10
|
import { socketDir, socketPath, pidFile } from "./paths.js";
|
|
11
|
+
import { removeSocketFile } from "./client.js";
|
|
10
12
|
import { correlateRenderPropagation, formatPropagationTraceReport } from "./trace.js";
|
|
11
13
|
import { EventQueue } from "./event-queue.js";
|
|
12
14
|
import * as networkLog from "./network.js";
|
|
@@ -100,12 +102,20 @@ export function createRunner(api = browser) {
|
|
|
100
102
|
if (cmd.action === "correlate-errors") {
|
|
101
103
|
const errorText = String(await api.errors(Boolean(cmd.mapped), Boolean(cmd.inlineSource)));
|
|
102
104
|
const events = queue ? queue.window(cmd.windowMs ?? 5000) : [];
|
|
103
|
-
const
|
|
105
|
+
const fallbackHmrEvents = readTrackedHmrEvents(api, cmd.windowMs ?? 5000);
|
|
106
|
+
const hmrTraceText = String(await api.viteHMRTrace("trace", 20));
|
|
107
|
+
const fallbackModules = extractModules(hmrTraceText);
|
|
108
|
+
const baseCorrelation = errorText === "no errors"
|
|
109
|
+
? null
|
|
110
|
+
: correlateErrorWithHMR(errorText, [...events, ...fallbackHmrEvents], cmd.windowMs ?? 5000);
|
|
111
|
+
const data = formatErrorCorrelationReport(errorText, errorText === "no errors"
|
|
112
|
+
? null
|
|
113
|
+
: upgradeErrorCorrelation(baseCorrelation, fallbackModules, cmd.windowMs ?? 5000));
|
|
104
114
|
return { ok: true, data };
|
|
105
115
|
}
|
|
106
116
|
if (cmd.action === "correlate-renders") {
|
|
107
117
|
const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
|
|
108
|
-
const data = formatPropagationTraceReport(await buildPropagationTrace(api, events));
|
|
118
|
+
const data = formatPropagationTraceReport(await buildPropagationTrace(api, events, cmd.windowMs ?? 5000));
|
|
109
119
|
return { ok: true, data };
|
|
110
120
|
}
|
|
111
121
|
if (cmd.action === "diagnose-hmr") {
|
|
@@ -119,7 +129,7 @@ export function createRunner(api = browser) {
|
|
|
119
129
|
}
|
|
120
130
|
if (cmd.action === "diagnose-propagation") {
|
|
121
131
|
const events = await getSettledEventWindow(api, queue, cmd.windowMs ?? 5000);
|
|
122
|
-
const data = formatPropagationDiagnosisReport(diagnosePropagation(await buildPropagationTrace(api, events)));
|
|
132
|
+
const data = formatPropagationDiagnosisReport(diagnosePropagation(await buildPropagationTrace(api, events, cmd.windowMs ?? 5000)));
|
|
123
133
|
return { ok: true, data };
|
|
124
134
|
}
|
|
125
135
|
if (cmd.action === "logs") {
|
|
@@ -146,20 +156,89 @@ export function createRunner(api = browser) {
|
|
|
146
156
|
return { ok: false, error: `unknown action: ${cmd.action}` };
|
|
147
157
|
};
|
|
148
158
|
}
|
|
149
|
-
async function buildPropagationTrace(api, events) {
|
|
150
|
-
const
|
|
159
|
+
async function buildPropagationTrace(api, events, windowMs) {
|
|
160
|
+
const hmrTraceText = String(await api.viteHMRTrace("trace", 20));
|
|
161
|
+
const fallbackModules = extractModules(hmrTraceText);
|
|
162
|
+
const trace = correlateRenderPropagation([...events, ...readTrackedHmrEvents(api, windowMs)]);
|
|
151
163
|
if (!trace)
|
|
152
164
|
return null;
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
let augmented = trace;
|
|
166
|
+
if (augmented.sourceModules.length === 0 && fallbackModules.length > 0) {
|
|
167
|
+
augmented = {
|
|
168
|
+
...augmented,
|
|
169
|
+
sourceModules: fallbackModules,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (augmented.changedKeys.length === 0 && augmented.storeUpdates.length > 0) {
|
|
173
|
+
const inferredChangedKeys = await inferLikelyChangedKeys(api, augmented.storeUpdates[0]);
|
|
174
|
+
if (inferredChangedKeys.length > 0) {
|
|
175
|
+
augmented = {
|
|
176
|
+
...augmented,
|
|
177
|
+
changedKeys: inferredChangedKeys,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (augmented.errorMessages.length > 0)
|
|
182
|
+
return augmented;
|
|
155
183
|
const currentError = String(await api.errors(false, false));
|
|
156
184
|
if (!currentError || currentError === "no errors")
|
|
157
|
-
return
|
|
185
|
+
return augmented;
|
|
158
186
|
return {
|
|
159
|
-
...
|
|
160
|
-
errorMessages: [currentError, ...
|
|
187
|
+
...augmented,
|
|
188
|
+
errorMessages: [currentError, ...augmented.errorMessages],
|
|
161
189
|
};
|
|
162
190
|
}
|
|
191
|
+
function upgradeErrorCorrelation(correlation, fallbackModules, windowMs) {
|
|
192
|
+
if (fallbackModules.length === 0)
|
|
193
|
+
return correlation;
|
|
194
|
+
if (!correlation)
|
|
195
|
+
return synthesizeErrorCorrelation(fallbackModules, windowMs);
|
|
196
|
+
if (correlation.matchingModules.length > 0)
|
|
197
|
+
return correlation;
|
|
198
|
+
return synthesizeErrorCorrelation(fallbackModules, windowMs, correlation.relatedEvents);
|
|
199
|
+
}
|
|
200
|
+
function synthesizeErrorCorrelation(modules, windowMs, relatedEvents = []) {
|
|
201
|
+
if (modules.length === 0)
|
|
202
|
+
return null;
|
|
203
|
+
return {
|
|
204
|
+
summary: `HMR update observed within ${windowMs}ms of the current error`,
|
|
205
|
+
detail: `Matching modules: ${modules.join(", ")}\nRecent events considered: ${modules.length}`,
|
|
206
|
+
confidence: "high",
|
|
207
|
+
windowMs,
|
|
208
|
+
matchingModules: modules,
|
|
209
|
+
relatedEvents: relatedEvents.length > 0
|
|
210
|
+
? relatedEvents
|
|
211
|
+
: modules.map((module) => ({
|
|
212
|
+
timestamp: Date.now(),
|
|
213
|
+
type: "hmr-update",
|
|
214
|
+
payload: {
|
|
215
|
+
path: module,
|
|
216
|
+
updates: [{ path: module }],
|
|
217
|
+
},
|
|
218
|
+
})),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async function inferLikelyChangedKeys(api, storeName) {
|
|
222
|
+
try {
|
|
223
|
+
const raw = await api.evaluate(`(() => {
|
|
224
|
+
const store = window.__PINIA__?._s?.get(${JSON.stringify(storeName)});
|
|
225
|
+
if (!store) return [];
|
|
226
|
+
return Object.keys(store).filter((key) => store[key] === undefined);
|
|
227
|
+
})()`);
|
|
228
|
+
const parsed = JSON.parse(String(raw));
|
|
229
|
+
return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string" && value.length > 0) : [];
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function readTrackedHmrEvents(api, windowMs) {
|
|
236
|
+
const reader = api.getTrackedHmrEvents;
|
|
237
|
+
if (typeof reader !== "function")
|
|
238
|
+
return [];
|
|
239
|
+
const events = reader(windowMs);
|
|
240
|
+
return Array.isArray(events) ? events : [];
|
|
241
|
+
}
|
|
163
242
|
async function flushCurrentPageEvents(api, queue) {
|
|
164
243
|
if (!queue)
|
|
165
244
|
return;
|
|
@@ -225,7 +304,7 @@ export function startDaemon() {
|
|
|
225
304
|
networkLog.setEventQueue(eventQueue);
|
|
226
305
|
const run = createRunner();
|
|
227
306
|
mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
228
|
-
removeSocketFile();
|
|
307
|
+
removeSocketFile(socketPath);
|
|
229
308
|
rmSync(pidFile, { force: true });
|
|
230
309
|
writeFileSync(pidFile, String(process.pid));
|
|
231
310
|
const server = createServer((socket) => {
|
|
@@ -252,16 +331,10 @@ export function startDaemon() {
|
|
|
252
331
|
process.exit(0);
|
|
253
332
|
}
|
|
254
333
|
function cleanup() {
|
|
255
|
-
removeSocketFile();
|
|
334
|
+
removeSocketFile(socketPath);
|
|
256
335
|
rmSync(pidFile, { force: true });
|
|
257
336
|
}
|
|
258
337
|
}
|
|
259
|
-
function removeSocketFile() {
|
|
260
|
-
// Windows named pipes are not filesystem entries, so unlinking them fails with EPERM.
|
|
261
|
-
if (process.platform === "win32")
|
|
262
|
-
return;
|
|
263
|
-
rmSync(socketPath, { force: true });
|
|
264
|
-
}
|
|
265
338
|
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
266
339
|
startDaemon();
|
|
267
340
|
}
|
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;
|
package/dist/paths.js
CHANGED
|
@@ -1,8 +1,46 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
1
|
+
import { homedir, tmpdir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
const isWindows = process.platform === "win32";
|
|
4
|
-
const
|
|
5
|
-
|
|
3
|
+
export const isWindows = process.platform === "win32";
|
|
4
|
+
export const isLinux = process.platform === "linux";
|
|
5
|
+
/**
|
|
6
|
+
* Sanitize a session name for safe use in file paths and pipe names.
|
|
7
|
+
*/
|
|
8
|
+
export function sanitizeSession(name) {
|
|
9
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
10
|
+
}
|
|
11
|
+
const session = sanitizeSession(process.env.VITE_BROWSER_SESSION || "default");
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the base directory for vite-browser runtime files.
|
|
14
|
+
*
|
|
15
|
+
* Uses `~/.vite-browser` on all platforms.
|
|
16
|
+
* Falls back to `$TMPDIR/vite-browser-<uid>` when the home directory
|
|
17
|
+
* is not writable (e.g. some CI/container environments).
|
|
18
|
+
*/
|
|
19
|
+
export function resolveSocketDir() {
|
|
20
|
+
try {
|
|
21
|
+
const home = homedir();
|
|
22
|
+
if (home)
|
|
23
|
+
return join(home, ".vite-browser");
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// homedir() can throw on misconfigured systems
|
|
27
|
+
}
|
|
28
|
+
// Fallback: use tmpdir scoped by uid to avoid collisions
|
|
29
|
+
const uid = process.getuid?.() ?? process.pid;
|
|
30
|
+
return join(tmpdir(), `vite-browser-${uid}`);
|
|
31
|
+
}
|
|
32
|
+
export const socketDir = resolveSocketDir();
|
|
33
|
+
/**
|
|
34
|
+
* Socket path for the daemon.
|
|
35
|
+
*
|
|
36
|
+
* - Windows: uses a named pipe `\\.\pipe\vite-browser-<session>`
|
|
37
|
+
* - Unix: uses a Unix domain socket file in socketDir
|
|
38
|
+
*
|
|
39
|
+
* Note: Unix socket paths have a ~104-char limit on macOS and ~108 on
|
|
40
|
+
* Linux. The `~/.vite-browser/<session>.sock` path is well within
|
|
41
|
+
* that range. The tmpdir fallback may produce longer paths; we keep
|
|
42
|
+
* them short by using numeric uid.
|
|
43
|
+
*/
|
|
6
44
|
export const socketPath = isWindows
|
|
7
45
|
? `\\\\.\\pipe\\vite-browser-${session}`
|
|
8
46
|
: join(socketDir, `${session}.sock`);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React DevTools Hook Management
|
|
3
|
+
*
|
|
4
|
+
* Provides health checks and auto-injection for the bundled React DevTools hook.
|
|
5
|
+
* This removes the dependency on external browser extensions for React inspection.
|
|
6
|
+
*/
|
|
7
|
+
import type { Page } from "playwright";
|
|
8
|
+
/**
|
|
9
|
+
* Get the hook source code, lazily loaded and cached
|
|
10
|
+
*/
|
|
11
|
+
export declare function getHookSource(): string;
|
|
12
|
+
export interface HookHealthStatus {
|
|
13
|
+
installed: boolean;
|
|
14
|
+
hasRenderers: boolean;
|
|
15
|
+
rendererCount: number;
|
|
16
|
+
hasFiberSupport: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check the health of the React DevTools hook in the page
|
|
20
|
+
*/
|
|
21
|
+
export declare function checkHookHealth(page: Page): Promise<HookHealthStatus>;
|
|
22
|
+
/**
|
|
23
|
+
* Inject the React DevTools hook into a page if not already present
|
|
24
|
+
*/
|
|
25
|
+
export declare function injectHook(page: Page): Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Format hook health status for CLI output
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatHookHealth(status: HookHealthStatus): string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React DevTools Hook Management
|
|
3
|
+
*
|
|
4
|
+
* Provides health checks and auto-injection for the bundled React DevTools hook.
|
|
5
|
+
* This removes the dependency on external browser extensions for React inspection.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
/** Path to the bundled React DevTools hook */
|
|
10
|
+
const hookPath = resolve(import.meta.dirname, "./hook.js");
|
|
11
|
+
/** Cached hook source code */
|
|
12
|
+
let hookSource = null;
|
|
13
|
+
/**
|
|
14
|
+
* Get the hook source code, lazily loaded and cached
|
|
15
|
+
*/
|
|
16
|
+
export function getHookSource() {
|
|
17
|
+
if (!hookSource) {
|
|
18
|
+
hookSource = readFileSync(hookPath, "utf-8");
|
|
19
|
+
}
|
|
20
|
+
return hookSource;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Check the health of the React DevTools hook in the page
|
|
24
|
+
*/
|
|
25
|
+
export async function checkHookHealth(page) {
|
|
26
|
+
return page.evaluate(inPageCheckHookHealth);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inject the React DevTools hook into a page if not already present
|
|
30
|
+
*/
|
|
31
|
+
export async function injectHook(page) {
|
|
32
|
+
const alreadyInstalled = await page.evaluate(() => !!window.__REACT_DEVTOOLS_GLOBAL_HOOK__);
|
|
33
|
+
if (alreadyInstalled)
|
|
34
|
+
return false;
|
|
35
|
+
await page.evaluate(getHookSource());
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format hook health status for CLI output
|
|
40
|
+
*/
|
|
41
|
+
export function formatHookHealth(status) {
|
|
42
|
+
const lines = ["# React DevTools Hook Status\n"];
|
|
43
|
+
lines.push(`Installed: ${status.installed ? "✅ Yes" : "❌ No"}`);
|
|
44
|
+
lines.push(`Fiber support: ${status.hasFiberSupport ? "✅ Yes" : "❌ No"}`);
|
|
45
|
+
lines.push(`Renderers: ${status.rendererCount}`);
|
|
46
|
+
lines.push(`Has renderers: ${status.hasRenderers ? "✅ Yes" : "❌ No"}`);
|
|
47
|
+
if (!status.installed) {
|
|
48
|
+
lines.push("\n⚠️ Hook not installed. React DevTools features will not work.");
|
|
49
|
+
lines.push("The hook should be injected before React loads.");
|
|
50
|
+
}
|
|
51
|
+
else if (!status.hasRenderers) {
|
|
52
|
+
lines.push("\n⚠️ No React renderers detected.");
|
|
53
|
+
lines.push("This page may not be using React, or React hasn't mounted yet.");
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
// In-page functions
|
|
58
|
+
function inPageCheckHookHealth() {
|
|
59
|
+
const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
60
|
+
if (!hook) {
|
|
61
|
+
return {
|
|
62
|
+
installed: false,
|
|
63
|
+
hasRenderers: false,
|
|
64
|
+
rendererCount: 0,
|
|
65
|
+
hasFiberSupport: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const rendererCount = hook.renderers?.size ?? 0;
|
|
69
|
+
return {
|
|
70
|
+
installed: true,
|
|
71
|
+
hasRenderers: rendererCount > 0,
|
|
72
|
+
rendererCount,
|
|
73
|
+
hasFiberSupport: !!hook.supportsFiber,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|