design-clone 1.2.0 → 2.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.
Files changed (66) hide show
  1. package/README.md +26 -12
  2. package/bin/commands/clone-site.js +75 -10
  3. package/bin/commands/init.js +33 -1
  4. package/bin/commands/verify.js +5 -3
  5. package/bin/utils/validate.js +24 -8
  6. package/docs/cli-reference.md +200 -2
  7. package/docs/codebase-summary.md +309 -0
  8. package/docs/design-clone-architecture.md +259 -42
  9. package/docs/pixel-perfect.md +35 -4
  10. package/docs/project-roadmap.md +382 -0
  11. package/docs/troubleshooting.md +5 -4
  12. package/package.json +10 -8
  13. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  14. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  15. package/src/ai/analyze-structure.py +73 -3
  16. package/src/ai/extract-design-tokens.py +356 -13
  17. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  18. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  19. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  20. package/src/ai/prompts/design_tokens.py +133 -0
  21. package/src/ai/prompts/structure_analysis.py +329 -10
  22. package/src/ai/prompts/ux_audit.py +198 -0
  23. package/src/ai/ux-audit.js +596 -0
  24. package/src/core/app-state-snapshot.js +511 -0
  25. package/src/core/content-counter.js +342 -0
  26. package/src/core/cookie-handler.js +1 -1
  27. package/src/core/css-extractor.js +4 -4
  28. package/src/core/dimension-extractor.js +93 -21
  29. package/src/core/dimension-output.js +103 -6
  30. package/src/core/discover-pages.js +242 -14
  31. package/src/core/dom-tree-analyzer.js +298 -0
  32. package/src/core/extract-assets.js +1 -1
  33. package/src/core/framework-detector.js +538 -0
  34. package/src/core/html-extractor.js +45 -4
  35. package/src/core/lazy-loader.js +7 -7
  36. package/src/core/multi-page-screenshot.js +9 -6
  37. package/src/core/page-readiness.js +8 -8
  38. package/src/core/screenshot.js +138 -9
  39. package/src/core/section-cropper.js +209 -0
  40. package/src/core/section-detector.js +386 -0
  41. package/src/core/semantic-enhancer.js +492 -0
  42. package/src/core/state-capture.js +18 -22
  43. package/src/core/tests/test-section-cropper.js +177 -0
  44. package/src/core/tests/test-section-detector.js +55 -0
  45. package/src/core/video-capture.js +152 -146
  46. package/src/route-discoverers/angular-discoverer.js +157 -0
  47. package/src/route-discoverers/astro-discoverer.js +123 -0
  48. package/src/route-discoverers/base-discoverer.js +242 -0
  49. package/src/route-discoverers/index.js +106 -0
  50. package/src/route-discoverers/next-discoverer.js +130 -0
  51. package/src/route-discoverers/nuxt-discoverer.js +138 -0
  52. package/src/route-discoverers/react-discoverer.js +139 -0
  53. package/src/route-discoverers/svelte-discoverer.js +109 -0
  54. package/src/route-discoverers/universal-discoverer.js +227 -0
  55. package/src/route-discoverers/vue-discoverer.js +118 -0
  56. package/src/utils/__init__.py +1 -1
  57. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  58. package/src/utils/browser.js +11 -37
  59. package/src/utils/playwright.js +213 -0
  60. package/src/verification/generate-audit-report.js +398 -0
  61. package/src/verification/verify-footer.js +493 -0
  62. package/src/verification/verify-header.js +486 -0
  63. package/src/verification/verify-layout.js +2 -2
  64. package/src/verification/verify-menu.js +4 -20
  65. package/src/verification/verify-slider.js +533 -0
  66. package/src/utils/puppeteer.js +0 -281
