@detiq/crawler 0.2.10 → 0.2.12
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/Dockerfile +27 -11
- package/package.json +1 -1
- package/src/crawler.ts +7 -4
- package/src/job-queue.ts +5 -2
- package/src/tls-fingerprint.ts +1 -1
package/Dockerfile
CHANGED
|
@@ -16,13 +16,25 @@ RUN apt-get update && apt-get install -y curl && \
|
|
|
16
16
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
17
17
|
|
|
18
18
|
# Workspace manifests (layer cache)
|
|
19
|
+
# Copy all workspace package.json files so npm ci can resolve the full lockfile.
|
|
20
|
+
# Packages not used by the crawler (api, web, etc.) are stubs; their source is not copied later.
|
|
19
21
|
COPY package.json package-lock.json ./
|
|
20
|
-
COPY packages/
|
|
21
|
-
COPY packages/database/package.json packages/database/
|
|
22
|
-
COPY packages/app-brain/package.json packages/app-brain/
|
|
22
|
+
COPY packages/agent-cli/package.json packages/agent-cli/
|
|
23
23
|
COPY packages/agents/package.json packages/agents/
|
|
24
|
+
COPY packages/api/package.json packages/api/
|
|
25
|
+
COPY packages/app-brain/package.json packages/app-brain/
|
|
26
|
+
COPY packages/chrome-extension/package.json packages/chrome-extension/
|
|
27
|
+
COPY packages/connectors/package.json packages/connectors/
|
|
28
|
+
COPY packages/core/package.json packages/core/
|
|
24
29
|
COPY packages/crawler/package.json packages/crawler/
|
|
25
|
-
|
|
30
|
+
COPY packages/database/package.json packages/database/
|
|
31
|
+
COPY packages/github-action/package.json packages/github-action/
|
|
32
|
+
COPY packages/reranker/package.json packages/reranker/
|
|
33
|
+
COPY packages/sdk/package.json packages/sdk/
|
|
34
|
+
COPY packages/session-receiver/package.json packages/session-receiver/
|
|
35
|
+
COPY packages/vscode-extension/package.json packages/vscode-extension/
|
|
36
|
+
COPY packages/web/package.json packages/web/
|
|
37
|
+
RUN npm ci --include=dev
|
|
26
38
|
|
|
27
39
|
# Re-install chromium after npm ci so the browser revision matches the
|
|
28
40
|
# exact playwright version resolved by the lockfile (not the base image's
|
|
@@ -31,16 +43,20 @@ RUN npx playwright install chromium
|
|
|
31
43
|
|
|
32
44
|
# Source
|
|
33
45
|
COPY tsconfig.base.json ./
|
|
34
|
-
COPY packages/
|
|
35
|
-
COPY packages/
|
|
36
|
-
COPY packages/
|
|
37
|
-
COPY packages/
|
|
38
|
-
COPY packages/
|
|
46
|
+
COPY packages/database packages/database
|
|
47
|
+
COPY packages/reranker packages/reranker
|
|
48
|
+
COPY packages/core packages/core
|
|
49
|
+
COPY packages/connectors packages/connectors
|
|
50
|
+
COPY packages/app-brain packages/app-brain
|
|
51
|
+
COPY packages/agents packages/agents
|
|
52
|
+
COPY packages/crawler packages/crawler
|
|
39
53
|
|
|
40
|
-
# Build
|
|
54
|
+
# Build order: database → reranker → core → connectors → app-brain → agents → crawler
|
|
41
55
|
RUN npx prisma generate --schema packages/database/prisma/schema.prisma && \
|
|
42
|
-
npm run -w @detiq/core build && \
|
|
43
56
|
npm run -w @detiq/database build && \
|
|
57
|
+
npm run -w @detiq/reranker build && \
|
|
58
|
+
npm run -w @detiq/core build && \
|
|
59
|
+
npm run -w @detiq/connectors build && \
|
|
44
60
|
npm run -w @detiq/app-brain build && \
|
|
45
61
|
npm run -w @detiq/agents build && \
|
|
46
62
|
npm run -w @detiq/crawler build
|
package/package.json
CHANGED
package/src/crawler.ts
CHANGED
|
@@ -20,7 +20,7 @@ import StealthPlugin from 'puppeteer-extra-plugin-stealth';
|
|
|
20
20
|
// Option 3: Camoufox — Firefox-based browser with built-in fingerprint randomisation.
|
|
21
21
|
// Loaded dynamically (not at startup) to avoid ESM/CJS conflict when API imports crawler.
|
|
22
22
|
import { spawn, execSync, type ChildProcess } from 'node:child_process';
|
|
23
|
-
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { readFileSync, existsSync as existsSyncFs } from 'node:fs';
|
|
24
24
|
import { createRequire } from 'node:module';
|
|
25
25
|
|
|
26
26
|
stealthChromium.use(StealthPlugin());
|
|
@@ -510,6 +510,7 @@ export async function crawlProject(opts: {
|
|
|
510
510
|
// Upload screenshot to cloud storage if configured, returns cloudUrl or undefined
|
|
511
511
|
const uploadToCloud = async (localPath: string, key: string): Promise<string | undefined> => {
|
|
512
512
|
if (!opts.storageConfig) return undefined;
|
|
513
|
+
if (!existsSyncFs(localPath)) return undefined;
|
|
513
514
|
try {
|
|
514
515
|
const { uploadScreenshotToTenantStorage } = await import('@detiq/app-brain');
|
|
515
516
|
return await uploadScreenshotToTenantStorage(opts.storageConfig as any, localPath, key);
|
|
@@ -1339,7 +1340,7 @@ export async function crawlProject(opts: {
|
|
|
1339
1340
|
}
|
|
1340
1341
|
let checkpointSuccessCount = 0;
|
|
1341
1342
|
let maxSeenTotal = 0; // track high-water mark so progress % never goes backwards
|
|
1342
|
-
let lastEmittedPct =
|
|
1343
|
+
let lastEmittedPct = 20; // floor above auth-discovery + login phases (max ~19%)
|
|
1343
1344
|
const crawlLoopStart = Date.now();
|
|
1344
1345
|
const startUrl = page.url();
|
|
1345
1346
|
// Resume from previous segment's pending queue if provided
|
|
@@ -4313,13 +4314,15 @@ async function captureAuthDiscoveryScreen(
|
|
|
4313
4314
|
const screen = await AppBrain.saveScreen(opts.tenantId, opts.projectId, name, screenUrl, opts.jobId, source === 'entry' ? 'ENTRY_POINT' : 'SEED_URL').catch(() => null);
|
|
4314
4315
|
if (!screen) return { captured: false, finalUrl, flowType };
|
|
4315
4316
|
let authCloudUrl: string | undefined;
|
|
4316
|
-
if (opts.storageConfig) {
|
|
4317
|
+
if (opts.storageConfig && existsSyncFs(shotPath)) {
|
|
4317
4318
|
try {
|
|
4318
4319
|
const { uploadScreenshotToTenantStorage } = await import('@detiq/app-brain');
|
|
4319
4320
|
authCloudUrl = await uploadScreenshotToTenantStorage(opts.storageConfig as any, shotPath, `${opts.tenantId}/${opts.projectId}/screenshots/${path.basename(shotPath)}`);
|
|
4320
4321
|
} catch { /* non-fatal */ }
|
|
4321
4322
|
}
|
|
4322
|
-
|
|
4323
|
+
if (existsSyncFs(shotPath)) {
|
|
4324
|
+
await AppBrain.saveScreenshot(opts.tenantId, opts.projectId, screen.id, shotPath, screen.version, 'DESKTOP', authCloudUrl).catch(() => { });
|
|
4325
|
+
}
|
|
4323
4326
|
await AppBrain.saveCrawlEvidence(opts.tenantId, opts.projectId, {
|
|
4324
4327
|
screenId: screen.id,
|
|
4325
4328
|
jobId: opts.jobId,
|
package/src/job-queue.ts
CHANGED
|
@@ -1555,21 +1555,24 @@ async function processDiscoverJob(jobId: string, config: any, opts?: ProcessJobO
|
|
|
1555
1555
|
// This lets the frontend poll and see a growing list before the job finishes.
|
|
1556
1556
|
const allUrls = new Set<string>();
|
|
1557
1557
|
let lastPartialFlush = 0;
|
|
1558
|
+
let lastFlushedPct = 0; // high-water mark — progress never decreases
|
|
1558
1559
|
|
|
1559
1560
|
const flushPartialUrls = async (pct: number, step: string) => {
|
|
1560
1561
|
const now = Date.now();
|
|
1561
1562
|
// Flush to DB at most every 5 seconds to avoid excessive writes
|
|
1562
1563
|
if (now - lastPartialFlush < 5_000 && allUrls.size < 5) return;
|
|
1564
|
+
const monotonic = Math.max(pct, lastFlushedPct);
|
|
1565
|
+
lastFlushedPct = monotonic;
|
|
1563
1566
|
lastPartialFlush = now;
|
|
1564
1567
|
try {
|
|
1565
1568
|
await withTenant(tenantId, (tx) =>
|
|
1566
1569
|
tx.platformJob.update({
|
|
1567
1570
|
where: { id: jobId },
|
|
1568
|
-
data: { progress:
|
|
1571
|
+
data: { progress: monotonic, result: { step, partialUrls: Array.from(allUrls) } as any, heartbeatAt: new Date() },
|
|
1569
1572
|
})
|
|
1570
1573
|
);
|
|
1571
1574
|
} catch { /* non-fatal */ }
|
|
1572
|
-
await opts?.onProgress?.(
|
|
1575
|
+
await opts?.onProgress?.(monotonic);
|
|
1573
1576
|
};
|
|
1574
1577
|
|
|
1575
1578
|
const onProgress = async (pct: number, step: string) => {
|
package/src/tls-fingerprint.ts
CHANGED
|
@@ -82,7 +82,7 @@ export async function probeTlsFingerprint(host: string, port = 443): Promise<{
|
|
|
82
82
|
protocol: socket.getProtocol() ?? 'unknown',
|
|
83
83
|
cipher: socket.getCipher()?.name ?? 'unknown',
|
|
84
84
|
authorized: socket.authorized,
|
|
85
|
-
subjectCN: cert?.subject?.CN ?? null,
|
|
85
|
+
subjectCN: cert?.subject?.CN?.toString() ?? null,
|
|
86
86
|
});
|
|
87
87
|
socket.destroy();
|
|
88
88
|
});
|