@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 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/core/package.json packages/core/
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
- RUN npm ci
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/core packages/core
35
- COPY packages/database packages/database
36
- COPY packages/app-brain packages/app-brain
37
- COPY packages/agents packages/agents
38
- COPY packages/crawler packages/crawler
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@detiq/crawler",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",
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 = 5; // clamp so progress never decreases even when queue grows
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
- await AppBrain.saveScreenshot(opts.tenantId, opts.projectId, screen.id, shotPath, screen.version, 'DESKTOP', authCloudUrl).catch(() => { });
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: pct, result: { step, partialUrls: Array.from(allUrls) } as any, heartbeatAt: new Date() },
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?.(pct);
1575
+ await opts?.onProgress?.(monotonic);
1573
1576
  };
1574
1577
 
1575
1578
  const onProgress = async (pct: number, step: string) => {
@@ -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
  });