@presto1314w/vite-devtools-browser 0.1.3 → 0.2.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.
@@ -3,6 +3,214 @@
3
3
  *
4
4
  * Uses @vue/devtools-kit to access Vue component tree and state
5
5
  */
6
+ export function formatComponentTree(apps) {
7
+ if (apps.length === 0)
8
+ return "No Vue apps found";
9
+ const output = [];
10
+ output.push("# Vue Component Tree");
11
+ output.push(`# ${apps.length} app(s) detected\n`);
12
+ apps.forEach((app, appIndex) => {
13
+ const appName = app._component?.name || app.config?.globalProperties?.$options?.name || `App ${appIndex}`;
14
+ output.push(`## App: ${appName}`);
15
+ const rootInstance = app._instance || app._container?._vnode?.component;
16
+ if (!rootInstance) {
17
+ output.push(" (no root instance)");
18
+ return;
19
+ }
20
+ const seen = new WeakSet();
21
+ const visit = (instance, depth = 0) => {
22
+ if (!instance || typeof instance !== "object")
23
+ return;
24
+ if (seen.has(instance))
25
+ return;
26
+ seen.add(instance);
27
+ const indent = " ".repeat(depth);
28
+ const name = instance.type?.name || instance.type?.__name || "Anonymous";
29
+ const uid = instance.uid ?? "?";
30
+ output.push(`${indent}[${uid}] ${name}`);
31
+ const subTree = instance.subTree;
32
+ if (subTree?.component)
33
+ visit(subTree.component, depth + 1);
34
+ if (Array.isArray(subTree?.children)) {
35
+ subTree.children.forEach((child) => {
36
+ if (child?.component)
37
+ visit(child.component, depth + 1);
38
+ });
39
+ }
40
+ if (Array.isArray(instance.children)) {
41
+ instance.children.forEach((child) => visit(child, depth + 1));
42
+ }
43
+ };
44
+ visit(rootInstance, 1);
45
+ output.push("");
46
+ });
47
+ return output.join("\n");
48
+ }
49
+ export function formatComponentDetails(targetInstance, componentId) {
50
+ if (!targetInstance)
51
+ return `Component ${componentId} not found`;
52
+ const output = [];
53
+ const name = targetInstance.type?.name || targetInstance.type?.__name || "Anonymous";
54
+ output.push(`# Component: ${name}`);
55
+ output.push(`# UID: ${targetInstance.uid}\n`);
56
+ if (targetInstance.props && Object.keys(targetInstance.props).length > 0) {
57
+ output.push("## Props");
58
+ for (const [key, value] of Object.entries(targetInstance.props)) {
59
+ output.push(` ${key}: ${JSON.stringify(value)}`);
60
+ }
61
+ output.push("");
62
+ }
63
+ if (targetInstance.data && Object.keys(targetInstance.data).length > 0) {
64
+ output.push("## Data");
65
+ for (const [key, value] of Object.entries(targetInstance.data)) {
66
+ output.push(` ${key}: ${JSON.stringify(value)}`);
67
+ }
68
+ output.push("");
69
+ }
70
+ const setupState = targetInstance.setupState || targetInstance.devtoolsRawSetupState;
71
+ if (setupState && Object.keys(setupState).length > 0) {
72
+ output.push("## Setup State");
73
+ for (const [key, value] of Object.entries(setupState)) {
74
+ output.push(` ${key}: ${typeof value === "function" ? "[Function]" : JSON.stringify(value)}`);
75
+ }
76
+ output.push("");
77
+ }
78
+ const computed = targetInstance.type?.computed;
79
+ if (computed && Object.keys(computed).length > 0) {
80
+ output.push("## Computed");
81
+ for (const key of Object.keys(computed)) {
82
+ try {
83
+ const value = targetInstance.proxy?.[key];
84
+ output.push(` ${key}: ${JSON.stringify(value)}`);
85
+ }
86
+ catch {
87
+ output.push(` ${key}: [Error]`);
88
+ }
89
+ }
90
+ output.push("");
91
+ }
92
+ const file = targetInstance.type?.__file;
93
+ if (file) {
94
+ output.push("## Source");
95
+ output.push(` ${file}`);
96
+ }
97
+ return output.join("\n");
98
+ }
99
+ export function formatPiniaStores(pinia, storeName, piniaFromWindow = true) {
100
+ if (!pinia && piniaFromWindow)
101
+ return "Pinia not found";
102
+ const safeJson = (value) => {
103
+ if (typeof value === "function")
104
+ return "[Function]";
105
+ if (typeof value === "bigint")
106
+ return value.toString();
107
+ const seen = new WeakSet();
108
+ try {
109
+ return JSON.stringify(value, (_, v) => {
110
+ if (typeof v === "function")
111
+ return "[Function]";
112
+ if (typeof v === "bigint")
113
+ return v.toString();
114
+ if (v && typeof v === "object") {
115
+ if (seen.has(v))
116
+ return "[Circular]";
117
+ seen.add(v);
118
+ }
119
+ return v;
120
+ });
121
+ }
122
+ catch {
123
+ return String(value);
124
+ }
125
+ };
126
+ const storesById = {};
127
+ const registry = pinia?._s;
128
+ if (registry instanceof Map) {
129
+ registry.forEach((store, id) => {
130
+ storesById[String(id)] = store;
131
+ });
132
+ }
133
+ else if (registry && typeof registry === "object") {
134
+ for (const [id, store] of Object.entries(registry)) {
135
+ storesById[id] = store;
136
+ }
137
+ }
138
+ const stateById = pinia?.state?.value && typeof pinia.state.value === "object" ? pinia.state.value : {};
139
+ const storeKeys = Array.from(new Set([...Object.keys(storesById), ...Object.keys(stateById)]));
140
+ if (storeKeys.length === 0)
141
+ return "No Pinia stores found";
142
+ const output = [];
143
+ if (!storeName) {
144
+ output.push("# Pinia Stores\n");
145
+ storeKeys.forEach((key) => output.push(`- ${key}`));
146
+ output.push("\nUse 'vite-browser vue pinia <store-name>' to inspect a specific store");
147
+ return output.join("\n");
148
+ }
149
+ const store = storesById[storeName] ?? null;
150
+ const stateOnly = stateById[storeName];
151
+ if (!store && !stateOnly)
152
+ return `Store '${storeName}' not found`;
153
+ output.push(`# Pinia Store: ${storeName}\n`);
154
+ const state = store?.$state || store?.state || stateOnly || store;
155
+ if (state && typeof state === "object") {
156
+ output.push("## State");
157
+ for (const [key, value] of Object.entries(state)) {
158
+ if (key.startsWith("$"))
159
+ continue;
160
+ output.push(` ${key}: ${safeJson(value)}`);
161
+ }
162
+ output.push("");
163
+ }
164
+ const getterNames = [];
165
+ const rawGetters = store?._getters;
166
+ if (Array.isArray(rawGetters))
167
+ getterNames.push(...rawGetters.map((g) => String(g)));
168
+ else if (rawGetters instanceof Set)
169
+ getterNames.push(...Array.from(rawGetters).map(String));
170
+ else if (rawGetters && typeof rawGetters === "object")
171
+ getterNames.push(...Object.keys(rawGetters));
172
+ if (getterNames.length > 0) {
173
+ output.push("## Getters");
174
+ for (const key of getterNames) {
175
+ try {
176
+ output.push(` ${key}: ${safeJson(store[key])}`);
177
+ }
178
+ catch {
179
+ output.push(` ${key}: [Error]`);
180
+ }
181
+ }
182
+ output.push("");
183
+ }
184
+ return output.join("\n");
185
+ }
186
+ export function formatRouterInfo(actualRouter) {
187
+ if (!actualRouter)
188
+ return "Vue Router not found";
189
+ const output = [];
190
+ output.push("# Vue Router\n");
191
+ const currentRoute = actualRouter.currentRoute?.value || actualRouter.currentRoute;
192
+ if (currentRoute) {
193
+ output.push("## Current Route");
194
+ output.push(` Path: ${currentRoute.path}`);
195
+ output.push(` Name: ${currentRoute.name || "(unnamed)"}`);
196
+ if (currentRoute.params && Object.keys(currentRoute.params).length > 0) {
197
+ output.push(` Params: ${JSON.stringify(currentRoute.params)}`);
198
+ }
199
+ if (currentRoute.query && Object.keys(currentRoute.query).length > 0) {
200
+ output.push(` Query: ${JSON.stringify(currentRoute.query)}`);
201
+ }
202
+ output.push("");
203
+ }
204
+ const routes = actualRouter.getRoutes?.() || actualRouter.options?.routes || [];
205
+ if (routes.length > 0) {
206
+ output.push("## All Routes");
207
+ routes.forEach((route) => {
208
+ const routeName = route.name ? ` (${route.name})` : "";
209
+ output.push(` ${route.path}${routeName}`);
210
+ });
211
+ }
212
+ return output.join("\n");
213
+ }
6
214
  /**
7
215
  * Get Vue component tree from the page
8
216
  */
