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.
- package/README.md +42 -20
- package/SKILL.md +74 -0
- 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 +224 -2
- package/docs/codebase-summary.md +309 -0
- package/docs/design-clone-architecture.md +290 -45
- package/docs/pixel-perfect.md +35 -4
- package/docs/project-roadmap.md +382 -0
- package/docs/troubleshooting.md +5 -4
- package/package.json +12 -6
- 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__/__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 +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/animation-extractor.js +526 -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 +311 -7
- 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 +598 -0
- 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 +546 -0
- 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/__pycache__/env.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
|
@@ -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();
|