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.
- package/README.md +13 -34
- package/SKILL.md +69 -45
- package/bin/cli.js +22 -4
- package/bin/commands/clone-site.js +31 -171
- package/bin/commands/help.js +19 -6
- package/bin/commands/init.js +9 -86
- package/bin/commands/uninstall.js +105 -0
- package/bin/commands/update.js +70 -0
- package/bin/commands/verify.js +7 -14
- package/bin/utils/paths.js +28 -0
- package/bin/utils/validate.js +2 -22
- package/bin/utils/version.js +23 -0
- package/docs/code-standards.md +789 -0
- package/docs/codebase-summary.md +533 -286
- package/docs/index.md +74 -0
- package/docs/project-overview-pdr.md +797 -0
- package/docs/system-architecture.md +718 -0
- package/package.json +14 -17
- package/src/ai/prompts/design-tokens/basic.md +80 -0
- package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
- package/src/ai/prompts/design-tokens/section.md +48 -0
- package/src/ai/prompts/design-tokens/with-css.md +87 -0
- package/src/ai/prompts/structure-analysis/basic.md +55 -0
- package/src/ai/prompts/structure-analysis/with-context.md +59 -0
- package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
- package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
- package/src/ai/prompts/ux-audit/aggregation.md +42 -0
- package/src/ai/prompts/ux-audit/desktop.md +92 -0
- package/src/ai/prompts/ux-audit/mobile.md +93 -0
- package/src/ai/prompts/ux-audit/tablet.md +92 -0
- package/src/core/animation/animation-extractor-ast.js +183 -0
- package/src/core/animation/animation-extractor-output.js +152 -0
- package/src/core/animation/animation-extractor.js +178 -0
- package/src/core/animation/state-capture-detection.js +200 -0
- package/src/core/animation/state-capture.js +193 -0
- package/src/core/capture/browser-context-pool.js +96 -0
- package/src/core/capture/multi-page-screenshot-page.js +110 -0
- package/src/core/capture/multi-page-screenshot.js +208 -0
- package/src/core/capture/screenshot-extraction.js +186 -0
- package/src/core/capture/screenshot-helpers.js +175 -0
- package/src/core/capture/screenshot-orchestrator.js +174 -0
- package/src/core/capture/screenshot-viewport.js +93 -0
- package/src/core/capture/screenshot.js +192 -0
- package/src/core/content/content-counter-dom.js +191 -0
- package/src/core/content/content-counter.js +76 -0
- package/src/core/css/breakpoint-detector.js +66 -0
- package/src/core/css/chromium-defaults.json +23 -0
- package/src/core/css/computed-style-extractor.js +102 -0
- package/src/core/css/css-chunker.js +103 -0
- package/src/core/css/filter-css-dead-code.js +120 -0
- package/src/core/css/filter-css-html-analyzer.js +110 -0
- package/src/core/css/filter-css-selector-matcher.js +172 -0
- package/src/core/css/filter-css.js +206 -0
- package/src/core/css/merge-css-atrule-processor.js +158 -0
- package/src/core/css/merge-css-file-io.js +68 -0
- package/src/core/css/merge-css.js +148 -0
- package/src/core/detection/framework-detector-routing.js +68 -0
- package/src/core/detection/framework-detector-signals.js +65 -0
- package/src/core/detection/framework-detector.js +198 -0
- package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
- package/src/core/dimension/dimension-extractor.js +317 -0
- package/src/core/dimension/dimension-output-ai-summary.js +111 -0
- package/src/core/dimension/dimension-output.js +173 -0
- package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
- package/src/core/dimension/dom-tree-analyzer.js +191 -0
- package/src/core/discovery/app-state-snapshot-capture.js +195 -0
- package/src/core/discovery/app-state-snapshot-utils.js +178 -0
- package/src/core/discovery/app-state-snapshot.js +131 -0
- package/src/core/discovery/discover-pages-routes.js +84 -0
- package/src/core/discovery/discover-pages-utils.js +177 -0
- package/src/core/discovery/discover-pages.js +191 -0
- package/src/core/html/html-extractor-inline-styler.js +70 -0
- package/src/core/html/html-extractor.js +147 -0
- package/src/core/html/semantic-enhancer-mappings.js +200 -0
- package/src/core/html/semantic-enhancer-page.js +148 -0
- package/src/core/html/semantic-enhancer.js +135 -0
- package/src/core/links/rewrite-links-css-rewriter.js +53 -0
- package/src/core/links/rewrite-links.js +173 -0
- package/src/core/media/asset-validator.js +118 -0
- package/src/core/media/extract-assets-downloader.js +187 -0
- package/src/core/media/extract-assets-page-scraper.js +115 -0
- package/src/core/media/extract-assets.js +159 -0
- package/src/core/media/video-capture-convert.js +200 -0
- package/src/core/media/video-capture.js +201 -0
- package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
- package/src/core/section/section-cropper-helpers.js +43 -0
- package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
- package/src/core/section/section-detector-strategies.js +139 -0
- package/src/core/section/section-detector-utils.js +100 -0
- package/src/core/section/section-detector.js +88 -0
- package/src/core/tests/test-section-cropper.js +2 -2
- package/src/core/tests/test-section-detector.js +2 -2
- package/src/post-process/enhance-assets.js +29 -4
- package/src/post-process/fetch-images-unsplash-client.js +123 -0
- package/src/post-process/fetch-images.js +60 -263
- package/src/post-process/inject-gosnap.js +88 -0
- package/src/post-process/inject-icons-svg-replacer.js +76 -0
- package/src/post-process/inject-icons.js +47 -200
- package/src/route-discoverers/base-discoverer-utils.js +137 -0
- package/src/route-discoverers/base-discoverer.js +29 -118
- package/src/route-discoverers/index.js +1 -1
- package/src/shared/config.js +38 -0
- package/src/shared/error-codes.js +31 -0
- package/src/shared/viewports.js +46 -0
- package/src/utils/browser.js +0 -7
- package/src/utils/helpers.js +4 -0
- package/src/utils/log.js +12 -0
- package/src/utils/playwright-loader.js +76 -0
- package/src/utils/playwright.js +3 -69
- package/src/utils/progress.js +32 -0
- package/src/verification/generate-audit-report-css-fixes.js +52 -0
- package/src/verification/generate-audit-report-sections.js +158 -0
- package/src/verification/generate-audit-report.js +5 -281
- package/src/verification/quality-scorer.js +92 -0
- package/src/verification/verify-footer-checks.js +103 -0
- package/src/verification/verify-footer-helpers.js +178 -0
- package/src/verification/verify-footer.js +23 -381
- package/src/verification/verify-header-checks.js +104 -0
- package/src/verification/verify-header-helpers.js +156 -0
- package/src/verification/verify-header.js +23 -365
- package/src/verification/verify-layout-report.js +101 -0
- package/src/verification/verify-layout.js +13 -259
- package/src/verification/verify-menu-checks.js +104 -0
- package/src/verification/verify-menu-helpers.js +112 -0
- package/src/verification/verify-menu.js +17 -285
- package/src/verification/verify-slider-checks.js +115 -0
- package/src/verification/verify-slider-constants.js +65 -0
- package/src/verification/verify-slider-helpers.js +164 -0
- package/src/verification/verify-slider.js +23 -414
- package/.env.example +0 -14
- package/docs/basic-clone.md +0 -63
- package/docs/cli-reference.md +0 -316
- package/docs/design-clone-architecture.md +0 -492
- package/docs/pixel-perfect.md +0 -117
- package/docs/project-roadmap.md +0 -382
- package/docs/troubleshooting.md +0 -170
- package/requirements.txt +0 -5
- package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
- package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
- package/src/ai/analyze-structure.py +0 -375
- package/src/ai/extract-design-tokens.py +0 -782
- package/src/ai/prompts/__init__.py +0 -2
- package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
- package/src/ai/prompts/design_tokens.py +0 -316
- package/src/ai/prompts/structure_analysis.py +0 -592
- package/src/ai/prompts/ux_audit.py +0 -198
- package/src/ai/ux-audit.js +0 -596
- package/src/core/animation-extractor.js +0 -526
- package/src/core/app-state-snapshot.js +0 -511
- package/src/core/content-counter.js +0 -342
- package/src/core/design-tokens.js +0 -103
- package/src/core/dimension-extractor.js +0 -438
- package/src/core/dimension-output.js +0 -305
- package/src/core/discover-pages.js +0 -542
- package/src/core/dom-tree-analyzer.js +0 -298
- package/src/core/extract-assets.js +0 -468
- package/src/core/filter-css.js +0 -499
- package/src/core/framework-detector.js +0 -538
- package/src/core/html-extractor.js +0 -212
- package/src/core/merge-css.js +0 -407
- package/src/core/multi-page-screenshot.js +0 -380
- package/src/core/rewrite-links.js +0 -226
- package/src/core/screenshot.js +0 -701
- package/src/core/section-detector.js +0 -386
- package/src/core/semantic-enhancer.js +0 -492
- package/src/core/state-capture.js +0 -598
- package/src/core/video-capture.js +0 -546
- package/src/utils/__init__.py +0 -16
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/env.py +0 -134
- /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
- /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
- /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
|
-
};
|