@@ -12,54 +220,9 @@ export async function getComponentTree(page) {
12
220
  const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
13
221
  if (!hook)
14
222
  return "Vue DevTools not found";
15
- const apps = hook.apps || [];
16
- if (apps.length === 0)
17
- return "No Vue apps found";
18
- const output = [];
19
- output.push("# Vue Component Tree");
20
- output.push(`# ${apps.length} app(s) detected\n`);
21
- apps.forEach((app, appIndex) => {
22
- const appName = app._component?.name || app.config?.globalProperties?.$options?.name || `App ${appIndex}`;
23
- output.push(`## App: ${appName}`);
24
- // Get root instance
25
- const rootInstance = app._instance || app._container?._vnode?.component;
26
- if (!rootInstance) {
27
- output.push(" (no root instance)");
28
- return;
29
- }
30
- // Traverse component tree
31
- const traverse = (instance, depth = 0) => {
32
- if (!instance)
33
- return;
34
- const indent = " ".repeat(depth);
35
- const name = instance.type?.name || instance.type?.__name || "Anonymous";
36
- const uid = instance.uid ?? "?";
37
- output.push(`${indent}[${uid}] ${name}`);
38
- // Traverse children
39
- const subTree = instance.subTree;
40
- if (subTree?.component) {
41
- traverse(subTree.component, depth + 1);
42
- }
43
- if (subTree?.children) {
44
- subTree.children.forEach((child) => {
45
- if (child?.component) {
46
- traverse(child.component, depth + 1);
47
- }
48
- });
49
- }
50
- // Traverse direct children
51
- if (instance.children) {
52
- instance.children.forEach((child) => {
53
- traverse(child, depth + 1);
54
- });
55
- }
56
- };
57
- traverse(rootInstance, 1);
58
- output.push("");
59
- });
60
- return output.join("\n");
223
+ return hook.apps || [];
61
224
  });