@@ -1,281 +0,0 @@
1
- /**
2
- * Standalone Puppeteer browser wrapper for design-clone scripts
3
- * Provides browser automation without requiring chrome-devtools skill
4
- *
5
- * Features:
6
- * - Auto-detects Chrome installation path (macOS, Linux, Windows)
7
- * - Session persistence via WebSocket endpoint file
8
- * - Graceful connect/disconnect lifecycle
9
- *
10
- * @note Session file may have race conditions in concurrent execution scenarios.
11
- * For production use with multiple parallel scripts, consider external lock.
12
- */
13
-
14
- import fs from 'fs';
15
- import path from 'path';
16
- import { fileURLToPath } from 'url';
17
-
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
-
20
- // Session file for browser reuse across script invocations
21
- const SESSION_FILE = path.join(__dirname, '..', '.browser-session.json');
22
- const SESSION_MAX_AGE = 3600000; // 1 hour
23
-
24
- let browserInstance = null;
25
- let pageInstance = null;
26
- let puppeteer = null;
27
-
28
- /**
29
- * Detect Chrome executable path by platform
30
- * @returns {string|null} Chrome path or null if not found
31
- */
32
- function detectChromePath() {
33
- const platform = process.platform;
34
-
35
- const paths = {
36
- darwin: [
37
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
38
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
39
- '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
40
- ],
41
- linux: [
42
- '/usr/bin/google-chrome',
43
- '/usr/bin/google-chrome-stable',
44
- '/usr/bin/chromium',
45
- '/usr/bin/chromium-browser',
46
- '/snap/bin/chromium'
47
- ],
48
- win32: [
49
- 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
50
- 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
51
- `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`
52
- ]
53
- };
54
-
55
- const candidates = paths[platform] || [];
56
- for (const chromePath of candidates) {
57
- if (fs.existsSync(chromePath)) {
58
- return chromePath;
59
- }
60
- }
61
-
62
- return null;
63
- }
64
-
65
- /**
66
- * Load puppeteer module (try puppeteer first, then puppeteer-core)
67
- * @returns {Promise<Object>} Puppeteer module
68
- * @throws {Error} If neither puppeteer nor puppeteer-core is installed
69
- */
70
- async function loadPuppeteer() {
71
- if (puppeteer) return puppeteer;
72
-
73
- try {
74
- // Try full puppeteer first (includes bundled Chrome)
75
- puppeteer = (await import('puppeteer')).default;
76
- return puppeteer;
77
- } catch (e1) {
78
- try {
79
- // Fall back to puppeteer-core (requires Chrome)
80
- puppeteer = (await import('puppeteer-core')).default;
81
- return puppeteer;
82
- } catch (e2) {
83
- throw new Error(
84
- 'Puppeteer not found. Install with: npm install puppeteer\n' +
85
- 'Or for smaller install: npm install puppeteer-core\n' +
86
- `Details: puppeteer: ${e1.message}, puppeteer-core: ${e2.message}`
87
- );
88
- }
89
- }
90
- }
91
-
92
- /**
93
- * Read browser session from file
94
- * @returns {Object|null} Session data with wsEndpoint and timestamp, or null if invalid/missing
95
- */
96
- function readSession() {
97
- try {
98
- if (fs.existsSync(SESSION_FILE)) {
99
- const data = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
100
- // Validate session age
101
- if (data.timestamp && Date.now() - data.timestamp < SESSION_MAX_AGE) {
102
- return data;
103
- }
104
- // Session expired, clean up
105
- clearSession();
106
- }
107
- } catch (err) {
108
- console.error(`[browser] Failed to read session: ${err.message}`);
109
- }
110
- return null;
111
- }
112
-
113
- /**
114
- * Write browser session to file with PID tracking
115
- * @param {string} wsEndpoint - WebSocket endpoint URL
116
- */
117
- function writeSession(wsEndpoint) {
118
- try {
119
- fs.writeFileSync(SESSION_FILE, JSON.stringify({
120
- wsEndpoint,
121
- timestamp: Date.now(),
122
- pid: process.pid
123
- }));
124
- } catch (err) {
125
- console.error(`[browser] Failed to write session: ${err.message}`);
126
- }
127
- }
128
-
129
- /**
130
- * Clear browser session file
131
- */
132
- function clearSession() {
133
- try {
134
- if (fs.existsSync(SESSION_FILE)) {
135
- fs.unlinkSync(SESSION_FILE);
136
- }
137
- } catch (err) {
138
- console.error(`[browser] Failed to clear session: ${err.message}`);
139
- }
140
- }
141
-
142
- /**
143
- * Launch or connect to browser instance
144
- * Reuses existing session if available and valid
145
- *
146
- * @param {Object} options - Browser options
147
- * @param {boolean} [options.headless=true] - Run in headless mode
148
- * @param {Object} [options.viewport] - Default viewport dimensions
149
- * @param {string} [options.executablePath] - Chrome executable path override
150
- * @param {string[]} [options.args] - Additional Chrome arguments
151
- * @returns {Promise<Browser>} Puppeteer browser instance
152
- * @throws {Error} If Chrome not found and no executablePath provided
153
- */
154
- export async function getBrowser(options = {}) {
155
- const pptr = await loadPuppeteer();
156
-
157
- // Reuse existing browser in this process
158
- if (browserInstance && browserInstance.isConnected()) {
159
- return browserInstance;
160
- }
161
-
162
- // Try to connect to existing browser from session
163
- const session = readSession();
164
- if (session?.wsEndpoint) {
165
- try {
166
- browserInstance = await pptr.connect({
167
- browserWSEndpoint: session.wsEndpoint
168
- });
169
- console.error('[browser] Connected to existing session');
170
- return browserInstance;
171
- } catch (err) {
172
- console.error(`[browser] Failed to connect to existing session: ${err.message}`);
173
- clearSession();
174
- }
175
- }
176
-
177
- // Determine executable path
178
- let executablePath = options.executablePath;
179
- if (!executablePath && pptr.executablePath) {
180
- try {
181
- // Full puppeteer has built-in Chrome
182
- executablePath = pptr.executablePath();
183
- } catch {
184
- // puppeteer-core needs manual path
185
- executablePath = detectChromePath();
186
- }
187
- }
188
-
189
- if (!executablePath) {
190
- throw new Error(
191
- 'Chrome not found. Either:\n' +
192
- '1. Install Google Chrome\n' +
193
- '2. Use full puppeteer (npm install puppeteer)\n' +
194
- '3. Set executablePath option'
195
- );
196
- }
197
-
198
- // Launch new browser
199
- const launchOptions = {
200
- headless: options.headless !== false,
201
- executablePath,
202
- args: [
203
- '--no-sandbox',
204
- '--disable-setuid-sandbox',
205
- '--disable-dev-shm-usage',
206
- '--disable-gpu',
207
- ...(options.args || [])
208
- ],
209
- defaultViewport: options.viewport || {
210
- width: 1920,
211
- height: 1080
212
- }
213
- };
214
-
215
- browserInstance = await pptr.launch(launchOptions);
216
-
217
- // Save session for reuse
218
- const wsEndpoint = browserInstance.wsEndpoint();
219
- writeSession(wsEndpoint);
220
- console.error('[browser] Launched new browser');
221
-
222
- return browserInstance;
223
- }
224
-
225
- /**
226
- * Get current page or create new one
227
- * Reuses existing page if available
228
- *
229
- * @param {Browser} browser - Puppeteer browser instance
230
- * @returns {Promise<Page>} Puppeteer page instance
231
- * @throws {Error} If browser is null or disconnected
232
- */
233
- export async function getPage(browser) {
234
- if (!browser || !browser.isConnected()) {
235
- throw new Error('Browser not connected. Call getBrowser() first.');
236
- }
237
-
238
- if (pageInstance && !pageInstance.isClosed()) {
239
- return pageInstance;
240
- }
241
-
242
- const pages = await browser.pages();
243
- pageInstance = pages.length > 0 ? pages[0] : await browser.newPage();
244
-
245
- return pageInstance;
246
- }
247
-
248
- /**
249
- * Close browser and clear session
250
- * Use when completely done with browser
251
- */
252
- export async function closeBrowser() {
253
- if (browserInstance) {
254
- try {
255
- await browserInstance.close();
256
- } catch (err) {
257
- console.error(`[browser] Error closing browser: ${err.message}`);
258
- }
259
- browserInstance = null;
260
- pageInstance = null;
261
- clearSession();
262
- console.error('[browser] Closed and cleared session');
263
- }
264
- }
265
-
266
- /**
267
- * Disconnect from browser without closing it
268
- * Use to keep browser running for future script executions
269
- */
270
- export async function disconnectBrowser() {
271
- if (browserInstance) {
272
- try {
273
- browserInstance.disconnect();
274
- } catch (err) {
275
- console.error(`[browser] Error disconnecting: ${err.message}`);
276
- }
277
- browserInstance = null;
278
- pageInstance = null;
279
- console.error('[browser] Disconnected (browser still running)');
280
- }
281
- }