design-clone 1.2.0 → 2.3.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 (174) hide show
  1. package/README.md +32 -39
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -106
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +11 -56
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +11 -16
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +24 -28
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +556 -0
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +20 -21
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/{css-extractor.js → css/css-extractor.js} +4 -4
  51. package/src/core/css/filter-css-dead-code.js +120 -0
  52. package/src/core/css/filter-css-html-analyzer.js +110 -0
  53. package/src/core/css/filter-css-selector-matcher.js +172 -0
  54. package/src/core/css/filter-css.js +206 -0
  55. package/src/core/css/merge-css-atrule-processor.js +158 -0
  56. package/src/core/css/merge-css-file-io.js +68 -0
  57. package/src/core/css/merge-css.js +148 -0
  58. package/src/core/detection/framework-detector-routing.js +68 -0
  59. package/src/core/detection/framework-detector-signals.js +65 -0
  60. package/src/core/detection/framework-detector.js +198 -0
  61. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  62. package/src/core/dimension/dimension-extractor.js +317 -0
  63. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  64. package/src/core/dimension/dimension-output.js +173 -0
  65. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  66. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  67. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  68. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  69. package/src/core/discovery/app-state-snapshot.js +131 -0
  70. package/src/core/discovery/discover-pages-routes.js +84 -0
  71. package/src/core/discovery/discover-pages-utils.js +177 -0
  72. package/src/core/discovery/discover-pages.js +191 -0
  73. package/src/core/html/html-extractor-inline-styler.js +70 -0
  74. package/src/core/html/html-extractor.js +147 -0
  75. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  76. package/src/core/html/semantic-enhancer-page.js +148 -0
  77. package/src/core/html/semantic-enhancer.js +135 -0
  78. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  79. package/src/core/links/rewrite-links.js +173 -0
  80. package/src/core/media/asset-validator.js +118 -0
  81. package/src/core/media/extract-assets-downloader.js +187 -0
  82. package/src/core/media/extract-assets-page-scraper.js +115 -0
  83. package/src/core/media/extract-assets.js +159 -0
  84. package/src/core/media/video-capture-convert.js +200 -0
  85. package/src/core/media/video-capture.js +201 -0
  86. package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +1 -1
  87. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +44 -46
  88. package/src/core/{page-readiness.js → page-prep/page-readiness.js} +8 -8
  89. package/src/core/section/section-cropper-helpers.js +43 -0
  90. package/src/core/section/section-cropper.js +132 -0
  91. package/src/core/section/section-detector-strategies.js +139 -0
  92. package/src/core/section/section-detector-utils.js +100 -0
  93. package/src/core/section/section-detector.js +88 -0
  94. package/src/core/tests/test-section-cropper.js +177 -0
  95. package/src/core/tests/test-section-detector.js +55 -0
  96. package/src/post-process/enhance-assets.js +29 -4
  97. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  98. package/src/post-process/fetch-images.js +60 -263
  99. package/src/post-process/inject-gosnap.js +88 -0
  100. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  101. package/src/post-process/inject-icons.js +47 -200
  102. package/src/route-discoverers/angular-discoverer.js +157 -0
  103. package/src/route-discoverers/astro-discoverer.js +123 -0
  104. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  105. package/src/route-discoverers/base-discoverer.js +153 -0
  106. package/src/route-discoverers/index.js +106 -0
  107. package/src/route-discoverers/next-discoverer.js +130 -0
  108. package/src/route-discoverers/nuxt-discoverer.js +138 -0
  109. package/src/route-discoverers/react-discoverer.js +139 -0
  110. package/src/route-discoverers/svelte-discoverer.js +109 -0
  111. package/src/route-discoverers/universal-discoverer.js +227 -0
  112. package/src/route-discoverers/vue-discoverer.js +118 -0
  113. package/src/shared/config.js +38 -0
  114. package/src/shared/error-codes.js +31 -0
  115. package/src/shared/viewports.js +46 -0
  116. package/src/utils/browser.js +11 -44
  117. package/src/utils/helpers.js +4 -0
  118. package/src/utils/log.js +12 -0
  119. package/src/utils/playwright-loader.js +76 -0
  120. package/src/utils/playwright.js +147 -0
  121. package/src/utils/progress.js +32 -0
  122. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  123. package/src/verification/generate-audit-report-sections.js +158 -0
  124. package/src/verification/generate-audit-report.js +122 -0
  125. package/src/verification/quality-scorer.js +92 -0
  126. package/src/verification/verify-footer-checks.js +103 -0
  127. package/src/verification/verify-footer-helpers.js +178 -0
  128. package/src/verification/verify-footer.js +135 -0
  129. package/src/verification/verify-header-checks.js +104 -0
  130. package/src/verification/verify-header-helpers.js +156 -0
  131. package/src/verification/verify-header.js +144 -0
  132. package/src/verification/verify-layout-report.js +101 -0
  133. package/src/verification/verify-layout.js +14 -260
  134. package/src/verification/verify-menu-checks.js +104 -0
  135. package/src/verification/verify-menu-helpers.js +112 -0
  136. package/src/verification/verify-menu.js +18 -302
  137. package/src/verification/verify-slider-checks.js +115 -0
  138. package/src/verification/verify-slider-constants.js +65 -0
  139. package/src/verification/verify-slider-helpers.js +164 -0
  140. package/src/verification/verify-slider.js +142 -0
  141. package/.env.example +0 -14
  142. package/docs/basic-clone.md +0 -63
  143. package/docs/cli-reference.md +0 -118
  144. package/docs/design-clone-architecture.md +0 -275
  145. package/docs/pixel-perfect.md +0 -86
  146. package/docs/troubleshooting.md +0 -169
  147. package/requirements.txt +0 -5
  148. package/src/ai/analyze-structure.py +0 -305
  149. package/src/ai/extract-design-tokens.py +0 -439
  150. package/src/ai/prompts/__init__.py +0 -2
  151. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  152. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  153. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  154. package/src/ai/prompts/design_tokens.py +0 -183
  155. package/src/ai/prompts/structure_analysis.py +0 -273
  156. package/src/core/animation-extractor.js +0 -526
  157. package/src/core/design-tokens.js +0 -103
  158. package/src/core/dimension-extractor.js +0 -366
  159. package/src/core/dimension-output.js +0 -208
  160. package/src/core/discover-pages.js +0 -314
  161. package/src/core/extract-assets.js +0 -468
  162. package/src/core/filter-css.js +0 -499
  163. package/src/core/html-extractor.js +0 -171
  164. package/src/core/merge-css.js +0 -407
  165. package/src/core/multi-page-screenshot.js +0 -377
  166. package/src/core/rewrite-links.js +0 -226
  167. package/src/core/screenshot.js +0 -572
  168. package/src/core/state-capture.js +0 -602
  169. package/src/core/video-capture.js +0 -540
  170. package/src/utils/__init__.py +0 -16
  171. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  172. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  173. package/src/utils/env.py +0 -134
  174. package/src/utils/puppeteer.js +0 -281