62
- return result;
225
+ return formatComponentTree(result);
63
226
  }
64
227
  /**
65
228
  * Get component details by ID
@@ -110,218 +273,37 @@ export async function getComponentDetails(page, id) {
110
273
  if (targetInstance)
111
274
  break;
112
275
  }
113
- if (!targetInstance)
114
- return `Component ${componentId} not found`;
115
- const output = [];
116
- const name = targetInstance.type?.name || targetInstance.type?.__name || "Anonymous";
117
- output.push(`# Component: ${name}`);
118
- output.push(`# UID: ${targetInstance.uid}\n`);
119
- // Props
120
- if (targetInstance.props && Object.keys(targetInstance.props).length > 0) {
121
- output.push("## Props");
122
- for (const [key, value] of Object.entries(targetInstance.props)) {
123
- output.push(` ${key}: ${JSON.stringify(value)}`);
124
- }
125
- output.push("");
126
- }
127
- // Data
128
- if (targetInstance.data && Object.keys(targetInstance.data).length > 0) {
129
- output.push("## Data");
130
- for (const [key, value] of Object.entries(targetInstance.data)) {
131
- output.push(` ${key}: ${JSON.stringify(value)}`);
132
- }
133
- output.push("");
134
- }
135
- // Setup state (Composition API)
136
- const setupState = targetInstance.setupState || targetInstance.devtoolsRawSetupState;
137
- if (setupState && Object.keys(setupState).length > 0) {
138
- output.push("## Setup State");
139
- for (const [key, value] of Object.entries(setupState)) {
140
- if (typeof value === 'function') {
141
- output.push(` ${key}: [Function]`);
142
- }
143
- else {
144
- output.push(` ${key}: ${JSON.stringify(value)}`);
145
- }
146
- }
147
- output.push("");
148
- }
149
- // Computed
150
- const computed = targetInstance.type?.computed;
151
- if (computed && Object.keys(computed).length > 0) {
152
- output.push("## Computed");
153
- for (const key of Object.keys(computed)) {
154
- try {
155
- const value = targetInstance.proxy?.[key];
156
- output.push(` ${key}: ${JSON.stringify(value)}`);
157
- }
158
- catch {
159
- output.push(` ${key}: [Error]`);
160
- }
161
- }
162
- output.push("");
163
- }
164
- // File location
165
- const file = targetInstance.type?.__file;
166
- if (file) {
167
- output.push(`## Source`);
168
- output.push(` ${file}`);
169
- }
170
- return output.join("\n");
276
+ return targetInstance;
171
277
  }, id);
172
- return result;
278
+ if (typeof result === "string")
279
+ return result;
280
+ return formatComponentDetails(result, id);
173
281
  }
174
282
  /**
175
283
  * Get Pinia stores
176
284
  */
