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.
- package/README.md +32 -39
- package/SKILL.md +69 -45
- package/bin/cli.js +22 -4
- package/bin/commands/clone-site.js +31 -106
- package/bin/commands/help.js +19 -6
- package/bin/commands/init.js +11 -56
- package/bin/commands/uninstall.js +105 -0
- package/bin/commands/update.js +70 -0
- package/bin/commands/verify.js +11 -16
- package/bin/utils/paths.js +28 -0
- package/bin/utils/validate.js +24 -28
- package/bin/utils/version.js +23 -0
- package/docs/code-standards.md +789 -0
- package/docs/codebase-summary.md +556 -0
- package/docs/index.md +74 -0
- package/docs/project-overview-pdr.md +797 -0
- package/docs/system-architecture.md +718 -0
- package/package.json +20 -21
- package/src/ai/prompts/design-tokens/basic.md +80 -0
- package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
- package/src/ai/prompts/design-tokens/section.md +48 -0
- package/src/ai/prompts/design-tokens/with-css.md +87 -0
- package/src/ai/prompts/structure-analysis/basic.md +55 -0
- package/src/ai/prompts/structure-analysis/with-context.md +59 -0
- package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
- package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
- package/src/ai/prompts/ux-audit/aggregation.md +42 -0
- package/src/ai/prompts/ux-audit/desktop.md +92 -0
- package/src/ai/prompts/ux-audit/mobile.md +93 -0
- package/src/ai/prompts/ux-audit/tablet.md +92 -0
- package/src/core/animation/animation-extractor-ast.js +183 -0
- package/src/core/animation/animation-extractor-output.js +152 -0
- package/src/core/animation/animation-extractor.js +178 -0
- package/src/core/animation/state-capture-detection.js +200 -0
- package/src/core/animation/state-capture.js +193 -0
- package/src/core/capture/browser-context-pool.js +96 -0
- package/src/core/capture/multi-page-screenshot-page.js +110 -0
- package/src/core/capture/multi-page-screenshot.js +208 -0
- package/src/core/capture/screenshot-extraction.js +186 -0
- package/src/core/capture/screenshot-helpers.js +175 -0
- package/src/core/capture/screenshot-orchestrator.js +174 -0
- package/src/core/capture/screenshot-viewport.js +93 -0
- package/src/core/capture/screenshot.js +192 -0
- package/src/core/content/content-counter-dom.js +191 -0
- package/src/core/content/content-counter.js +76 -0
- package/src/core/css/breakpoint-detector.js +66 -0
- package/src/core/css/chromium-defaults.json +23 -0
- package/src/core/css/computed-style-extractor.js +102 -0
- package/src/core/css/css-chunker.js +103 -0
- package/src/core/{css-extractor.js → css/css-extractor.js} +4 -4
- package/src/core/css/filter-css-dead-code.js +120 -0
- package/src/core/css/filter-css-html-analyzer.js +110 -0
- package/src/core/css/filter-css-selector-matcher.js +172 -0
- package/src/core/css/filter-css.js +206 -0
- package/src/core/css/merge-css-atrule-processor.js +158 -0
- package/src/core/css/merge-css-file-io.js +68 -0
- package/src/core/css/merge-css.js +148 -0
- package/src/core/detection/framework-detector-routing.js +68 -0
- package/src/core/detection/framework-detector-signals.js +65 -0
- package/src/core/detection/framework-detector.js +198 -0
- package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
- package/src/core/dimension/dimension-extractor.js +317 -0
- package/src/core/dimension/dimension-output-ai-summary.js +111 -0
- package/src/core/dimension/dimension-output.js +173 -0
- package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
- package/src/core/dimension/dom-tree-analyzer.js +191 -0
- package/src/core/discovery/app-state-snapshot-capture.js +195 -0
- package/src/core/discovery/app-state-snapshot-utils.js +178 -0
- package/src/core/discovery/app-state-snapshot.js +131 -0
- package/src/core/discovery/discover-pages-routes.js +84 -0
- package/src/core/discovery/discover-pages-utils.js +177 -0
- package/src/core/discovery/discover-pages.js +191 -0
- package/src/core/html/html-extractor-inline-styler.js +70 -0
- package/src/core/html/html-extractor.js +147 -0
- package/src/core/html/semantic-enhancer-mappings.js +200 -0
- package/src/core/html/semantic-enhancer-page.js +148 -0
- package/src/core/html/semantic-enhancer.js +135 -0
- package/src/core/links/rewrite-links-css-rewriter.js +53 -0
- package/src/core/links/rewrite-links.js +173 -0
- package/src/core/media/asset-validator.js +118 -0
- package/src/core/media/extract-assets-downloader.js +187 -0
- package/src/core/media/extract-assets-page-scraper.js +115 -0
- package/src/core/media/extract-assets.js +159 -0
- package/src/core/media/video-capture-convert.js +200 -0
- package/src/core/media/video-capture.js +201 -0
- package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +1 -1
- package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +44 -46
- package/src/core/{page-readiness.js → page-prep/page-readiness.js} +8 -8
- package/src/core/section/section-cropper-helpers.js +43 -0
- package/src/core/section/section-cropper.js +132 -0
- package/src/core/section/section-detector-strategies.js +139 -0
- package/src/core/section/section-detector-utils.js +100 -0
- package/src/core/section/section-detector.js +88 -0
- package/src/core/tests/test-section-cropper.js +177 -0
- package/src/core/tests/test-section-detector.js +55 -0
- package/src/post-process/enhance-assets.js +29 -4
- package/src/post-process/fetch-images-unsplash-client.js +123 -0
- package/src/post-process/fetch-images.js +60 -263
- package/src/post-process/inject-gosnap.js +88 -0
- package/src/post-process/inject-icons-svg-replacer.js +76 -0
- package/src/post-process/inject-icons.js +47 -200
- package/src/route-discoverers/angular-discoverer.js +157 -0
- package/src/route-discoverers/astro-discoverer.js +123 -0
- package/src/route-discoverers/base-discoverer-utils.js +137 -0
- package/src/route-discoverers/base-discoverer.js +153 -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/shared/config.js +38 -0
- package/src/shared/error-codes.js +31 -0
- package/src/shared/viewports.js +46 -0
- package/src/utils/browser.js +11 -44
- package/src/utils/helpers.js +4 -0
- package/src/utils/log.js +12 -0
- package/src/utils/playwright-loader.js +76 -0
- package/src/utils/playwright.js +147 -0
- package/src/utils/progress.js +32 -0
- package/src/verification/generate-audit-report-css-fixes.js +52 -0
- package/src/verification/generate-audit-report-sections.js +158 -0
- package/src/verification/generate-audit-report.js +122 -0
- package/src/verification/quality-scorer.js +92 -0
- package/src/verification/verify-footer-checks.js +103 -0
- package/src/verification/verify-footer-helpers.js +178 -0
- package/src/verification/verify-footer.js +135 -0
- package/src/verification/verify-header-checks.js +104 -0
- package/src/verification/verify-header-helpers.js +156 -0
- package/src/verification/verify-header.js +144 -0
- package/src/verification/verify-layout-report.js +101 -0
- package/src/verification/verify-layout.js +14 -260
- package/src/verification/verify-menu-checks.js +104 -0
- package/src/verification/verify-menu-helpers.js +112 -0
- package/src/verification/verify-menu.js +18 -302
- package/src/verification/verify-slider-checks.js +115 -0
- package/src/verification/verify-slider-constants.js +65 -0
- package/src/verification/verify-slider-helpers.js +164 -0
- package/src/verification/verify-slider.js +142 -0
- package/.env.example +0 -14
- package/docs/basic-clone.md +0 -63
- package/docs/cli-reference.md +0 -118
- package/docs/design-clone-architecture.md +0 -275
- package/docs/pixel-perfect.md +0 -86
- package/docs/troubleshooting.md +0 -169
- package/requirements.txt +0 -5
- package/src/ai/analyze-structure.py +0 -305
- package/src/ai/extract-design-tokens.py +0 -439
- package/src/ai/prompts/__init__.py +0 -2
- 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/design_tokens.py +0 -183
- package/src/ai/prompts/structure_analysis.py +0 -273
- package/src/core/animation-extractor.js +0 -526
- package/src/core/design-tokens.js +0 -103
- package/src/core/dimension-extractor.js +0 -366
- package/src/core/dimension-output.js +0 -208
- package/src/core/discover-pages.js +0 -314
- package/src/core/extract-assets.js +0 -468
- package/src/core/filter-css.js +0 -499
- package/src/core/html-extractor.js +0 -171
- package/src/core/merge-css.js +0 -407
- package/src/core/multi-page-screenshot.js +0 -377
- package/src/core/rewrite-links.js +0 -226
- package/src/core/screenshot.js +0 -572
- package/src/core/state-capture.js +0 -602
- package/src/core/video-capture.js +0 -540
- package/src/utils/__init__.py +0 -16
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/env.py +0 -134
- 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
|
-
}
|