@presto1314w/vite-devtools-browser 0.3.3 → 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/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 (process.platform === "win32")
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";
@@ -303,7 +304,7 @@ export function startDaemon() {
303
304
  networkLog.setEventQueue(eventQueue);
304
305
  const run = createRunner();
305
306
  mkdirSync(socketDir, { recursive: true, mode: 0o700 });
306
- removeSocketFile();
307
+ removeSocketFile(socketPath);
307
308
  rmSync(pidFile, { force: true });
308
309
  writeFileSync(pidFile, String(process.pid));
309
310
  const server = createServer((socket) => {
@@ -330,16 +331,10 @@ export function startDaemon() {
330
331
  process.exit(0);
331
332
  }
332
333
  function cleanup() {
333
- removeSocketFile();
334
+ removeSocketFile(socketPath);
334
335
  rmSync(pidFile, { force: true });
335
336
  }
336
337
  }
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
338
  if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
344
339
  startDaemon();
345
340
  }
@@ -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
  }
@@ -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
- return this.events.filter((e) => e.timestamp >= start && e.timestamp <= before);
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 session = process.env.VITE_BROWSER_SESSION || "default";
5
- export const socketDir = join(homedir(), ".vite-browser");
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 {};