@@ -1,314 +0,0 @@
1
- /**
2
- * Page Discovery Module
3
- *
4
- * Extracts navigation links from a website to discover cloneable pages.
5
- * Handles SPA hydration, filters external links, and normalizes URLs.
6
- *
7
- * Usage:
8
- * import { discoverPages } from './discover-pages.js';
9
- * const result = await discoverPages('https://example.com', { maxPages: 10 });
10
- */
11
-
12
- import { getBrowser, getPage, disconnectBrowser } from '../utils/browser.js';
13
- import { waitForDomStable, waitForPageReady } from './page-readiness.js';
14
- import { dismissCookieBanner } from './cookie-handler.js';
15
-
16
- // Navigation selectors in priority order
17
- const NAV_SELECTORS = [
18
- 'header nav a',
19
- 'header a',
20
- 'nav a',
21
- '[role="navigation"] a',
22
- '.navbar a',
23
- '.nav-menu a',
24
- '.navigation a',
25
- 'footer nav a',
26
- 'footer a'
27
- ];
28
-
29
- // Patterns to exclude from discovered links
30
- const EXCLUDE_PATTERNS = [
31
- /^mailto:/i,
32
- /^tel:/i,
33
- /^javascript:/i,
34
- /^#/,
35
- /\.(pdf|jpg|jpeg|png|gif|svg|webp|ico|zip|tar|gz|mp3|mp4|avi|mov)$/i,
36
- /facebook\.com/i,
37
- /twitter\.com/i,
38
- /instagram\.com/i,
39
- /linkedin\.com/i,
40
- /youtube\.com/i,
41
- /tiktok\.com/i
42
- ];
43
-
44
- // Default options
45
- const DEFAULT_OPTIONS = {
46
- maxPages: 10,
47
- selectors: null, // Use default NAV_SELECTORS if null
48
- includeSubdomains: false,
49
- timeout: 30000
50
- };
51
-
52
- /**
53
- * Normalize URL for comparison and deduplication
54
- * @param {string} baseUrl - Base URL for resolving relative paths
55
- * @param {string} href - URL to normalize
56
- * @returns {string|null} Normalized URL or null if invalid
57
- */
58
- export function normalizeUrl(baseUrl, href) {
59
- if (!href || typeof href !== 'string') return null;
60
-
61
- try {
62
- const url = new URL(href, baseUrl);
63
-
64
- // Skip non-http(s) protocols
65
- if (!url.protocol.startsWith('http')) return null;
66
-
67
- // Build normalized URL: origin + pathname (no hash, no query)
68
- let normalized = url.origin + url.pathname;
69
-
70
- // Remove trailing slash (except for root)
71
- if (normalized.endsWith('/') && normalized !== url.origin + '/') {
72
- normalized = normalized.slice(0, -1);
73
- }
74
-
75
- return normalized;
76
- } catch {
77
- return null;
78
- }
79
- }
80
-
81
- /**
82
- * Check if URL is same domain as base
83
- * @param {string} url - URL to check
84
- * @param {string} baseDomain - Base domain to compare against
85
- * @param {boolean} includeSubdomains - Whether to include subdomains
86
- * @returns {boolean}
87
- */
88
- export function isSameDomain(url, baseDomain, includeSubdomains = false) {
89
- try {
90
- const urlObj = new URL(url);
91
- const hostname = urlObj.hostname.toLowerCase();
92
- const base = baseDomain.toLowerCase();
93
-
94
- if (hostname === base) return true;
95
-
96
- if (includeSubdomains) {
97
- return hostname.endsWith('.' + base);
98
- }
99
-
100
- return false;
101
- } catch {
102
- return false;
103
- }
104
- }
105
-
106
- /**
107
- * Extract page name from link text or URL path
108
- * @param {string} text - Link text
109
- * @param {string} path - URL path
110
- * @returns {string} Page name
111
- */
112
- export function extractPageName(text, path) {
113
- // Use link text if available and meaningful
114
- if (text && text.length > 0 && text.length < 50) {
115
- return text;
116
- }
117
-
118
- // Extract from path
119
- if (!path || path === '/') return 'Home';
120
-
121
- // Get last segment of path
122
- const segments = path.split('/').filter(Boolean);
123
- if (segments.length === 0) return 'Home';
124
-
125
- const lastSegment = segments[segments.length - 1];
126
-
127
- // Convert kebab-case/snake_case to Title Case
128
- return lastSegment
129
- .replace(/[-_]/g, ' ')
130
- .replace(/\b\w/g, c => c.toUpperCase());
131
- }
132
-
133
- /**
134
- * Check if href should be excluded
135
- * @param {string} href - URL to check
136
- * @returns {boolean}
137
- */
138
- function shouldExclude(href) {
139
- if (!href) return true;
140
- return EXCLUDE_PATTERNS.some(pattern => pattern.test(href));
141
- }
142
-
143
- /**
144
- * Discover pages from a website by extracting navigation links
145
- * @param {string} baseUrl - Starting URL to discover from
146
- * @param {Object} options - Discovery options
147
- * @returns {Promise<Object>} Discovery result
148
- */
149
- export async function discoverPages(baseUrl, options = {}) {
150
- const opts = { ...DEFAULT_OPTIONS, ...options };
151
- const startTime = Date.now();
152
-
153
- let browser = null;
154
- let page = null;
155
-
156
- try {
157
- // Parse base URL
158
- const baseUrlObj = new URL(baseUrl);
159
- const baseDomain = baseUrlObj.hostname;
160
-
161
- // Launch browser
162
- browser = await getBrowser({ headless: true });
163
- page = await getPage(browser);
164
-
165
- // Navigate to page
166
- await page.goto(baseUrl, {
167
- waitUntil: ['load', 'networkidle0'],
168
- timeout: opts.timeout
169
- });
170
-
171
- // Wait for SPA hydration
172
- await page.waitForSelector('nav a, header a, [role="navigation"] a', {
173
- visible: true,
174
- timeout: 5000
175
- }).catch(() => {});
176
-
177
- await waitForDomStable(page, 500, 5000);
178
-
179
- // Dismiss cookie banner if present
180
- await dismissCookieBanner(page);
181
-
182
- // Wait a bit more for any dynamic content
183
- await new Promise(r => setTimeout(r, 1000));
184
-
185
- // Extract links using selectors
186
- const selectors = opts.selectors || NAV_SELECTORS;
187
- const selectorString = selectors.join(', ');
188
-
189
- const rawLinks = await page.$$eval(selectorString, (elements) => {
190
- return elements.map(el => ({
191
- href: el.href,
192
- text: el.textContent?.trim() || '',
193
- tagName: el.tagName
194
- }));
195
- }).catch(() => []);
196
-
197
- // Process and filter links
198
- const seenUrls = new Set();
199
- const pages = [];
200
-
201
- // Always include homepage first
202
- const homeUrl = normalizeUrl(baseUrl, '/');
203
- if (homeUrl) {
204
- seenUrls.add(homeUrl);
205
- pages.push({
206
- path: '/',
207
- name: 'Home',
208
- url: homeUrl
209
- });
210
- }
211
-
212
- for (const link of rawLinks) {
213
- // Skip excluded patterns
214
- if (shouldExclude(link.href)) continue;
215
-
216
- // Normalize URL
217
- const normalized = normalizeUrl(baseUrl, link.href);
218
- if (!normalized) continue;
219
-
220
- // Skip if already seen
221
- if (seenUrls.has(normalized)) continue;
222
-
223
- // Check same domain
224
- if (!isSameDomain(normalized, baseDomain, opts.includeSubdomains)) continue;
225
-
226
- // Extract path
227
- const urlObj = new URL(normalized);
228
- const path = urlObj.pathname;
229
-
230
- // Skip homepage (already added)
231
- if (path === '/') continue;
232
-
233
- // Add to results
234
- seenUrls.add(normalized);
235
- pages.push({
236
- path,
237
- name: extractPageName(link.text, path),
238
- url: normalized
239
- });
240
-
241
- // Check max pages limit
242
- if (pages.length >= opts.maxPages) break;
243
- }
244
-
245
- // Sort by path depth (shallow first)
246
- pages.sort((a, b) => {
247
- if (a.path === '/') return -1;
248
- if (b.path === '/') return 1;
249
- const depthA = (a.path.match(/\//g) || []).length;
250
- const depthB = (b.path.match(/\//g) || []).length;
251
- return depthA - depthB;
252
- });
253
-
254
- const duration = Date.now() - startTime;
255
-
256
- return {
257
- success: true,
258
- baseUrl: baseUrlObj.origin,
259
- baseDomain,
260
- pages,
261
- stats: {
262
- totalLinksFound: rawLinks.length,
263
- pagesDiscovered: pages.length,
264
- durationMs: duration
265
- }
266
- };
267
- } catch (error) {
268
- return {
269
- success: false,
270
- baseUrl,
271
- pages: [{
272
- path: '/',
273
- name: 'Home',
274
- url: normalizeUrl(baseUrl, '/') || baseUrl
275
- }],
276
- error: error.message,
277
- stats: {
278
- totalLinksFound: 0,
279
- pagesDiscovered: 1,
280
- durationMs: Date.now() - startTime
281
- }
282
- };
283
- } finally {
284
- if (browser) {
285
- await disconnectBrowser();
286
- }
287
- }
288
- }
289
-
290
- // CLI support
291
- const isMainModule = process.argv[1] && (
292
- process.argv[1].endsWith('discover-pages.js') ||
293
- process.argv[1].includes('discover-pages')
294
- );
295
-
296
- if (isMainModule) {
297
- const url = process.argv[2];
298
- const maxPages = parseInt(process.argv[3]) || 10;
299
-
300
- if (!url) {
301
- console.error('Usage: node discover-pages.js <url> [maxPages]');
302
- process.exit(1);
303
- }
304
-
305
- discoverPages(url, { maxPages })
306
- .then(result => {
307
- console.log(JSON.stringify(result, null, 2));
308
- process.exit(result.success ? 0 : 1);
309
- })
310
- .catch(err => {
311
- console.error(JSON.stringify({ success: false, error: err.message }));
312
- process.exit(1);
313
- });
314
- }