@presto1314w/vite-devtools-browser 0.1.1 → 0.1.2

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/browser.js CHANGED
@@ -15,6 +15,7 @@ const installHook = hasReactExtension
15
15
  let context = null;
16
16
  let page = null;
17
17
  let framework = "unknown";
18
+ let extensionModeDisabled = false;
18
19
  const consoleLogs = [];
19
20
  const MAX_LOGS = 200;
20
21
  let lastReactSnapshot = [];
@@ -48,7 +49,17 @@ async function ensurePage() {
48
49
  if (!context)
49
50
  throw new Error("browser not open");
50
51
  if (!page || page.isClosed()) {
51
- page = context.pages()[0] ?? (await context.newPage());
52
+ try {
53
+ page = context.pages()[0] ?? (await context.newPage());
54
+ }
55
+ catch (error) {
56
+ if (!isClosedTargetError(error))
57
+ throw error;
58
+ await close();
59
+ extensionModeDisabled = true;
60
+ context = await launch();
61
+ page = context.pages()[0] ?? (await context.newPage());
62
+ }
52
63
  attachListeners(page);
53
64
  networkLog.attach(page);
54
65
  }
@@ -65,20 +76,30 @@ function contextUsable(current) {
65
76
  return false;
66
77
  }
67
78
  }
79
+ function isClosedTargetError(error) {
80
+ if (!(error instanceof Error))
81
+ return false;
82
+ return /Target page, context or browser has been closed/i.test(error.message);
83
+ }
68
84
  async function launch() {
69
- if (hasReactExtension && installHook) {
70
- const ctx = await chromium.launchPersistentContext("", {
71
- headless: false,
72
- viewport: { width: 1280, height: 720 },
73
- args: [
74
- `--disable-extensions-except=${extensionPath}`,
75
- `--load-extension=${extensionPath}`,
76
- "--auto-open-devtools-for-tabs",
77
- ],
78
- });
79
- await ctx.waitForEvent("serviceworker").catch(() => { });
80
- await ctx.addInitScript(installHook);
81
- return ctx;
85
+ if (hasReactExtension && installHook && !extensionModeDisabled) {
86
+ try {
87
+ const ctx = await chromium.launchPersistentContext("", {
88
+ headless: false,
89
+ viewport: { width: 1280, height: 720 },
90
+ args: [
91
+ `--disable-extensions-except=${extensionPath}`,
92
+ `--load-extension=${extensionPath}`,
93
+ "--auto-open-devtools-for-tabs",
94
+ ],
95
+ });
96
+ await ctx.waitForEvent("serviceworker").catch(() => { });
97
+ await ctx.addInitScript(installHook);
98
+ return ctx;
99
+ }
100
+ catch {
101
+ extensionModeDisabled = true;
102
+ }
82
103
  }
83
104
  const browser = await chromium.launch({
84
105
  headless: false,
@@ -176,12 +176,51 @@ export async function getComponentDetails(page, id) {
176
176
  */
177
177
  export async function getPiniaStores(page, storeName) {
178
178
  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
+ const hook = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
204
+ const piniaFromApp = hook?.apps?.[0]?.config?.globalProperties?.$pinia;
179
205
  // Try to find Pinia instance
180
- const pinia = window.__PINIA__ || window.pinia;
206
+ const pinia = window.__PINIA__ || window.pinia || piniaFromApp;
181
207
  if (!pinia)
182
208
  return "Pinia not found";
183
- const stores = pinia._s || pinia.state?.value || {};
184
- const storeKeys = Object.keys(stores);
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)]));
185
224
  if (storeKeys.length === 0)
186
225
  return "No Pinia stores found";
187
226
  const output = [];
@@ -195,29 +234,40 @@ export async function getPiniaStores(page, storeName) {
195
234
  return output.join("\n");
196
235
  }
197
236
  // Get specific store
198
- const store = stores[name];
199
- if (!store)
237
+ const store = storesById[name] ?? null;
238
+ const stateOnly = stateById[name];
239
+ if (!store && !stateOnly)
200
240
  return `Store '${name}' not found`;
201
241
  output.push(`# Pinia Store: ${name}\n`);
202
242
  // State
203
- const state = store.$state || store.state || store;
243
+ const state = store?.$state || store?.state || stateOnly || store;
204
244
  if (state && typeof state === 'object') {
205
245
  output.push("## State");
206
246
  for (const [key, value] of Object.entries(state)) {
207
247
  if (key.startsWith('$'))
208
248
  continue; // Skip Pinia internals
209
- output.push(` ${key}: ${JSON.stringify(value)}`);
249
+ output.push(` ${key}: ${safeJson(value)}`);
210
250
  }
211
251
  output.push("");
212
252
  }
213
253
  // Getters
214
- const getters = store._getters || {};
215
- if (Object.keys(getters).length > 0) {
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) {
216
266
  output.push("## Getters");
217
- for (const key of Object.keys(getters)) {
267
+ for (const key of getterNames) {
218
268
  try {
219
269
  const value = store[key];
220
- output.push(` ${key}: ${JSON.stringify(value)}`);
270
+ output.push(` ${key}: ${safeJson(value)}`);
221
271
  }
222
272
  catch {
223
273
  output.push(` ${key}: [Error]`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@presto1314w/vite-devtools-browser",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "CLI for programmatic access to Vue/React DevTools in Vite applications",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,7 +19,7 @@
19
19
  "dist"
20
20
  ],
21
21
  "bin": {
22
- "vite-browser": "./dist/cli.js"
22
+ "vite-browser": "dist/cli.js"
23
23
  },
24
24
  "scripts": {
25
25
  "start": "node --import tsx src/cli.ts",