design-clone 2.1.0 → 2.3.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.
Files changed (177) hide show
  1. package/README.md +13 -34
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -171
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +9 -86
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +7 -14
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +2 -22
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +533 -286
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +14 -17
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/css/filter-css-dead-code.js +120 -0
  51. package/src/core/css/filter-css-html-analyzer.js +110 -0
  52. package/src/core/css/filter-css-selector-matcher.js +172 -0
  53. package/src/core/css/filter-css.js +206 -0
  54. package/src/core/css/merge-css-atrule-processor.js +158 -0
  55. package/src/core/css/merge-css-file-io.js +68 -0
  56. package/src/core/css/merge-css.js +148 -0
  57. package/src/core/detection/framework-detector-routing.js +68 -0
  58. package/src/core/detection/framework-detector-signals.js +65 -0
  59. package/src/core/detection/framework-detector.js +198 -0
  60. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  61. package/src/core/dimension/dimension-extractor.js +317 -0
  62. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  63. package/src/core/dimension/dimension-output.js +173 -0
  64. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  65. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  66. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  67. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  68. package/src/core/discovery/app-state-snapshot.js +131 -0
  69. package/src/core/discovery/discover-pages-routes.js +84 -0
  70. package/src/core/discovery/discover-pages-utils.js +177 -0
  71. package/src/core/discovery/discover-pages.js +191 -0
  72. package/src/core/html/html-extractor-inline-styler.js +70 -0
  73. package/src/core/html/html-extractor.js +147 -0
  74. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  75. package/src/core/html/semantic-enhancer-page.js +148 -0
  76. package/src/core/html/semantic-enhancer.js +135 -0
  77. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  78. package/src/core/links/rewrite-links.js +173 -0
  79. package/src/core/media/asset-validator.js +118 -0
  80. package/src/core/media/extract-assets-downloader.js +187 -0
  81. package/src/core/media/extract-assets-page-scraper.js +115 -0
  82. package/src/core/media/extract-assets.js +159 -0
  83. package/src/core/media/video-capture-convert.js +200 -0
  84. package/src/core/media/video-capture.js +201 -0
  85. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
  86. package/src/core/section/section-cropper-helpers.js +43 -0
  87. package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
  88. package/src/core/section/section-detector-strategies.js +139 -0
  89. package/src/core/section/section-detector-utils.js +100 -0
  90. package/src/core/section/section-detector.js +88 -0
  91. package/src/core/tests/test-section-cropper.js +2 -2
  92. package/src/core/tests/test-section-detector.js +2 -2
  93. package/src/post-process/enhance-assets.js +29 -4
  94. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  95. package/src/post-process/fetch-images.js +60 -263
  96. package/src/post-process/inject-gosnap.js +88 -0
  97. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  98. package/src/post-process/inject-icons.js +47 -200
  99. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  100. package/src/route-discoverers/base-discoverer.js +29 -118
  101. package/src/route-discoverers/index.js +1 -1
  102. package/src/shared/config.js +38 -0
  103. package/src/shared/error-codes.js +31 -0
  104. package/src/shared/viewports.js +46 -0
  105. package/src/utils/browser.js +0 -7
  106. package/src/utils/helpers.js +4 -0
  107. package/src/utils/log.js +12 -0
  108. package/src/utils/playwright-loader.js +76 -0
  109. package/src/utils/playwright.js +3 -69
  110. package/src/utils/progress.js +32 -0
  111. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  112. package/src/verification/generate-audit-report-sections.js +158 -0
  113. package/src/verification/generate-audit-report.js +5 -281
  114. package/src/verification/quality-scorer.js +92 -0
  115. package/src/verification/verify-footer-checks.js +103 -0
  116. package/src/verification/verify-footer-helpers.js +178 -0
  117. package/src/verification/verify-footer.js +23 -381
  118. package/src/verification/verify-header-checks.js +104 -0
  119. package/src/verification/verify-header-helpers.js +156 -0
  120. package/src/verification/verify-header.js +23 -365
  121. package/src/verification/verify-layout-report.js +101 -0
  122. package/src/verification/verify-layout.js +13 -259
  123. package/src/verification/verify-menu-checks.js +104 -0
  124. package/src/verification/verify-menu-helpers.js +112 -0
  125. package/src/verification/verify-menu.js +17 -285
  126. package/src/verification/verify-slider-checks.js +115 -0
  127. package/src/verification/verify-slider-constants.js +65 -0
  128. package/src/verification/verify-slider-helpers.js +164 -0
  129. package/src/verification/verify-slider.js +23 -414
  130. package/.env.example +0 -14
  131. package/docs/basic-clone.md +0 -63
  132. package/docs/cli-reference.md +0 -316
  133. package/docs/design-clone-architecture.md +0 -492
  134. package/docs/pixel-perfect.md +0 -117
  135. package/docs/project-roadmap.md +0 -382
  136. package/docs/troubleshooting.md +0 -170
  137. package/requirements.txt +0 -5
  138. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  139. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  140. package/src/ai/analyze-structure.py +0 -375
  141. package/src/ai/extract-design-tokens.py +0 -782
  142. package/src/ai/prompts/__init__.py +0 -2
  143. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  144. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  145. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  146. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  147. package/src/ai/prompts/design_tokens.py +0 -316
  148. package/src/ai/prompts/structure_analysis.py +0 -592
  149. package/src/ai/prompts/ux_audit.py +0 -198
  150. package/src/ai/ux-audit.js +0 -596
  151. package/src/core/animation-extractor.js +0 -526
  152. package/src/core/app-state-snapshot.js +0 -511
  153. package/src/core/content-counter.js +0 -342
  154. package/src/core/design-tokens.js +0 -103
  155. package/src/core/dimension-extractor.js +0 -438
  156. package/src/core/dimension-output.js +0 -305
  157. package/src/core/discover-pages.js +0 -542
  158. package/src/core/dom-tree-analyzer.js +0 -298
  159. package/src/core/extract-assets.js +0 -468
  160. package/src/core/filter-css.js +0 -499
  161. package/src/core/framework-detector.js +0 -538
  162. package/src/core/html-extractor.js +0 -212
  163. package/src/core/merge-css.js +0 -407
  164. package/src/core/multi-page-screenshot.js +0 -380
  165. package/src/core/rewrite-links.js +0 -226
  166. package/src/core/screenshot.js +0 -701
  167. package/src/core/section-detector.js +0 -386
  168. package/src/core/semantic-enhancer.js +0 -492
  169. package/src/core/state-capture.js +0 -598
  170. package/src/core/video-capture.js +0 -546
  171. package/src/utils/__init__.py +0 -16
  172. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  173. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  174. package/src/utils/env.py +0 -134
  175. /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
  176. /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
  177. /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