177
285
  export async function getPiniaStores(page, storeName) {
178
286
  const result = await page.evaluate((name) => {
179
- const safeJson = (value) => {
180
- if (typeof value === "function")
181
- return "[Function]";
182
- if (typeof value === "bigint")
183
- return value.toString();
184
- const seen = new WeakSet();
185
- try {
186
- return JSON.stringify(value, (_, v) => {
187
- if (typeof v === "function")
188
- return "[Function]";
189
- if (typeof v === "bigint")
190
- return v.toString();
191
- if (v && typeof v === "object") {
192
- if (seen.has(v))
193
- return "[Circular]";
194
- seen.add(v);
195
- }
196
- return v;
197
- });
198
- }
199
- catch {
200
- return String(value);
201
- }
202
- };
203
287
  const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
204
288
  const piniaFromApp = hook?.apps?.[0]?.config?.globalProperties?.$pinia;
205
- // Try to find Pinia instance
206
289
  const pinia = window.__PINIA__ || window.pinia || piniaFromApp;
207
- if (!pinia)
208
- return "Pinia not found";
209
- // Pinia v3 uses Map for _s, older integrations can expose plain objects.
210
- const storesById = {};
211
- const registry = pinia._s;
212
- if (registry instanceof Map) {
213
- registry.forEach((store, id) => {
214
- storesById[String(id)] = store;
215
- });
216
- }
217
- else if (registry && typeof registry === "object") {
218
- for (const [id, store] of Object.entries(registry)) {
219
- storesById[id] = store;
220
- }
221
- }
222
- const stateById = pinia.state?.value && typeof pinia.state.value === "object" ? pinia.state.value : {};
223
- const storeKeys = Array.from(new Set([...Object.keys(storesById), ...Object.keys(stateById)]));
224
- if (storeKeys.length === 0)
225
- return "No Pinia stores found";
226
- const output = [];
227
- if (!name) {
228
- // List all stores
229
- output.push("# Pinia Stores\n");
230
- storeKeys.forEach((key) => {
231
- output.push(`- ${key}`);
232
- });
233
- output.push("\nUse 'vite-browser vue pinia <store-name>' to inspect a specific store");
234
- return output.join("\n");
235
- }
236
- // Get specific store
237
- const store = storesById[name] ?? null;
238
- const stateOnly = stateById[name];
239
- if (!store && !stateOnly)
240
- return `Store '${name}' not found`;
241
- output.push(`# Pinia Store: ${name}\n`);
242
- // State
243
- const state = store?.$state || store?.state || stateOnly || store;
244
- if (state && typeof state === 'object') {
245
- output.push("## State");
246
- for (const [key, value] of Object.entries(state)) {
247
- if (key.startsWith('$'))
248
- continue; // Skip Pinia internals
249
- output.push(` ${key}: ${safeJson(value)}`);
250
- }
251
- output.push("");
252
- }
253
- // Getters
254
- const getterNames = [];
255
- const rawGetters = store?._getters;
256
- if (Array.isArray(rawGetters)) {
257
- getterNames.push(...rawGetters.map((g) => String(g)));
258
- }
259
- else if (rawGetters instanceof Set) {
260
- getterNames.push(...Array.from(rawGetters).map((g) => String(g)));
261
- }
262
- else if (rawGetters && typeof rawGetters === "object") {
263
- getterNames.push(...Object.keys(rawGetters));
264
- }
265
- if (getterNames.length > 0) {
266
- output.push("## Getters");
267
- for (const key of getterNames) {
268
- try {
269
- const value = store[key];
270
- output.push(` ${key}: ${safeJson(value)}`);
271
- }
272
- catch {
273
- output.push(` ${key}: [Error]`);
274
- }
275
- }
276
- output.push("");
277
- }
278
- return output.join("\n");
290
+ return pinia || null;
279
291
  }, storeName);
280
- return result;
292
+ return formatPiniaStores(result, storeName);
281
293
  }
