@presto1314w/vite-devtools-browser 0.1.0
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 -0
- package/README.md +112 -0
- package/dist/browser.d.ts +22 -0
- package/dist/browser.js +238 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +168 -0
- package/dist/client.d.ts +8 -0
- package/dist/client.js +72 -0
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +132 -0
- package/dist/network.d.ts +5 -0
- package/dist/network.js +97 -0
- package/dist/paths.d.ts +3 -0
- package/dist/paths.js +9 -0
- package/dist/react/devtools.d.ts +16 -0
- package/dist/react/devtools.js +218 -0
- package/dist/svelte/devtools.d.ts +3 -0
- package/dist/svelte/devtools.js +177 -0
- package/dist/vue/devtools.d.ts +29 -0
- package/dist/vue/devtools.js +277 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vercel, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# vite-browser
|
|
2
|
+
|
|
3
|
+
CLI for programmatic access to Vue/React/Svelte DevTools in Vite applications. Provides component trees, props, state, Pinia stores, Vue Router, console logs, and network requests as structured text output designed for AI agents and automation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @presto1314w/vite-devtools-browser
|
|
9
|
+
npx playwright install chromium
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# Start your Vite dev server
|
|
16
|
+
cd my-vue-app
|
|
17
|
+
npm run dev
|
|
18
|
+
|
|
19
|
+
# In another terminal
|
|
20
|
+
vite-browser open http://localhost:5173
|
|
21
|
+
vite-browser detect # vue@3.5.29 / react@19.x / svelte@x
|
|
22
|
+
vite-browser vue tree # Vue component tree
|
|
23
|
+
vite-browser react tree # React component tree
|
|
24
|
+
vite-browser svelte tree # Svelte component tree (best effort)
|
|
25
|
+
vite-browser screenshot # Take screenshot
|
|
26
|
+
vite-browser logs # Console logs
|
|
27
|
+
vite-browser network # Network requests
|
|
28
|
+
vite-browser close
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Framework Detection: Auto-detect Vue, React, Svelte and versions (best effort)
|
|
34
|
+
- Vue DevTools: Component tree, props, state, computed properties, source locations
|
|
35
|
+
- React DevTools: Component tree + component inspection (props/hooks/state/context)
|
|
36
|
+
- Svelte Support: Component tree + component detail inspection when runtime metadata is available
|
|
37
|
+
- Pinia Integration: Inspect store state and getters
|
|
38
|
+
- Vue Router: Current route, params, query, all routes
|
|
39
|
+
- Network Monitoring: Track requests, headers, bodies, and response payloads
|
|
40
|
+
- Console Logs: Capture console.log, warn, error, debug
|
|
41
|
+
- Screenshots: Full-page PNG screenshots
|
|
42
|
+
- JavaScript Evaluation: Run arbitrary JS in page context
|
|
43
|
+
- Vite Integration: Error tracking, HMR monitoring
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
### Browser Control
|
|
48
|
+
```bash
|
|
49
|
+
vite-browser open <url> # Launch browser and navigate
|
|
50
|
+
vite-browser close # Close browser and daemon
|
|
51
|
+
vite-browser goto <url> # Navigate to URL
|
|
52
|
+
vite-browser back # Go back
|
|
53
|
+
vite-browser reload # Reload page
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Framework Detection
|
|
57
|
+
```bash
|
|
58
|
+
vite-browser detect # Detect framework and version
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Vue DevTools
|
|
62
|
+
```bash
|
|
63
|
+
vite-browser vue tree # Show component tree
|
|
64
|
+
vite-browser vue tree <id> # Inspect component details
|
|
65
|
+
vite-browser vue pinia # List all Pinia stores
|
|
66
|
+
vite-browser vue pinia <store> # Inspect specific store
|
|
67
|
+
vite-browser vue router # Show router information
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### React DevTools
|
|
71
|
+
```bash
|
|
72
|
+
vite-browser react tree # Show React component tree
|
|
73
|
+
vite-browser react tree <id> # Inspect props/hooks/state/context/source
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Svelte
|
|
77
|
+
```bash
|
|
78
|
+
vite-browser svelte tree # Show Svelte component tree
|
|
79
|
+
vite-browser svelte tree <id> # Inspect Svelte component details
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Debugging
|
|
83
|
+
```bash
|
|
84
|
+
vite-browser screenshot # Take full-page screenshot
|
|
85
|
+
vite-browser eval <script> # Run JavaScript in page
|
|
86
|
+
vite-browser logs # Show console logs
|
|
87
|
+
vite-browser errors # Show Vite errors
|
|
88
|
+
vite-browser network # List network requests
|
|
89
|
+
vite-browser network <idx> # Inspect specific request
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Architecture
|
|
93
|
+
|
|
94
|
+
- Daemon + Socket: Background process with socket communication
|
|
95
|
+
- Playwright: Headed Chromium browser
|
|
96
|
+
- One Browser, One Page: Single persistent browser instance
|
|
97
|
+
- Auto-start: Daemon starts automatically on first command
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- Node.js 20+
|
|
102
|
+
- Chromium (via Playwright)
|
|
103
|
+
- Vite dev server running
|
|
104
|
+
- Vue/React/Svelte app for framework-specific commands
|
|
105
|
+
|
|
106
|
+
## Documentation
|
|
107
|
+
|
|
108
|
+
See [SKILL.md](./skills/SKILL.md) for complete command reference and usage examples.
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare function open(url: string | undefined): Promise<void>;
|
|
2
|
+
export declare function cookies(cookies: {
|
|
3
|
+
name: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}[], domain: string): Promise<number>;
|
|
6
|
+
export declare function close(): Promise<void>;
|
|
7
|
+
export declare function goto(url: string): Promise<string>;
|
|
8
|
+
export declare function back(): Promise<void>;
|
|
9
|
+
export declare function reload(): Promise<string>;
|
|
10
|
+
export declare function detectFramework(): Promise<string>;
|
|
11
|
+
export declare function vueTree(id?: string): Promise<string>;
|
|
12
|
+
export declare function vuePinia(store?: string): Promise<string>;
|
|
13
|
+
export declare function vueRouter(): Promise<string>;
|
|
14
|
+
export declare function reactTree(id?: string): Promise<string>;
|
|
15
|
+
export declare function svelteTree(id?: string): Promise<string>;
|
|
16
|
+
export declare function viteRestart(): Promise<string>;
|
|
17
|
+
export declare function viteHMR(): Promise<string>;
|
|
18
|
+
export declare function errors(): Promise<string>;
|
|
19
|
+
export declare function logs(): Promise<string>;
|
|
20
|
+
export declare function screenshot(): Promise<string>;
|
|
21
|
+
export declare function evaluate(script: string): Promise<string>;
|
|
22
|
+
export declare function network(idx?: number): Promise<string>;
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { chromium } from "playwright";
|
|
5
|
+
import * as vueDevtools from "./vue/devtools.js";
|
|
6
|
+
import * as reactDevtools from "./react/devtools.js";
|
|
7
|
+
import * as svelteDevtools from "./svelte/devtools.js";
|
|
8
|
+
import * as networkLog from "./network.js";
|
|
9
|
+
const extensionPath = process.env.REACT_DEVTOOLS_EXTENSION ??
|
|
10
|
+
resolve(import.meta.dirname, "../../next-browser/extensions/react-devtools-chrome");
|
|
11
|
+
const hasReactExtension = existsSync(join(extensionPath, "manifest.json"));
|
|
12
|
+
const installHook = hasReactExtension
|
|
13
|
+
? readFileSync(join(extensionPath, "build", "installHook.js"), "utf-8")
|
|
14
|
+
: null;
|
|
15
|
+
let context = null;
|
|
16
|
+
let page = null;
|
|
17
|
+
let framework = "unknown";
|
|
18
|
+
const consoleLogs = [];
|
|
19
|
+
const MAX_LOGS = 200;
|
|
20
|
+
let lastReactSnapshot = [];
|
|
21
|
+
export async function open(url) {
|
|
22
|
+
if (!context) {
|
|
23
|
+
context = await launch();
|
|
24
|
+
page = context.pages()[0] ?? (await context.newPage());
|
|
25
|
+
attachListeners(page);
|
|
26
|
+
networkLog.attach(page);
|
|
27
|
+
}
|
|
28
|
+
const currentPage = page;
|
|
29
|
+
if (!currentPage)
|
|
30
|
+
throw new Error("browser not open");
|
|
31
|
+
if (url) {
|
|
32
|
+
await currentPage.goto(url, { waitUntil: "domcontentloaded" });
|
|
33
|
+
await detectFramework();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function cookies(cookies, domain) {
|
|
37
|
+
if (!context)
|
|
38
|
+
throw new Error("browser not open");
|
|
39
|
+
await context.addCookies(cookies.map((c) => ({ name: c.name, value: c.value, domain, path: "/" })));
|
|
40
|
+
return cookies.length;
|
|
41
|
+
}
|
|
42
|
+
export async function close() {
|
|
43
|
+
await context?.close();
|
|
44
|
+
context = null;
|
|
45
|
+
page = null;
|
|
46
|
+
framework = "unknown";
|
|
47
|
+
consoleLogs.length = 0;
|
|
48
|
+
networkLog.clear();
|
|
49
|
+
lastReactSnapshot = [];
|
|
50
|
+
}
|
|
51
|
+
async function launch() {
|
|
52
|
+
if (hasReactExtension && installHook) {
|
|
53
|
+
const ctx = await chromium.launchPersistentContext("", {
|
|
54
|
+
headless: false,
|
|
55
|
+
viewport: { width: 1280, height: 720 },
|
|
56
|
+
args: [
|
|
57
|
+
`--disable-extensions-except=${extensionPath}`,
|
|
58
|
+
`--load-extension=${extensionPath}`,
|
|
59
|
+
"--auto-open-devtools-for-tabs",
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
await ctx.waitForEvent("serviceworker").catch(() => { });
|
|
63
|
+
await ctx.addInitScript(installHook);
|
|
64
|
+
return ctx;
|
|
65
|
+
}
|
|
66
|
+
const browser = await chromium.launch({
|
|
67
|
+
headless: false,
|
|
68
|
+
args: ["--auto-open-devtools-for-tabs"],
|
|
69
|
+
});
|
|
70
|
+
return browser.newContext({ viewport: { width: 1280, height: 720 } });
|
|
71
|
+
}
|
|
72
|
+
function attachListeners(currentPage) {
|
|
73
|
+
currentPage.on("console", (msg) => {
|
|
74
|
+
const text = `[${msg.type()}] ${msg.text()}`;
|
|
75
|
+
consoleLogs.push(text);
|
|
76
|
+
if (consoleLogs.length > MAX_LOGS)
|
|
77
|
+
consoleLogs.shift();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export async function goto(url) {
|
|
81
|
+
if (!page)
|
|
82
|
+
throw new Error("browser not open");
|
|
83
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
84
|
+
await detectFramework();
|
|
85
|
+
return page.url();
|
|
86
|
+
}
|
|
87
|
+
export async function back() {
|
|
88
|
+
if (!page)
|
|
89
|
+
throw new Error("browser not open");
|
|
90
|
+
await page.goBack({ waitUntil: "domcontentloaded" });
|
|
91
|
+
}
|
|
92
|
+
export async function reload() {
|
|
93
|
+
if (!page)
|
|
94
|
+
throw new Error("browser not open");
|
|
95
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
96
|
+
return page.url();
|
|
97
|
+
}
|
|
98
|
+
export async function detectFramework() {
|
|
99
|
+
if (!page)
|
|
100
|
+
throw new Error("browser not open");
|
|
101
|
+
const detected = await page.evaluate(() => {
|
|
102
|
+
if (window.__VUE__ || window.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
|
|
103
|
+
const version = window.__VUE__?.version || "unknown";
|
|
104
|
+
return `vue@${version}`;
|
|
105
|
+
}
|
|
106
|
+
const reactHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
107
|
+
if (reactHook || window.React || document.querySelector("[data-reactroot]")) {
|
|
108
|
+
const renderers = reactHook?.renderers;
|
|
109
|
+
const firstRenderer = renderers ? renderers.values().next().value : null;
|
|
110
|
+
const version = firstRenderer?.version || window.React?.version || "unknown";
|
|
111
|
+
return `react@${version}`;
|
|
112
|
+
}
|
|
113
|
+
if (window.__SVELTE__ ||
|
|
114
|
+
window.__svelte ||
|
|
115
|
+
window.__SVELTE_DEVTOOLS_GLOBAL_HOOK__) {
|
|
116
|
+
const version = window.__SVELTE__?.VERSION ||
|
|
117
|
+
window.__svelte?.version ||
|
|
118
|
+
"unknown";
|
|
119
|
+
return `svelte@${version}`;
|
|
120
|
+
}
|
|
121
|
+
return "unknown";
|
|
122
|
+
});
|
|
123
|
+
if (detected.startsWith("vue"))
|
|
124
|
+
framework = "vue";
|
|
125
|
+
else if (detected.startsWith("react"))
|
|
126
|
+
framework = "react";
|
|
127
|
+
else if (detected.startsWith("svelte"))
|
|
128
|
+
framework = "svelte";
|
|
129
|
+
else
|
|
130
|
+
framework = "unknown";
|
|
131
|
+
return detected;
|
|
132
|
+
}
|
|
133
|
+
export async function vueTree(id) {
|
|
134
|
+
if (!page)
|
|
135
|
+
throw new Error("browser not open");
|
|
136
|
+
return id ? vueDevtools.getComponentDetails(page, id) : vueDevtools.getComponentTree(page);
|
|
137
|
+
}
|
|
138
|
+
export async function vuePinia(store) {
|
|
139
|
+
if (!page)
|
|
140
|
+
throw new Error("browser not open");
|
|
141
|
+
return vueDevtools.getPiniaStores(page, store);
|
|
142
|
+
}
|
|
143
|
+
export async function vueRouter() {
|
|
144
|
+
if (!page)
|
|
145
|
+
throw new Error("browser not open");
|
|
146
|
+
return vueDevtools.getRouterInfo(page);
|
|
147
|
+
}
|
|
148
|
+
export async function reactTree(id) {
|
|
149
|
+
if (!page)
|
|
150
|
+
throw new Error("browser not open");
|
|
151
|
+
if (!id) {
|
|
152
|
+
lastReactSnapshot = await reactDevtools.snapshot(page);
|
|
153
|
+
return reactDevtools.format(lastReactSnapshot);
|
|
154
|
+
}
|
|
155
|
+
const parsed = Number.parseInt(id, 10);
|
|
156
|
+
if (!Number.isFinite(parsed))
|
|
157
|
+
throw new Error("react component id must be a number");
|
|
158
|
+
const inspected = await reactDevtools.inspect(page, parsed);
|
|
159
|
+
const lines = [];
|
|
160
|
+
const componentPath = reactDevtools.path(lastReactSnapshot, parsed);
|
|
161
|
+
if (componentPath)
|
|
162
|
+
lines.push(`path: ${componentPath}`);
|
|
163
|
+
lines.push(inspected.text);
|
|
164
|
+
if (inspected.source) {
|
|
165
|
+
const [file, line, column] = inspected.source;
|
|
166
|
+
lines.push(`source: ${file}:${line}:${column}`);
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
export async function svelteTree(id) {
|
|
171
|
+
if (!page)
|
|
172
|
+
throw new Error("browser not open");
|
|
173
|
+
return id ? svelteDevtools.getComponentDetails(page, id) : svelteDevtools.getComponentTree(page);
|
|
174
|
+
}
|
|
175
|
+
export async function viteRestart() {
|
|
176
|
+
if (!page)
|
|
177
|
+
throw new Error("browser not open");
|
|
178
|
+
return page.evaluate(async () => {
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch("/__vite_restart", { method: "POST" });
|
|
181
|
+
return response.ok ? "restarted" : "restart endpoint not available";
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return "restart endpoint not available (install vite-browser plugin)";
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
export async function viteHMR() {
|
|
189
|
+
if (!page)
|
|
190
|
+
throw new Error("browser not open");
|
|
191
|
+
return page.evaluate(() => {
|
|
192
|
+
const updates = window.__vite_hmr_updates || [];
|
|
193
|
+
if (updates.length === 0)
|
|
194
|
+
return "No HMR updates";
|
|
195
|
+
return updates
|
|
196
|
+
.slice(-20)
|
|
197
|
+
.map((u) => `${new Date(u.timestamp).toLocaleTimeString()} - ${u.path}`)
|
|
198
|
+
.join("\n");
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
export async function errors() {
|
|
202
|
+
if (!page)
|
|
203
|
+
throw new Error("browser not open");
|
|
204
|
+
const errorInfo = await page.evaluate(() => {
|
|
205
|
+
const overlay = document.querySelector("vite-error-overlay");
|
|
206
|
+
if (!overlay || !overlay.shadowRoot)
|
|
207
|
+
return null;
|
|
208
|
+
const message = overlay.shadowRoot.querySelector(".message")?.textContent?.trim();
|
|
209
|
+
const stack = overlay.shadowRoot.querySelector(".stack")?.textContent?.trim();
|
|
210
|
+
return { message, stack };
|
|
211
|
+
});
|
|
212
|
+
if (!errorInfo)
|
|
213
|
+
return "no errors";
|
|
214
|
+
return `${errorInfo.message ?? "Vite error"}\n\n${errorInfo.stack ?? ""}`.trim();
|
|
215
|
+
}
|
|
216
|
+
export async function logs() {
|
|
217
|
+
if (consoleLogs.length === 0)
|
|
218
|
+
return "no logs";
|
|
219
|
+
return consoleLogs.slice(-50).join("\n");
|
|
220
|
+
}
|
|
221
|
+
export async function screenshot() {
|
|
222
|
+
if (!page)
|
|
223
|
+
throw new Error("browser not open");
|
|
224
|
+
const path = join(tmpdir(), `vite-browser-${Date.now()}.png`);
|
|
225
|
+
await page.screenshot({ path, fullPage: true });
|
|
226
|
+
return path;
|
|
227
|
+
}
|
|
228
|
+
export async function evaluate(script) {
|
|
229
|
+
if (!page)
|
|
230
|
+
throw new Error("browser not open");
|
|
231
|
+
const result = await page.evaluate(script);
|
|
232
|
+
return JSON.stringify(result, null, 2);
|
|
233
|
+
}
|
|
234
|
+
export async function network(idx) {
|
|
235
|
+
if (idx == null)
|
|
236
|
+
return networkLog.format();
|
|
237
|
+
return networkLog.detail(idx);
|
|
238
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { send } from "./client.js";
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
const cmd = args[0];
|
|
6
|
+
const arg = args[1];
|
|
7
|
+
if (cmd === "--help" || cmd === "-h" || !cmd) {
|
|
8
|
+
printUsage();
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
if (cmd === "open") {
|
|
12
|
+
if (!arg) {
|
|
13
|
+
console.error("usage: vite-browser open <url> [--cookies-json <file>]");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const cookieIdx = args.indexOf("--cookies-json");
|
|
17
|
+
const cookieFile = cookieIdx >= 0 ? args[cookieIdx + 1] : undefined;
|
|
18
|
+
if (cookieFile) {
|
|
19
|
+
const res = await send("open");
|
|
20
|
+
if (!res.ok)
|
|
21
|
+
exit(res, "");
|
|
22
|
+
const raw = readFileSync(cookieFile, "utf-8");
|
|
23
|
+
const cookies = JSON.parse(raw);
|
|
24
|
+
const domain = new URL(arg).hostname;
|
|
25
|
+
const cRes = await send("cookies", { cookies, domain });
|
|
26
|
+
if (!cRes.ok)
|
|
27
|
+
exit(cRes, "");
|
|
28
|
+
await send("goto", { url: arg });
|
|
29
|
+
exit(res, `opened -> ${arg} (${cookies.length} cookies for ${domain})`);
|
|
30
|
+
}
|
|
31
|
+
const res = await send("open", { url: arg });
|
|
32
|
+
exit(res, `opened -> ${arg}`);
|
|
33
|
+
}
|
|
34
|
+
if (cmd === "close") {
|
|
35
|
+
const res = await send("close");
|
|
36
|
+
exit(res, "closed");
|
|
37
|
+
}
|
|
38
|
+
if (cmd === "goto") {
|
|
39
|
+
if (!arg) {
|
|
40
|
+
console.error("usage: vite-browser goto <url>");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const res = await send("goto", { url: arg });
|
|
44
|
+
exit(res, res.ok ? `-> ${res.data}` : "");
|
|
45
|
+
}
|
|
46
|
+
if (cmd === "back") {
|
|
47
|
+
const res = await send("back");
|
|
48
|
+
exit(res, "back");
|
|
49
|
+
}
|
|
50
|
+
if (cmd === "reload") {
|
|
51
|
+
const res = await send("reload");
|
|
52
|
+
exit(res, res.ok ? `reloaded -> ${res.data}` : "");
|
|
53
|
+
}
|
|
54
|
+
if (cmd === "detect") {
|
|
55
|
+
const res = await send("detect");
|
|
56
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
57
|
+
}
|
|
58
|
+
if (cmd === "vue" && arg === "tree") {
|
|
59
|
+
const id = args[2];
|
|
60
|
+
const res = await send("vue-tree", { id });
|
|
61
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
62
|
+
}
|
|
63
|
+
if (cmd === "vue" && arg === "pinia") {
|
|
64
|
+
const store = args[2];
|
|
65
|
+
const res = await send("vue-pinia", { store });
|
|
66
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
67
|
+
}
|
|
68
|
+
if (cmd === "vue" && arg === "router") {
|
|
69
|
+
const res = await send("vue-router");
|
|
70
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
71
|
+
}
|
|
72
|
+
if (cmd === "react" && arg === "tree") {
|
|
73
|
+
const id = args[2];
|
|
74
|
+
const res = await send("react-tree", { id });
|
|
75
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
76
|
+
}
|
|
77
|
+
if (cmd === "svelte" && arg === "tree") {
|
|
78
|
+
const id = args[2];
|
|
79
|
+
const res = await send("svelte-tree", { id });
|
|
80
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
81
|
+
}
|
|
82
|
+
if (cmd === "vite" && arg === "restart") {
|
|
83
|
+
const res = await send("vite-restart");
|
|
84
|
+
exit(res, res.ok && res.data ? String(res.data) : "restarted");
|
|
85
|
+
}
|
|
86
|
+
if (cmd === "vite" && arg === "hmr") {
|
|
87
|
+
const res = await send("vite-hmr");
|
|
88
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
89
|
+
}
|
|
90
|
+
if (cmd === "errors") {
|
|
91
|
+
const res = await send("errors");
|
|
92
|
+
exit(res, res.ok && res.data ? String(res.data) : "no errors");
|
|
93
|
+
}
|
|
94
|
+
if (cmd === "logs") {
|
|
95
|
+
const res = await send("logs");
|
|
96
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
97
|
+
}
|
|
98
|
+
if (cmd === "screenshot") {
|
|
99
|
+
const res = await send("screenshot");
|
|
100
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
101
|
+
}
|
|
102
|
+
if (cmd === "eval") {
|
|
103
|
+
if (!arg) {
|
|
104
|
+
console.error("usage: vite-browser eval <script>");
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const res = await send("eval", { script: arg });
|
|
108
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
109
|
+
}
|
|
110
|
+
if (cmd === "network") {
|
|
111
|
+
const idx = arg ? parseInt(arg, 10) : undefined;
|
|
112
|
+
const res = await send("network", { idx });
|
|
113
|
+
exit(res, res.ok && res.data ? String(res.data) : "");
|
|
114
|
+
}
|
|
115
|
+
console.error(`unknown command: ${cmd}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
function exit(res, msg) {
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
console.error(res.error || "error");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
if (msg)
|
|
123
|
+
console.log(msg);
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
function printUsage() {
|
|
127
|
+
console.log(`
|
|
128
|
+
vite-browser - Programmatic access to Vue/React/Svelte DevTools and Vite dev server
|
|
129
|
+
|
|
130
|
+
USAGE
|
|
131
|
+
vite-browser <command> [options]
|
|
132
|
+
|
|
133
|
+
BROWSER CONTROL
|
|
134
|
+
open <url> [--cookies-json <file>] Launch browser and navigate
|
|
135
|
+
close Close browser and daemon
|
|
136
|
+
goto <url> Full-page navigation
|
|
137
|
+
back Go back in history
|
|
138
|
+
reload Reload current page
|
|
139
|
+
|
|
140
|
+
FRAMEWORK DETECTION
|
|
141
|
+
detect Detect framework (vue/react/svelte)
|
|
142
|
+
|
|
143
|
+
VUE COMMANDS
|
|
144
|
+
vue tree [id] Show Vue component tree or inspect component
|
|
145
|
+
vue pinia [store] Show Pinia stores or inspect specific store
|
|
146
|
+
vue router Show Vue Router information
|
|
147
|
+
|
|
148
|
+
REACT COMMANDS
|
|
149
|
+
react tree [id] Show React component tree or inspect component
|
|
150
|
+
|
|
151
|
+
SVELTE COMMANDS
|
|
152
|
+
svelte tree [id] Show Svelte component tree or inspect component
|
|
153
|
+
|
|
154
|
+
VITE COMMANDS
|
|
155
|
+
vite restart Restart Vite dev server
|
|
156
|
+
vite hmr Show HMR status
|
|
157
|
+
errors Show build/runtime errors
|
|
158
|
+
logs Show dev server logs
|
|
159
|
+
|
|
160
|
+
UTILITIES
|
|
161
|
+
screenshot Save screenshot to temp file
|
|
162
|
+
eval <script> Evaluate JavaScript in page context
|
|
163
|
+
network [idx] List network requests or inspect one
|
|
164
|
+
|
|
165
|
+
OPTIONS
|
|
166
|
+
-h, --help Show this help message
|
|
167
|
+
`);
|
|
168
|
+
}
|
package/dist/client.d.ts
ADDED
package/dist/client.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { connect as netConnect } from "node:net";
|
|
2
|
+
import { readFileSync, existsSync, rmSync } from "node:fs";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { socketPath, pidFile } from "./paths.js";
|
|
7
|
+
export async function send(action, payload = {}) {
|
|
8
|
+
await ensureDaemon();
|
|
9
|
+
const socket = await connect();
|
|
10
|
+
const id = String(Date.now());
|
|
11
|
+
socket.write(JSON.stringify({ id, action, ...payload }) + "\n");
|
|
12
|
+
const line = await readLine(socket);
|
|
13
|
+
socket.end();
|
|
14
|
+
return JSON.parse(line);
|
|
15
|
+
}
|
|
16
|
+
async function ensureDaemon() {
|
|
17
|
+
if (daemonAlive() && (await connect().then(ok, no)))
|
|
18
|
+
return;
|
|
19
|
+
const ext = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
20
|
+
const daemon = fileURLToPath(new URL(`./daemon${ext}`, import.meta.url));
|
|
21
|
+
const child = spawn(process.execPath, [daemon], {
|
|
22
|
+
detached: true,
|
|
23
|
+
stdio: "ignore",
|
|
24
|
+
});
|
|
25
|
+
child.unref();
|
|
26
|
+
for (let i = 0; i < 50; i++) {
|
|
27
|
+
if (await connect().then(ok, no))
|
|
28
|
+
return;
|
|
29
|
+
await sleep(100);
|
|
30
|
+
}
|
|
31
|
+
throw new Error(`daemon failed to start (${socketPath})`);
|
|
32
|
+
}
|
|
33
|
+
function daemonAlive() {
|
|
34
|
+
if (!existsSync(pidFile))
|
|
35
|
+
return false;
|
|
36
|
+
const pid = Number(readFileSync(pidFile, "utf-8"));
|
|
37
|
+
try {
|
|
38
|
+
process.kill(pid, 0);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
rmSync(pidFile, { force: true });
|
|
43
|
+
rmSync(socketPath, { force: true });
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function connect() {
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const socket = netConnect(socketPath);
|
|
50
|
+
socket.once("connect", () => resolve(socket));
|
|
51
|
+
socket.once("error", reject);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function readLine(socket) {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
let buffer = "";
|
|
57
|
+
socket.on("data", (chunk) => {
|
|
58
|
+
buffer += chunk;
|
|
59
|
+
const newline = buffer.indexOf("\n");
|
|
60
|
+
if (newline >= 0)
|
|
61
|
+
resolve(buffer.slice(0, newline));
|
|
62
|
+
});
|
|
63
|
+
socket.on("error", reject);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function ok(s) {
|
|
67
|
+
s.destroy();
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
function no() {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|