@detiq/crawler 0.1.5 → 0.1.6
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/dist/crawler.js +33 -18
- package/dist/crawler.js.map +2 -2
- package/package.json +1 -1
- package/src/crawler.ts +29 -17
package/package.json
CHANGED
package/src/crawler.ts
CHANGED
|
@@ -1303,7 +1303,8 @@ export async function crawlProject(opts: {
|
|
|
1303
1303
|
}
|
|
1304
1304
|
let checkpointSuccessCount = 0;
|
|
1305
1305
|
let maxSeenTotal = 0; // track high-water mark so progress % never goes backwards
|
|
1306
|
-
let lastEmittedPct =
|
|
1306
|
+
let lastEmittedPct = 5; // clamp so progress never decreases even when queue grows
|
|
1307
|
+
const crawlLoopStart = Date.now();
|
|
1307
1308
|
const startUrl = page.url();
|
|
1308
1309
|
// Resume from previous segment's pending queue if provided
|
|
1309
1310
|
const resumeUrls = opts.resumeUrls ?? [];
|
|
@@ -2135,7 +2136,13 @@ export async function crawlProject(opts: {
|
|
|
2135
2136
|
|
|
2136
2137
|
// Emit progress now — navigation is done and the page is stable.
|
|
2137
2138
|
// This fires before the 30-90s parallel screenshot+AI phase, preventing a silent gap in the UI.
|
|
2138
|
-
|
|
2139
|
+
// Phase 1 (link discovery): 5–20% over first 60s of wall-clock time so users see movement
|
|
2140
|
+
// before visited.size grows enough to drive the 20–80% phase.
|
|
2141
|
+
const elapsedDiscoverySec = (Date.now() - crawlLoopStart) / 1000;
|
|
2142
|
+
const discoveryPct = Math.round(5 + Math.min(15, (elapsedDiscoverySec / 60) * 15));
|
|
2143
|
+
const preCapturePct = Math.max(lastEmittedPct, discoveryPct);
|
|
2144
|
+
lastEmittedPct = preCapturePct;
|
|
2145
|
+
await onProgress?.(preCapturePct, `Crawling ${visited.size} of ~${visited.size + queue.length} · ${(() => { try { return new URL(url).pathname || '/'; } catch { return '/'; } })()}`);
|
|
2139
2146
|
|
|
2140
2147
|
// P3a: Async content detection — get raw DOM element count before AI extraction.
|
|
2141
2148
|
// The AI extraction takes 30-90s; any async-loaded content will be visible after it completes.
|
|
@@ -2145,6 +2152,7 @@ export async function crawlProject(opts: {
|
|
|
2145
2152
|
).catch(() => 0);
|
|
2146
2153
|
|
|
2147
2154
|
await waitForScreenshotReady(page);
|
|
2155
|
+
await onProgress?.(lastEmittedPct, `Capturing ${(() => { try { return new URL(url).pathname || '/'; } catch { return '/'; } })()}`);
|
|
2148
2156
|
|
|
2149
2157
|
// Parallelize: screenshot + element extraction + link discovery + AT capture (all work on current page state)
|
|
2150
2158
|
const [, elements, nextLinks, atElements] = await Promise.all([
|
|
@@ -2644,9 +2652,10 @@ export async function crawlProject(opts: {
|
|
|
2644
2652
|
|
|
2645
2653
|
discovered.push({ url, screenId: screen.id, elements: elements.length, elementFacts: elements, screenshotPath: shotPath, version: screen.version });
|
|
2646
2654
|
// Progress: 20% baseline + up to 60% across discovered screens.
|
|
2647
|
-
// maxSeenTotal
|
|
2648
|
-
// the
|
|
2649
|
-
|
|
2655
|
+
// Cap maxSeenTotal at maxScreens * 3 so unbounded link discovery does not
|
|
2656
|
+
// collapse the denominator and freeze progress in the 20-22% band.
|
|
2657
|
+
const effectiveMax = isFinite(maxScreens) ? Math.min(visited.size + queue.length, maxScreens * 3) : visited.size + queue.length;
|
|
2658
|
+
maxSeenTotal = Math.max(maxSeenTotal, effectiveMax);
|
|
2650
2659
|
const rawPct = 20 + Math.round(Math.min(60, (visited.size / Math.max(1, maxSeenTotal)) * 60));
|
|
2651
2660
|
const clampedPct = Math.max(lastEmittedPct, rawPct);
|
|
2652
2661
|
lastEmittedPct = clampedPct;
|
|
@@ -4018,24 +4027,27 @@ async function waitForStableDOM(page: any, maxWait = 8_000, stabilityMs = 400):
|
|
|
4018
4027
|
}
|
|
4019
4028
|
|
|
4020
4029
|
async function waitForScreenshotReady(page: any): Promise<void> {
|
|
4030
|
+
const hardDeadline = Date.now() + 20_000;
|
|
4031
|
+
const withHardCap = <T>(p: Promise<T>): Promise<T | void> =>
|
|
4032
|
+
Promise.race([p, new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, hardDeadline - Date.now())))]);
|
|
4021
4033
|
try {
|
|
4022
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => { });
|
|
4023
|
-
await page.waitForLoadState('networkidle', { timeout: 12_000 }).catch(() => { });
|
|
4024
|
-
await page.evaluate(() => {
|
|
4034
|
+
await withHardCap(page.waitForLoadState('domcontentloaded', { timeout: 5_000 }).catch(() => { }));
|
|
4035
|
+
await withHardCap(page.waitForLoadState('networkidle', { timeout: 12_000 }).catch(() => { }));
|
|
4036
|
+
await withHardCap(page.evaluate(() => {
|
|
4025
4037
|
if (document.readyState === 'complete') return true;
|
|
4026
4038
|
return new Promise<boolean>((resolve) => {
|
|
4027
4039
|
const done = () => resolve(true);
|
|
4028
4040
|
window.addEventListener('load', done, { once: true });
|
|
4029
4041
|
setTimeout(done, 4_000);
|
|
4030
4042
|
});
|
|
4031
|
-
}).catch(() => { });
|
|
4032
|
-
await page.evaluate(async () => {
|
|
4043
|
+
}).catch(() => { }));
|
|
4044
|
+
await withHardCap(page.evaluate(async () => {
|
|
4033
4045
|
const fonts = (document as any).fonts;
|
|
4034
4046
|
if (fonts?.ready) await fonts.ready;
|
|
4035
|
-
}).catch(() => { });
|
|
4036
|
-
await scrollToTriggerLazyLoad(page);
|
|
4037
|
-
await waitForStableDOM(page,
|
|
4038
|
-
await page.evaluate(async () => {
|
|
4047
|
+
}).catch(() => { }));
|
|
4048
|
+
await withHardCap(scrollToTriggerLazyLoad(page));
|
|
4049
|
+
await withHardCap(waitForStableDOM(page, 4_000, 700));
|
|
4050
|
+
await withHardCap(page.evaluate(async () => {
|
|
4039
4051
|
const pending = Array.from(document.images).filter((img) => !img.complete);
|
|
4040
4052
|
if (pending.length === 0) return;
|
|
4041
4053
|
await Promise.race([
|
|
@@ -4045,9 +4057,9 @@ async function waitForScreenshotReady(page: any): Promise<void> {
|
|
|
4045
4057
|
}))),
|
|
4046
4058
|
new Promise<void>((resolve) => setTimeout(resolve, 4_000)),
|
|
4047
4059
|
]);
|
|
4048
|
-
}).catch(() => { });
|
|
4049
|
-
await waitForStableDOM(page,
|
|
4050
|
-
await page.waitForTimeout(250).catch(() => { });
|
|
4060
|
+
}).catch(() => { }));
|
|
4061
|
+
await withHardCap(waitForStableDOM(page, 2_000, 500));
|
|
4062
|
+
await withHardCap(page.waitForTimeout(250).catch(() => { }));
|
|
4051
4063
|
} catch { /* readiness is best-effort; never fail the crawl for a continuously mutating page */ }
|
|
4052
4064
|
}
|
|
4053
4065
|
|