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
@@ -0,0 +1,398 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Audit Report Generator
4
+ *
5
+ * Aggregates verification results from header, footer, slider, menu, and layout
6
+ * verifiers into a consolidated markdown report with:
7
+ * - Summary table with pass/warn/fail status
8
+ * - Component sections with side-by-side screenshots
9
+ * - Responsive breakpoint analysis
10
+ * - CSS fix suggestions
11
+ *
12
+ * Usage:
13
+ * node generate-audit-report.js --dir <results-dir> [--output <report-path>]
14
+ *
15
+ * Options:
16
+ * --dir Directory containing verification JSON results
17
+ * --output Output path for markdown report (default: component-audit.md)
18
+ * --verbose Show detailed progress
19
+ */
20
+
21
+ import fs from 'fs/promises';
22
+ import path from 'path';
23
+ import { parseArgs, outputJSON, outputError } from '../utils/browser.js';
24
+
25
+ // Component types and their result files
26
+ const COMPONENT_FILES = {
27
+ header: 'header-results.json',
28
+ footer: 'footer-results.json',
29
+ slider: 'slider-results.json',
30
+ menu: 'menu-results.json',
31
+ layout: 'layout-results.json'
32
+ };
33
+
34
+ // Status icons
35
+ const STATUS_ICONS = {
36
+ pass: 'āœ…',
37
+ warn: 'āš ļø',
38
+ fail: 'āŒ',
39
+ info: 'ā„¹ļø'
40
+ };
41
+
42
+ /**
43
+ * Load verification results from directory
44
+ */
45
+ async function loadVerificationResults(dir) {
46
+ const results = {};
47
+
48
+ for (const [component, filename] of Object.entries(COMPONENT_FILES)) {
49
+ const filePath = path.join(dir, filename);
50
+ try {
51
+ const content = await fs.readFile(filePath, 'utf-8');
52
+ results[component] = JSON.parse(content);
53
+ } catch (err) {
54
+ if (err.code !== 'ENOENT') {
55
+ console.error(`Warning: Failed to load ${filename}: ${err.message}`);
56
+ }
57
+ results[component] = null;
58
+ }
59
+ }
60
+
61
+ return results;
62
+ }
63
+
64
+ /**
65
+ * Calculate component status
66
+ */
67
+ function getComponentStatus(result) {
68
+ if (!result) return { status: 'skip', icon: STATUS_ICONS.info, label: 'Not tested' };
69
+
70
+ const { summary } = result;
71
+ if (!summary) return { status: 'skip', icon: STATUS_ICONS.info, label: 'No data' };
72
+
73
+ if (summary.failed > 0) {
74
+ return { status: 'fail', icon: STATUS_ICONS.fail, label: `${summary.failed} failed` };
75
+ }
76
+ if (summary.warnings?.length > 0) {
77
+ return { status: 'warn', icon: STATUS_ICONS.warn, label: `${summary.warnings.length} warnings` };
78
+ }
79
+ return { status: 'pass', icon: STATUS_ICONS.pass, label: 'Passed' };
80
+ }
81
+
82
+ /**
83
+ * Generate summary table
84
+ */
85
+ function generateSummaryTable(results) {
86
+ let table = `| Component | Status | Tests | Details |
87
+ |-----------|--------|-------|---------|
88
+ `;
89
+
90
+ for (const [component, result] of Object.entries(results)) {
91
+ const status = getComponentStatus(result);
92
+ const tests = result?.summary
93
+ ? `${result.summary.passed}/${result.summary.totalTests}`
94
+ : '-';
95
+ table += `| ${component.charAt(0).toUpperCase() + component.slice(1)} | ${status.icon} ${status.label} | ${tests} | ${result?.url || '-'} |\n`;
96
+ }
97
+
98
+ return table;
99
+ }
100
+
101
+ /**
102
+ * Generate viewport breakdown table
103
+ */
104
+ function generateViewportTable(results) {
105
+ const viewports = ['mobile', 'tablet', 'desktop'];
106
+ const components = Object.keys(results).filter(c => results[c]?.viewports);
107
+
108
+ if (components.length === 0) return '';
109
+
110
+ let table = `| Component | Mobile | Tablet | Desktop |
111
+ |-----------|--------|--------|---------|
112
+ `;
113
+
114
+ for (const component of components) {
115
+ const result = results[component];
116
+ const row = [component.charAt(0).toUpperCase() + component.slice(1)];
117
+
118
+ for (const vp of viewports) {
119
+ const vpResult = result.viewports?.[vp];
120
+ if (vpResult) {
121
+ const icon = vpResult.failed > 0 ? STATUS_ICONS.fail
122
+ : vpResult.warnings?.length > 0 ? STATUS_ICONS.warn
123
+ : STATUS_ICONS.pass;
124
+ row.push(`${icon} ${vpResult.passed}/${vpResult.tests?.length || 0}`);
125
+ } else {
126
+ row.push('-');
127
+ }
128
+ }
129
+
130
+ table += `| ${row.join(' | ')} |\n`;
131
+ }
132
+
133
+ return table;
134
+ }
135
+
136
+ /**
137
+ * Generate component section
138
+ */
139
+ function generateComponentSection(component, result) {
140
+ if (!result) {
141
+ return `### ${component.charAt(0).toUpperCase() + component.slice(1)}
142
+
143
+ ${STATUS_ICONS.info} Not tested
144
+
145
+ ---
146
+
147
+ `;
148
+ }
149
+
150
+ const status = getComponentStatus(result);
151
+ let section = `### ${component.charAt(0).toUpperCase() + component.slice(1)} ${status.icon}
152
+
153
+ `;
154
+
155
+ // Add component-specific info
156
+ if (component === 'slider' && result.sliderLibrary) {
157
+ section += `**Library:** ${result.sliderLibrary}\n\n`;
158
+ }
159
+
160
+ // Test results by viewport
161
+ if (result.viewports) {
162
+ for (const [viewport, vpResult] of Object.entries(result.viewports)) {
163
+ section += `#### ${viewport.charAt(0).toUpperCase() + viewport.slice(1)} (${vpResult.dimensions?.width}x${vpResult.dimensions?.height})\n\n`;
164
+
165
+ // List tests
166
+ if (vpResult.tests?.length > 0) {
167
+ for (const test of vpResult.tests) {
168
+ const icon = test.passed ? STATUS_ICONS.pass : STATUS_ICONS.fail;
169
+ section += `- ${icon} **${test.name}**`;
170
+ if (test.selector) section += ` - \`${test.selector}\``;
171
+ if (test.count !== undefined) section += ` (${test.count} found)`;
172
+ if (test.note) section += ` - ${test.note}`;
173
+ if (test.error) section += ` - āš ļø ${test.error}`;
174
+ section += '\n';
175
+ }
176
+ }
177
+
178
+ // Warnings
179
+ if (vpResult.warnings?.length > 0) {
180
+ section += '\n**Warnings:**\n';
181
+ for (const warning of vpResult.warnings) {
182
+ section += `- ${STATUS_ICONS.warn} ${warning}\n`;
183
+ }
184
+ }
185
+
186
+ section += '\n';
187
+ }
188
+ }
189
+
190
+ // Screenshots
191
+ if (result.screenshots?.length > 0) {
192
+ section += `#### Screenshots\n\n`;
193
+ section += `| Viewport | Screenshot |\n|----------|------------|\n`;
194
+ for (const screenshot of result.screenshots) {
195
+ const name = path.basename(screenshot);
196
+ const viewport = name.replace(/^[a-z]+-test-/, '').replace('.png', '');
197
+ section += `| ${viewport} | [${name}](${screenshot}) |\n`;
198
+ }
199
+ section += '\n';
200
+ }
201
+
202
+ section += '---\n\n';
203
+ return section;
204
+ }
205
+
206
+ /**
207
+ * Generate CSS fixes section
208
+ */
209
+ function generateCSSFixes(results) {
210
+ const fixes = [];
211
+
212
+ // Collect issues that might need CSS fixes
213
+ for (const [component, result] of Object.entries(results)) {
214
+ if (!result?.viewports) continue;
215
+
216
+ for (const [viewport, vpResult] of Object.entries(result.viewports)) {
217
+ // Check for layout issues
218
+ if (component === 'header' && vpResult.headerHeight) {
219
+ // Height consistency check already done in verify-header
220
+ }
221
+
222
+ if (component === 'footer') {
223
+ const positionTest = vpResult.tests?.find(t => t.name === 'Footer at page bottom');
224
+ if (positionTest && !positionTest.passed) {
225
+ fixes.push({
226
+ component,
227
+ viewport,
228
+ issue: 'Footer not at page bottom',
229
+ suggestion: `/* Ensure footer sticks to bottom */
230
+ footer {
231
+ margin-top: auto;
232
+ }
233
+ body {
234
+ min-height: 100vh;
235
+ display: flex;
236
+ flex-direction: column;
237
+ }`
238
+ });
239
+ }
240
+ }
241
+
242
+ // Check warnings
243
+ if (vpResult.warnings) {
244
+ for (const warning of vpResult.warnings) {
245
+ if (warning.includes('z-index')) {
246
+ fixes.push({
247
+ component,
248
+ viewport,
249
+ issue: warning,
250
+ suggestion: `/* Increase header z-index */
251
+ header, .header, [role="banner"] {
252
+ z-index: 1000;
253
+ }`
254
+ });
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ if (fixes.length === 0) return '';
262
+
263
+ let section = `## Suggested CSS Fixes
264
+
265
+ `;
266
+
267
+ for (const fix of fixes) {
268
+ section += `### ${fix.component} (${fix.viewport})
269
+
270
+ **Issue:** ${fix.issue}
271
+
272
+ \`\`\`css
273
+ ${fix.suggestion}
274
+ \`\`\`
275
+
276
+ `;
277
+ }
278
+
279
+ return section;
280
+ }
281
+
282
+ /**
283
+ * Generate full markdown report
284
+ */
285
+ function generateMarkdownReport(results, url) {
286
+ const timestamp = new Date().toISOString();
287
+
288
+ let report = `# Component Audit Report
289
+
290
+ **Generated:** ${timestamp}
291
+ **URL:** ${url || 'N/A'}
292
+
293
+ ## Summary
294
+
295
+ ${generateSummaryTable(results)}
296
+
297
+ ## Responsive Breakdown
298
+
299
+ ${generateViewportTable(results)}
300
+
301
+ ## Component Details
302
+
303
+ `;
304
+
305
+ // Add each component section
306
+ for (const [component, result] of Object.entries(results)) {
307
+ report += generateComponentSection(component, result);
308
+ }
309
+
310
+ // Add CSS fixes if any
311
+ report += generateCSSFixes(results);
312
+
313
+ // Footer
314
+ report += `---
315
+
316
+ *Report generated by design-clone verification suite*
317
+ `;
318
+
319
+ return report;
320
+ }
321
+
322
+ /**
323
+ * Main function
324
+ */
325
+ async function generateAuditReport() {
326
+ const args = parseArgs(process.argv.slice(2));
327
+
328
+ if (!args.dir) {
329
+ outputError(new Error('--dir is required'));
330
+ process.exit(1);
331
+ }
332
+
333
+ const verbose = args.verbose === 'true';
334
+ const outputPath = args.output || path.join(args.dir, 'component-audit.md');
335
+
336
+ try {
337
+ if (verbose) console.error(`\nšŸ“Š Generating audit report from ${args.dir}\n`);
338
+
339
+ // Load all verification results
340
+ const results = await loadVerificationResults(args.dir);
341
+
342
+ // Get URL from first available result
343
+ const url = Object.values(results).find(r => r?.url)?.url;
344
+
345
+ // Count loaded components
346
+ const loadedCount = Object.values(results).filter(r => r !== null).length;
347
+ if (verbose) console.error(` Loaded ${loadedCount}/${Object.keys(COMPONENT_FILES).length} component results`);
348
+
349
+ // Generate report
350
+ const report = generateMarkdownReport(results, url);
351
+
352
+ // Write report
353
+ await fs.writeFile(outputPath, report, 'utf-8');
354
+ if (verbose) console.error(` āœ“ Report written to ${outputPath}`);
355
+
356
+ // Calculate overall stats
357
+ let totalTests = 0, totalPassed = 0, totalFailed = 0, totalWarnings = 0;
358
+ for (const result of Object.values(results)) {
359
+ if (result?.summary) {
360
+ totalTests += result.summary.totalTests || 0;
361
+ totalPassed += result.summary.passed || 0;
362
+ totalFailed += result.summary.failed || 0;
363
+ totalWarnings += result.summary.warnings?.length || 0;
364
+ }
365
+ }
366
+
367
+ const output = {
368
+ success: totalFailed === 0,
369
+ reportPath: outputPath,
370
+ url,
371
+ summary: {
372
+ components: loadedCount,
373
+ totalTests,
374
+ passed: totalPassed,
375
+ failed: totalFailed,
376
+ warnings: totalWarnings
377
+ }
378
+ };
379
+
380
+ if (verbose) {
381
+ console.error('\nšŸ“‹ Report Summary:');
382
+ console.error(` Components: ${loadedCount}`);
383
+ console.error(` Tests: ${totalPassed}/${totalTests} passed`);
384
+ console.error(` Failures: ${totalFailed}`);
385
+ console.error(` Warnings: ${totalWarnings}`);
386
+ console.error(` Status: ${output.success ? 'āœ“ PASS' : 'āœ— ISSUES FOUND'}\n`);
387
+ }
388
+
389
+ outputJSON(output);
390
+ process.exit(output.success ? 0 : 1);
391
+
392
+ } catch (error) {
393
+ outputError(error);
394
+ process.exit(1);
395
+ }
396
+ }
397
+
398
+ generateAuditReport();