design-clone 1.1.1 → 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 (70) hide show
  1. package/README.md +42 -20
  2. package/SKILL.md +74 -0
  3. package/bin/commands/clone-site.js +75 -10
  4. package/bin/commands/init.js +33 -1
  5. package/bin/commands/verify.js +5 -3
  6. package/bin/utils/validate.js +24 -8
  7. package/docs/cli-reference.md +224 -2
  8. package/docs/codebase-summary.md +309 -0
  9. package/docs/design-clone-architecture.md +290 -45
  10. package/docs/pixel-perfect.md +35 -4
  11. package/docs/project-roadmap.md +382 -0
  12. package/docs/troubleshooting.md +5 -4
  13. package/package.json +12 -6
  14. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  15. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  16. package/src/ai/analyze-structure.py +73 -3
  17. package/src/ai/extract-design-tokens.py +356 -13
  18. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  19. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  20. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  21. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  22. package/src/ai/prompts/design_tokens.py +133 -0
  23. package/src/ai/prompts/structure_analysis.py +329 -10
  24. package/src/ai/prompts/ux_audit.py +198 -0
  25. package/src/ai/ux-audit.js +596 -0
  26. package/src/core/animation-extractor.js +526 -0
  27. package/src/core/app-state-snapshot.js +511 -0
  28. package/src/core/content-counter.js +342 -0
  29. package/src/core/cookie-handler.js +1 -1
  30. package/src/core/css-extractor.js +4 -4
  31. package/src/core/dimension-extractor.js +93 -21
  32. package/src/core/dimension-output.js +103 -6
  33. package/src/core/discover-pages.js +242 -14
  34. package/src/core/dom-tree-analyzer.js +298 -0
  35. package/src/core/extract-assets.js +1 -1
  36. package/src/core/framework-detector.js +538 -0
  37. package/src/core/html-extractor.js +45 -4
  38. package/src/core/lazy-loader.js +7 -7
  39. package/src/core/multi-page-screenshot.js +9 -6
  40. package/src/core/page-readiness.js +8 -8
  41. package/src/core/screenshot.js +311 -7
  42. package/src/core/section-cropper.js +209 -0
  43. package/src/core/section-detector.js +386 -0
  44. package/src/core/semantic-enhancer.js +492 -0
  45. package/src/core/state-capture.js +598 -0
  46. package/src/core/tests/test-section-cropper.js +177 -0
  47. package/src/core/tests/test-section-detector.js +55 -0
  48. package/src/core/video-capture.js +546 -0
  49. package/src/route-discoverers/angular-discoverer.js +157 -0
  50. package/src/route-discoverers/astro-discoverer.js +123 -0
  51. package/src/route-discoverers/base-discoverer.js +242 -0
  52. package/src/route-discoverers/index.js +106 -0
  53. package/src/route-discoverers/next-discoverer.js +130 -0
  54. package/src/route-discoverers/nuxt-discoverer.js +138 -0
  55. package/src/route-discoverers/react-discoverer.js +139 -0
  56. package/src/route-discoverers/svelte-discoverer.js +109 -0
  57. package/src/route-discoverers/universal-discoverer.js +227 -0
  58. package/src/route-discoverers/vue-discoverer.js +118 -0
  59. package/src/utils/__init__.py +1 -1
  60. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  61. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  62. package/src/utils/browser.js +11 -37
  63. package/src/utils/playwright.js +213 -0
  64. package/src/verification/generate-audit-report.js +398 -0
  65. package/src/verification/verify-footer.js +493 -0
  66. package/src/verification/verify-header.js +486 -0
  67. package/src/verification/verify-layout.js +2 -2
  68. package/src/verification/verify-menu.js +4 -20
  69. package/src/verification/verify-slider.js +533 -0
  70. package/src/utils/puppeteer.js +0 -281
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Test Section Cropper
3
+ *
4
+ * Usage: node src/core/tests/test-section-cropper.js [screenshot-path]
5
+ *
6
+ * Tests the section cropper with a real screenshot.
7
+ * If no path provided, uses a sample from cloned-designs if available.
8
+ */
9
+
10
+ import { chromium } from 'playwright';
11
+ import path from 'path';
12
+ import fs from 'fs/promises';
13
+ import { fileURLToPath } from 'url';
14
+ import { detectSections, getSectionSummary } from '../section-detector.js';
15
+ import { cropSections, isSharpAvailable, getCropperSummary } from '../section-cropper.js';
16
+
17
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
+ const projectRoot = path.join(__dirname, '../../..');
19
+
20
+ async function findTestScreenshot() {
21
+ // Look for existing screenshots in cloned-designs
22
+ const clonedDir = path.join(projectRoot, 'cloned-designs');
23
+ try {
24
+ const dirs = await fs.readdir(clonedDir);
25
+ for (const dir of dirs.reverse()) { // newest first
26
+ const desktopPath = path.join(clonedDir, dir, 'analysis', 'desktop.png');
27
+ try {
28
+ await fs.access(desktopPath);
29
+ return desktopPath;
30
+ } catch {
31
+ continue;
32
+ }
33
+ }
34
+ } catch {
35
+ // No cloned-designs directory
36
+ }
37
+ return null;
38
+ }
39
+
40
+ async function testWithUrl(url, outputDir) {
41
+ console.log(`\n=== Testing with URL: ${url} ===\n`);
42
+
43
+ const browser = await chromium.launch({ headless: true });
44
+ const context = await browser.newContext({
45
+ viewport: { width: 1440, height: 900 }
46
+ });
47
+ const page = await context.newPage();
48
+
49
+ try {
50
+ // Navigate
51
+ console.log('Loading page...');
52
+ await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
53
+ await page.waitForTimeout(1000);
54
+
55
+ // Take full-page screenshot
56
+ const screenshotPath = path.join(outputDir, 'test-full.png');
57
+ await page.screenshot({ path: screenshotPath, fullPage: true });
58
+ console.log(`Screenshot saved: ${screenshotPath}`);
59
+
60
+ // Detect sections
61
+ console.log('\nDetecting sections...');
62
+ const sections = await detectSections(page, { padding: 40 });
63
+ console.log(`Found ${sections.length} sections:`);
64
+ sections.forEach(s => {
65
+ console.log(` [${s.index}] ${s.name} (${s.role}) - ${s.bounds.height}px`);
66
+ });
67
+
68
+ // Crop sections
69
+ console.log('\nCropping sections...');
70
+ const result = await cropSections(screenshotPath, sections, outputDir);
71
+
72
+ console.log(`\nCropped ${result.sections.length} sections:`);
73
+ result.sections.forEach(s => {
74
+ console.log(` [${s.index}] ${s.filename} - ${s.bounds.width}x${s.bounds.height}`);
75
+ });
76
+
77
+ if (result.skipped.length > 0) {
78
+ console.log(`\nSkipped ${result.skipped.length} sections:`);
79
+ result.skipped.forEach(s => {
80
+ console.log(` [${s.index}] ${s.name} - ${s.reason}`);
81
+ });
82
+ }
83
+
84
+ console.log(`\nSummary saved: ${result.summary}`);
85
+ console.log(`Sections directory: ${result.directory}`);
86
+
87
+ return result;
88
+
89
+ } finally {
90
+ await browser.close();
91
+ }
92
+ }
93
+
94
+ async function testWithExistingScreenshot(screenshotPath, outputDir) {
95
+ console.log(`\n=== Testing with existing screenshot ===`);
96
+ console.log(`Screenshot: ${screenshotPath}\n`);
97
+
98
+ // We need a browser to detect sections from the page
99
+ // For existing screenshots, we'll create mock sections based on image height
100
+ const { default: sharp } = await import('sharp');
101
+ const metadata = await sharp(screenshotPath).metadata();
102
+
103
+ console.log(`Image size: ${metadata.width}x${metadata.height}`);
104
+
105
+ // Create mock sections based on viewport chunking
106
+ const viewportHeight = 900;
107
+ const sections = [];
108
+ let y = 0;
109
+ let index = 0;
110
+
111
+ while (y < metadata.height) {
112
+ const height = Math.min(viewportHeight, metadata.height - y);
113
+ sections.push({
114
+ index,
115
+ name: `viewport-${index}`,
116
+ role: 'viewport-chunk',
117
+ bounds: { x: 0, y, width: metadata.width, height }
118
+ });
119
+ y += viewportHeight - 90; // 10% overlap
120
+ index++;
121
+ if (index > 20) break;
122
+ }
123
+
124
+ console.log(`Created ${sections.length} viewport chunks`);
125
+
126
+ // Crop sections
127
+ console.log('\nCropping sections...');
128
+ const result = await cropSections(screenshotPath, sections, outputDir);
129
+
130
+ console.log(`\nCropped ${result.sections.length} sections:`);
131
+ result.sections.forEach(s => {
132
+ console.log(` [${s.index}] ${s.filename} - ${s.bounds.width}x${s.bounds.height}`);
133
+ });
134
+
135
+ return result;
136
+ }
137
+
138
+ async function main() {
139
+ // Check Sharp availability
140
+ if (!isSharpAvailable()) {
141
+ console.error('ERROR: Sharp is not installed. Run: npm install sharp');
142
+ process.exit(1);
143
+ }
144
+ console.log('Sharp is available');
145
+
146
+ const arg = process.argv[2];
147
+ const outputDir = path.join(projectRoot, 'test-output', 'section-cropper-test');
148
+ await fs.mkdir(outputDir, { recursive: true });
149
+
150
+ let result;
151
+
152
+ if (arg && arg.startsWith('http')) {
153
+ // Test with URL
154
+ result = await testWithUrl(arg, outputDir);
155
+ } else if (arg) {
156
+ // Test with provided screenshot path
157
+ result = await testWithExistingScreenshot(arg, outputDir);
158
+ } else {
159
+ // Find existing screenshot or use default URL
160
+ const existingScreenshot = await findTestScreenshot();
161
+ if (existingScreenshot) {
162
+ result = await testWithExistingScreenshot(existingScreenshot, outputDir);
163
+ } else {
164
+ result = await testWithUrl('https://example.com', outputDir);
165
+ }
166
+ }
167
+
168
+ // Final summary
169
+ console.log('\n=== Final Summary ===');
170
+ console.log(getCropperSummary(result));
171
+ console.log(`\nOutput directory: ${outputDir}`);
172
+ }
173
+
174
+ main().catch(err => {
175
+ console.error('Test failed:', err);
176
+ process.exit(1);
177
+ });
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test script for section-detector.js
4
+ * Usage: node src/core/test-section-detector.js [url]
5
+ */
6
+
7
+ import { detectSections, getSectionSummary } from './section-detector.js';
8
+ import { getBrowser, getPage, closeBrowser } from '../utils/browser.js';
9
+
10
+ const url = process.argv[2] || 'https://www.techno-concier.co.jp/';
11
+
12
+ async function test() {
13
+ console.error(`Testing section detection on: ${url}\n`);
14
+
15
+ const browser = await getBrowser({ headless: true });
16
+ const page = await getPage(browser);
17
+
18
+ try {
19
+ await page.goto(url, {
20
+ waitUntil: 'domcontentloaded',
21
+ timeout: 30000
22
+ });
23
+
24
+ // Wait for page to stabilize
25
+ await new Promise(r => setTimeout(r, 3000));
26
+
27
+ const sections = await detectSections(page, {
28
+ padding: 40,
29
+ minSections: 3,
30
+ minSectionHeight: 150
31
+ });
32
+
33
+ const summary = getSectionSummary(sections);
34
+
35
+ console.log('=== Summary ===');
36
+ console.log(JSON.stringify(summary, null, 2));
37
+
38
+ console.log('\n=== Sections ===');
39
+ for (const s of sections) {
40
+ console.log(` [${s.index}] ${s.name.padEnd(20)} (${s.role.padEnd(15)}) y:${String(s.bounds.y).padStart(5)} h:${String(s.bounds.height).padStart(5)}`);
41
+ }
42
+
43
+ // Output JSON result
44
+ console.log('\n=== JSON Output ===');
45
+ console.log(JSON.stringify({ success: true, sections }, null, 2));
46
+
47
+ } catch (err) {
48
+ console.error('Error:', err.message);
49
+ process.exit(1);
50
+ } finally {
51
+ await closeBrowser();
52
+ }
53
+ }
54
+
55
+ test();