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.
- package/README.md +26 -12
- package/bin/commands/clone-site.js +75 -10
- package/bin/commands/init.js +33 -1
- package/bin/commands/verify.js +5 -3
- package/bin/utils/validate.js +24 -8
- package/docs/cli-reference.md +200 -2
- package/docs/codebase-summary.md +309 -0
- package/docs/design-clone-architecture.md +259 -42
- package/docs/pixel-perfect.md +35 -4
- package/docs/project-roadmap.md +382 -0
- package/docs/troubleshooting.md +5 -4
- package/package.json +10 -8
- 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 +73 -3
- package/src/ai/extract-design-tokens.py +356 -13
- 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 +133 -0
- package/src/ai/prompts/structure_analysis.py +329 -10
- package/src/ai/prompts/ux_audit.py +198 -0
- package/src/ai/ux-audit.js +596 -0
- package/src/core/app-state-snapshot.js +511 -0
- package/src/core/content-counter.js +342 -0
- package/src/core/cookie-handler.js +1 -1
- package/src/core/css-extractor.js +4 -4
- package/src/core/dimension-extractor.js +93 -21
- package/src/core/dimension-output.js +103 -6
- package/src/core/discover-pages.js +242 -14
- package/src/core/dom-tree-analyzer.js +298 -0
- package/src/core/extract-assets.js +1 -1
- package/src/core/framework-detector.js +538 -0
- package/src/core/html-extractor.js +45 -4
- package/src/core/lazy-loader.js +7 -7
- package/src/core/multi-page-screenshot.js +9 -6
- package/src/core/page-readiness.js +8 -8
- package/src/core/screenshot.js +138 -9
- package/src/core/section-cropper.js +209 -0
- package/src/core/section-detector.js +386 -0
- package/src/core/semantic-enhancer.js +492 -0
- package/src/core/state-capture.js +18 -22
- package/src/core/tests/test-section-cropper.js +177 -0
- package/src/core/tests/test-section-detector.js +55 -0
- package/src/core/video-capture.js +152 -146
- package/src/route-discoverers/angular-discoverer.js +157 -0
- package/src/route-discoverers/astro-discoverer.js +123 -0
- package/src/route-discoverers/base-discoverer.js +242 -0
- package/src/route-discoverers/index.js +106 -0
- package/src/route-discoverers/next-discoverer.js +130 -0
- package/src/route-discoverers/nuxt-discoverer.js +138 -0
- package/src/route-discoverers/react-discoverer.js +139 -0
- package/src/route-discoverers/svelte-discoverer.js +109 -0
- package/src/route-discoverers/universal-discoverer.js +227 -0
- package/src/route-discoverers/vue-discoverer.js +118 -0
- package/src/utils/__init__.py +1 -1
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/browser.js +11 -37
- package/src/utils/playwright.js +213 -0
- package/src/verification/generate-audit-report.js +398 -0
- package/src/verification/verify-footer.js +493 -0
- package/src/verification/verify-header.js +486 -0
- package/src/verification/verify-layout.js +2 -2
- package/src/verification/verify-menu.js +4 -20
- package/src/verification/verify-slider.js +533 -0
- package/src/utils/puppeteer.js +0 -281
package/src/utils/puppeteer.js
DELETED
|
@@ -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
|
-
}
|