282
294
  /**
283
295
  * Get Vue Router information
284
296
  */
285
297
  export async function getRouterInfo(page) {
286
298
  const result = await page.evaluate(() => {
287
- // Try to find Vue Router instance
288
299
  const router = window.$router || window.__VUE_ROUTER__;
289
- // Also try to get from Vue app
290
300
  const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
291
301
  let routerFromApp = null;
292
302
  if (hook?.apps?.[0]) {
293
303
  const app = hook.apps[0];
294
304
  routerFromApp = app.config?.globalProperties?.$router;
295
305
  }
296
- const actualRouter = router || routerFromApp;
297
- if (!actualRouter)
298
- return "Vue Router not found";
299
- const output = [];
300
- output.push("# Vue Router\n");
301
- // Current route
302
- const currentRoute = actualRouter.currentRoute?.value || actualRouter.currentRoute;
303
- if (currentRoute) {
304
- output.push("## Current Route");
305
- output.push(` Path: ${currentRoute.path}`);
306
- output.push(` Name: ${currentRoute.name || "(unnamed)"}`);
307
- if (currentRoute.params && Object.keys(currentRoute.params).length > 0) {
308
- output.push(` Params: ${JSON.stringify(currentRoute.params)}`);
309
- }
310
- if (currentRoute.query && Object.keys(currentRoute.query).length > 0) {
311
- output.push(` Query: ${JSON.stringify(currentRoute.query)}`);
312
- }
313
- output.push("");
314
- }
315
- // All routes
316
- const routes = actualRouter.getRoutes?.() || actualRouter.options?.routes || [];
317
- if (routes.length > 0) {
318
- output.push("## All Routes");
319
- routes.forEach((route) => {
320
- const name = route.name ? ` (${route.name})` : "";
321
- output.push(` ${route.path}${name}`);
322
- });
323
- }
324
- return output.join("\n");
306
+ return router || routerFromApp || null;
325
307
  });
326
- return result;
308
+ return formatRouterInfo(result);
327
309
  }
package/package.json CHANGED
@@ -1,8 +1,22 @@
1
1
  {
2
2
  "name": "@presto1314w/vite-devtools-browser",
3
- "version": "0.1.3",
4
- "description": "CLI for programmatic access to Vue/React DevTools in Vite applications",
5
- "license": "MIT",
3
+ "version": "0.2.0",
4
+ "description": "Runtime diagnostics CLI for Vite apps with event-stream correlation, HMR diagnosis, framework inspection, and mapped errors",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "vite",
8
+ "devtools",
9
+ "debugging",
10
+ "runtime-diagnostics",
11
+ "hmr",
12
+ "module-graph",
13
+ "sourcemap",
14
+ "vue",
15
+ "react",
16
+ "svelte",
17
+ "cli",
18
+ "ai-agents"
19
+ ],
6
20
  "repository": {
7
21
  "type": "git",
8
22
  "url": "git+https://github.com/MapleCity1314/vite-browser.git"