@@ -1,511 +0,0 @@
1
- /**
2
- * App State Snapshot Module
3
- *
4
- * Captures application state from SPAs including:
5
- * - Framework data (__NEXT_DATA__, __NUXT__)
6
- * - State management stores (Redux, Vuex, Pinia, Zustand)
7
- *
8
- * Features:
9
- * - Sensitive data filtering (tokens, passwords, secrets)
10
- * - Safe serialization (handles circular refs, functions, symbols)
11
- * - Size limit enforcement (1MB max)
12
- *
13
- * @module app-state-snapshot
14
- */
15
-
16
- // ============================================================================
17
- // Constants
18
- // ============================================================================
19
-
20
- /** Maximum state size in bytes (1MB) */
21
- const MAX_STATE_SIZE = 1024 * 1024;
22
-
23
- /** Maximum depth for recursive object traversal */
24
- const MAX_TRAVERSAL_DEPTH = 50;
25
-
26
- /** Patterns to identify sensitive keys */
27
- const SENSITIVE_PATTERNS = [
28
- /token/i,
29
- /password/i,
30
- /passwd/i,
31
- /secret/i,
32
- /auth/i,
33
- /api[_-]?key/i,
34
- /credential/i,
35
- /private/i,
36
- /session/i,
37
- /cookie/i,
38
- /bearer/i,
39
- /jwt/i,
40
- /access[_-]?key/i,
41
- /refresh[_-]?token/i
42
- ];
43
-
44
- /** Marker for filtered sensitive values */
45
- const FILTERED_MARKER = '[FILTERED]';
46
-
47
- /** Marker for circular references */
48
- const CIRCULAR_MARKER = '[Circular]';
49
-
50
- /** Marker for unserializable values */
51
- const UNSERIALIZABLE_MARKER = '[Unserializable]';
52
-
53
- // ============================================================================
54
- // Type Definitions (JSDoc)
55
- // ============================================================================
56
-
57
- /**
58
- * @typedef {Object} StateSnapshot
59
- * @property {Object|null} frameworkData - __NEXT_DATA__, __NUXT__, etc.
60
- * @property {Object|null} storeState - Redux/Vuex/Pinia/Zustand state
61
- * @property {string|null} framework - Detected framework name
62
- * @property {string} storeType - 'redux'|'vuex'|'pinia'|'zustand'|'none'
63
- * @property {string[]} warnings - Serialization/filtering warnings
64
- * @property {number} capturedAt - Unix timestamp
65
- * @property {number} sizeBytes - Serialized size in bytes
66
- */
67
-
68
- /**
69
- * @typedef {Object} StoreResult
70
- * @property {string} type - Store type identifier
71
- * @property {Object|null} state - Captured state object
72
- */
73
-
74
- // ============================================================================
75
- // Utility Functions
76
- // ============================================================================
77
-
78
- /**
79
- * Check if a key matches sensitive patterns
80
- * @param {string} key - Object key to check
81
- * @returns {boolean}
82
- */
83
- function isSensitiveKey(key) {
84
- if (typeof key !== 'string') return false;
85
- return SENSITIVE_PATTERNS.some(pattern => pattern.test(key));
86
- }
87
-
88
- /**
89
- * Filter sensitive keys from an object recursively
90
- * @param {*} obj - Object to filter
91
- * @param {string[]} warnings - Array to collect warnings
92
- * @param {string} path - Current path for warning messages
93
- * @param {number} depth - Current recursion depth
94
- * @returns {*} Filtered object
95
- */
96
- function filterSensitive(obj, warnings = [], path = '', depth = 0) {
97
- // Prevent infinite recursion
98
- if (depth > MAX_TRAVERSAL_DEPTH) {
99
- return '[Max Depth Exceeded]';
100
- }
101
-
102
- // Handle primitives
103
- if (obj === null || obj === undefined) return obj;
104
- if (typeof obj !== 'object') return obj;
105
-
106
- // Handle arrays
107
- if (Array.isArray(obj)) {
108
- return obj.map((item, i) =>
109
- filterSensitive(item, warnings, `${path}[${i}]`, depth + 1)
110
- );
111
- }
112
-
113
- // Handle objects
114
- const filtered = {};
115
- for (const [key, value] of Object.entries(obj)) {
116
- const fullPath = path ? `${path}.${key}` : key;
117
-
118
- // Check if key is sensitive
119
- if (isSensitiveKey(key)) {
120
- warnings.push(`Filtered sensitive key: ${fullPath}`);
121
- filtered[key] = FILTERED_MARKER;
122
- continue;
123
- }
124
-
125
- // Recursively filter nested objects
126
- filtered[key] = filterSensitive(value, warnings, fullPath, depth + 1);
127
- }
128
-
129
- return filtered;
130
- }
131
-
132
- /**
133
- * Safely serialize an object handling circular refs, functions, symbols
134
- * @param {*} obj - Object to serialize
135
- * @param {WeakSet} seen - Set of seen objects for circular detection
136
- * @param {number} depth - Current recursion depth
137
- * @returns {*} Serializable version of object
138
- */
139
- function safeSerialize(obj, seen = new WeakSet(), depth = 0) {
140
- // Prevent infinite recursion
141
- if (depth > MAX_TRAVERSAL_DEPTH) {
142
- return '[Max Depth Exceeded]';
143
- }
144
-
145
- // Handle primitives
146
- if (obj === null || obj === undefined) return obj;
147
- if (typeof obj === 'boolean' || typeof obj === 'number' || typeof obj === 'string') {
148
- return obj;
149
- }
150
-
151
- // Handle special types
152
- if (typeof obj === 'function') return '[Function]';
153
- if (typeof obj === 'symbol') return obj.toString();
154
- if (typeof obj === 'bigint') return obj.toString();
155
- if (obj instanceof Date) return obj.toISOString();
156
- if (obj instanceof RegExp) return obj.toString();
157
- if (obj instanceof Error) return { message: obj.message, name: obj.name };
158
- if (obj instanceof Map) return Object.fromEntries(obj);
159
- if (obj instanceof Set) return Array.from(obj);
160
-
161
- // Handle objects
162
- if (typeof obj === 'object') {
163
- // Circular reference check
164
- if (seen.has(obj)) return CIRCULAR_MARKER;
165
- seen.add(obj);
166
-
167
- try {
168
- if (Array.isArray(obj)) {
169
- return obj.map(item => safeSerialize(item, seen, depth + 1));
170
- }
171
-
172
- const result = {};
173
- for (const [key, value] of Object.entries(obj)) {
174
- try {
175
- result[key] = safeSerialize(value, seen, depth + 1);
176
- } catch {
177
- result[key] = UNSERIALIZABLE_MARKER;
178
- }
179
- }
180
- return result;
181
- } catch {
182
- return UNSERIALIZABLE_MARKER;
183
- }
184
- }
185
-
186
- return obj;
187
- }
188
-
189
- /**
190
- * Enforce state size limit
191
- * @param {StateSnapshot} snapshot - State snapshot to check
192
- * @param {string[]} warnings - Array to collect warnings
193
- * @returns {StateSnapshot} Possibly truncated snapshot
194
- */
195
- function enforceStateLimit(snapshot, warnings) {
196
- const serialized = JSON.stringify(snapshot);
197
- const sizeBytes = Buffer.byteLength(serialized, 'utf8');
198
-
199
- if (sizeBytes > MAX_STATE_SIZE) {
200
- const sizeMB = (sizeBytes / 1024 / 1024).toFixed(2);
201
- warnings.push(`State exceeded 1MB limit (${sizeMB}MB), store state truncated`);
202
-
203
- return {
204
- ...snapshot,
205
- storeState: {
206
- _truncated: true,
207
- _reason: `exceeded 1MB limit (${sizeMB}MB)`,
208
- _originalType: snapshot.storeType
209
- },
210
- sizeBytes: MAX_STATE_SIZE
211
- };
212
- }
213
-
214
- return { ...snapshot, sizeBytes };
215
- }
216
-
217
- // ============================================================================
218
- // Framework Data Capture
219
- // ============================================================================
220
-
221
- /**
222
- * Capture framework-specific data from page
223
- * @param {import('playwright').Page} page - Playwright page
224
- * @param {string|null} framework - Detected framework name
225
- * @returns {Promise<Object|null>}
226
- */
227
- async function captureFrameworkData(page, framework) {
228
- try {
229
- return await page.evaluate((fw) => {
230
- switch (fw) {
231
- case 'next':
232
- if (!window.__NEXT_DATA__) return null;
233
- return {
234
- props: window.__NEXT_DATA__.props,
235
- page: window.__NEXT_DATA__.page,
236
- query: window.__NEXT_DATA__.query,
237
- buildId: window.__NEXT_DATA__.buildId,
238
- runtimeConfig: window.__NEXT_DATA__.runtimeConfig,
239
- dynamicIds: window.__NEXT_DATA__.dynamicIds
240
- };
241
-
242
- case 'nuxt':
243
- if (!window.__NUXT__) return null;
244
- return {
245
- data: window.__NUXT__.data,
246
- state: window.__NUXT__.state,
247
- serverRendered: window.__NUXT__.serverRendered,
248
- routePath: window.__NUXT__.routePath,
249
- config: window.__NUXT__.config
250
- };
251
-
252
- case 'vue':
253
- // Vue 3 app data
254
- const vueApp = document.querySelector('[data-v-app]')?.__vue_app__;
255
- if (vueApp?.config?.globalProperties) {
256
- return {
257
- routePath: window.location.pathname,
258
- hasRouter: !!vueApp.config.globalProperties.$router,
259
- hasStore: !!vueApp.config.globalProperties.$store ||
260
- !!vueApp.config.globalProperties.$pinia
261
- };
262
- }
263
- return null;
264
-
265
- case 'react':
266
- // React doesn't have standard framework data
267
- // Return basic hydration info if available
268
- const reactRoot = document.getElementById('root') ||
269
- document.querySelector('[data-reactroot]');
270
- return reactRoot ? {
271
- hasReactRoot: true,
272
- rootId: reactRoot.id || null
273
- } : null;
274
-
275
- case 'angular':
276
- // Angular app state
277
- const appRoot = document.querySelector('app-root');
278
- if (appRoot && window.ng?.probe) {
279
- try {
280
- const component = window.ng.probe(appRoot);
281
- return {
282
- componentName: component?.componentInstance?.constructor?.name,
283
- hasRouter: !!component?.injector?.get?.('Router', null)
284
- };
285
- } catch {
286
- return { hasAppRoot: true };
287
- }
288
- }
289
- return appRoot ? { hasAppRoot: true } : null;
290
-
291
- case 'svelte':
292
- // SvelteKit data
293
- if (window.__sveltekit_data__) {
294
- return window.__sveltekit_data__;
295
- }
296
- return null;
297
-
298
- case 'astro':
299
- // Astro islands info
300
- const islands = document.querySelectorAll('astro-island');
301
- if (islands.length > 0) {
302
- return {
303
- islandCount: islands.length,
304
- componentNames: Array.from(islands)
305
- .map(i => i.getAttribute('component-export'))
306
- .filter(Boolean)
307
- };
308
- }
309
- return null;
310
-
311
- default:
312
- return null;
313
- }
314
- }, framework);
315
- } catch {
316
- return null;
317
- }
318
- }
319
-
320
- // ============================================================================
321
- // Store State Capture
322
- // ============================================================================
323
-
324
- /**
325
- * Capture state management store state
326
- * @param {import('playwright').Page} page - Playwright page
327
- * @returns {Promise<StoreResult>}
328
- */
329
- async function captureStoreState(page) {
330
- try {
331
- return await page.evaluate(() => {
332
- // Redux - check multiple detection methods
333
- // Method 1: Redux DevTools extension
334
- if (window.__REDUX_DEVTOOLS_EXTENSION__) {
335
- try {
336
- // Access store through devtools
337
- const stores = window.__REDUX_DEVTOOLS_EXTENSION__.stores ||
338
- window.__REDUX_DEVTOOLS_EXTENSION__.open?.() ||
339
- null;
340
- if (stores && typeof stores === 'object') {
341
- const storeKeys = Object.keys(stores);
342
- if (storeKeys.length > 0) {
343
- const store = stores[storeKeys[0]];
344
- if (store?.getState) {
345
- return { type: 'redux', state: store.getState() };
346
- }
347
- }
348
- }
349
- } catch {
350
- // Continue to other methods
351
- }
352
- }
353
-
354
- // Method 2: Direct store on window
355
- if (window.store?.getState) {
356
- return { type: 'redux', state: window.store.getState() };
357
- }
358
-
359
- // Method 3: __REDUX_STATE__ hydration
360
- if (window.__REDUX_STATE__) {
361
- return { type: 'redux', state: window.__REDUX_STATE__ };
362
- }
363
-
364
- // Vuex - Nuxt 2 / Vue 2/3
365
- if (window.$nuxt?.$store?.state) {
366
- return { type: 'vuex', state: window.$nuxt.$store.state };
367
- }
368
- if (window.__VUEX__?.state) {
369
- return { type: 'vuex', state: window.__VUEX__.state };
370
- }
371
-
372
- // Check Vue app for Vuex
373
- const vueApp = document.querySelector('[data-v-app]')?.__vue_app__;
374
- if (vueApp?.config?.globalProperties?.$store?.state) {
375
- return { type: 'vuex', state: vueApp.config.globalProperties.$store.state };
376
- }
377
-
378
- // Pinia - Nuxt 3 / Vue 3
379
- if (window.$nuxt?.$pinia?.state?.value) {
380
- return { type: 'pinia', state: window.$nuxt.$pinia.state.value };
381
- }
382
- if (window.__PINIA__?.state?.value) {
383
- return { type: 'pinia', state: window.__PINIA__.state.value };
384
- }
385
- if (vueApp?.config?.globalProperties?.$pinia?.state?.value) {
386
- return { type: 'pinia', state: vueApp.config.globalProperties.$pinia.state.value };
387
- }
388
-
389
- // Zustand - check common patterns
390
- // Zustand stores are typically named exports, check window for common names
391
- const zustandPatterns = ['useStore', 'useAppStore', 'useBearStore', 'store'];
392
- for (const pattern of zustandPatterns) {
393
- const potentialStore = window[pattern];
394
- if (potentialStore?.getState && typeof potentialStore.getState === 'function') {
395
- try {
396
- const state = potentialStore.getState();
397
- if (state && typeof state === 'object') {
398
- return { type: 'zustand', state };
399
- }
400
- } catch {
401
- // Not a valid Zustand store
402
- }
403
- }
404
- }
405
-
406
- // MobX - check for observable state
407
- if (window.__MOBX_STATE__) {
408
- return { type: 'mobx', state: window.__MOBX_STATE__ };
409
- }
410
-
411
- // No store found
412
- return { type: 'none', state: null };
413
- });
414
- } catch {
415
- return { type: 'none', state: null };
416
- }
417
- }
418
-
419
- // ============================================================================
420
- // Main Export
421
- // ============================================================================
422
-
423
- /**
424
- * Capture application state from page
425
- * @param {import('playwright').Page} page - Playwright page instance
426
- * @param {Object|null} [frameworkInfo] - Framework detection result
427
- * @returns {Promise<StateSnapshot>}
428
- */
429
- export async function captureAppState(page, frameworkInfo = null) {
430
- const warnings = [];
431
- const framework = frameworkInfo?.framework || null;
432
-
433
- // Initialize snapshot
434
- let snapshot = {
435
- frameworkData: null,
436
- storeState: null,
437
- framework,
438
- storeType: 'none',
439
- warnings,
440
- capturedAt: Date.now(),
441
- sizeBytes: 0
442
- };
443
-
444
- try {
445
- // Capture framework-specific data
446
- const rawFrameworkData = await captureFrameworkData(page, framework);
447
- if (rawFrameworkData) {
448
- const serialized = safeSerialize(rawFrameworkData);
449
- snapshot.frameworkData = filterSensitive(serialized, warnings);
450
- }
451
-
452
- // Capture store state
453
- const storeResult = await captureStoreState(page);
454
- if (storeResult.state) {
455
- const serialized = safeSerialize(storeResult.state);
456
- snapshot.storeState = filterSensitive(serialized, warnings);
457
- snapshot.storeType = storeResult.type;
458
- }
459
-
460
- // Enforce size limit
461
- snapshot = enforceStateLimit(snapshot, warnings);
462
-
463
- } catch (error) {
464
- warnings.push(`State capture error: ${error.message}`);
465
- }
466
-
467
- return snapshot;
468
- }
469
-
470
- /**
471
- * Format state snapshot for logging
472
- * @param {StateSnapshot} snapshot - Captured state
473
- * @returns {string}
474
- */
475
- export function formatStateSnapshot(snapshot) {
476
- const lines = [
477
- '\n=== App State Snapshot ===',
478
- `Framework: ${snapshot.framework || 'unknown'}`,
479
- `Store Type: ${snapshot.storeType}`,
480
- `Framework Data: ${snapshot.frameworkData ? 'captured' : 'none'}`,
481
- `Store State: ${snapshot.storeState ? 'captured' : 'none'}`,
482
- `Size: ${(snapshot.sizeBytes / 1024).toFixed(2)} KB`
483
- ];
484
-
485
- if (snapshot.warnings.length > 0) {
486
- lines.push(`Warnings (${snapshot.warnings.length}):`);
487
- snapshot.warnings.slice(0, 5).forEach(w => lines.push(` - ${w}`));
488
- if (snapshot.warnings.length > 5) {
489
- lines.push(` ... and ${snapshot.warnings.length - 5} more`);
490
- }
491
- }
492
-
493
- return lines.join('\n');
494
- }
495
-
496
- // ============================================================================
497
- // Exports for Testing
498
- // ============================================================================
499
-
500
- export {
501
- filterSensitive,
502
- safeSerialize,
503
- enforceStateLimit,
504
- isSensitiveKey,
505
- captureFrameworkData,
506
- captureStoreState,
507
- SENSITIVE_PATTERNS,
508
- MAX_STATE_SIZE,
509
- FILTERED_MARKER,
510
- CIRCULAR_MARKER
511
- };