@detiq/crawler 1.1.21 → 1.1.23

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/job-queue.js CHANGED
@@ -85,6 +85,7 @@ const WORKER_ID = `${os.hostname()}:${process.pid}`;
85
85
  const STALE_JOB_TIMEOUT_MINUTES = Number(process.env.JOB_STALE_TIMEOUT_MINUTES) || 30;
86
86
  const WORKER_POLL_INTERVAL_MS = Number(process.env.JOB_WORKER_POLL_INTERVAL_MS) || 15e3;
87
87
  const WORKER_CONCURRENCY = Number(process.env.JOB_WORKER_CONCURRENCY) || 8;
88
+ const MAX_CONCURRENT_DISCOVER = Number(process.env.MAX_CONCURRENT_DISCOVER) || 2;
88
89
  const JOB_HEARTBEAT_INTERVAL_MS = Number(process.env.JOB_HEARTBEAT_INTERVAL_MS) || 15e3;
89
90
  const TEST_EXEC_CONCURRENCY = Number(process.env.TEST_EXEC_CONCURRENCY) || 5;
90
91
  let jobDispatchTable = null;
@@ -98,12 +99,17 @@ async function claimSpecificJob(jobId) {
98
99
  return rows[0] ?? null;
99
100
  }
100
101
  async function claimNextQueuedJob() {
102
+ const maxDiscover = MAX_CONCURRENT_DISCOVER;
101
103
  const rows = await prismaAdmin.$queryRaw`
102
104
  UPDATE "PlatformJob"
103
105
  SET status = 'RUNNING', "startedAt" = NOW(), "heartbeatAt" = NOW(), "workerId" = ${WORKER_ID}
104
106
  WHERE id = (
105
107
  SELECT id FROM "PlatformJob"
106
108
  WHERE status = 'QUEUED'
109
+ AND (
110
+ type != 'DISCOVER'
111
+ OR (SELECT COUNT(*) FROM "PlatformJob" WHERE status = 'RUNNING' AND type = 'DISCOVER') < ${maxDiscover}
112
+ )
107
113
  ORDER BY "queuedAt" ASC
108
114
  FOR UPDATE SKIP LOCKED
109
115
  LIMIT 1
@@ -206,10 +212,15 @@ function startJobWorkerLoop(opts) {
206
212
  logger.error({ err: String(err) }, "[job-worker] stale-job sweep failed");
207
213
  }
208
214
  try {
209
- for (let i = 0; i < concurrency; i++) {
210
- const job = await claimNextQueuedJob();
211
- if (!job) break;
212
- void dispatchClaimedJob(job);
215
+ const pauseRow = await prismaAdmin.systemConfig.findUnique({ where: { key: "queue.paused" } });
216
+ if (pauseRow?.value === "true") {
217
+ logger.info("[job-worker] queue is paused \u2014 skipping claim");
218
+ } else {
219
+ for (let i = 0; i < concurrency; i++) {
220
+ const job = await claimNextQueuedJob();
221
+ if (!job) break;
222
+ void dispatchClaimedJob(job);
223
+ }
213
224
  }
214
225
  } catch (err) {
215
226
  logger.error({ err: String(err) }, "[job-worker] claim loop failed");
@@ -1530,7 +1541,7 @@ async function processDiscoverJob(jobId, config, opts) {
1530
1541
  }
1531
1542
  logger.info({ count: allUrls.size }, "[discover] Phase 3 auth spider done");
1532
1543
  }
1533
- const discoveredUrls = Array.from(allUrls);
1544
+ const discoveredUrls = Array.from(allUrls).filter((u) => u.startsWith("http"));
1534
1545
  const existingCfg = await AppBrain.getCrawlConfig(tenantId, projectId).catch(() => ({}));
1535
1546
  await AppBrain.saveCrawlConfig(tenantId, projectId, {
1536
1547
  ...existingCfg,
@@ -1941,9 +1952,20 @@ async function processCaptureStepsJob(jobId, config) {
1941
1952
  ]);
1942
1953
  if (!tc) throw new Error("Test case not found");
1943
1954
  const rawSteps = Array.isArray(tc.steps) ? tc.steps : [];
1944
- const stepTexts = rawSteps.map(captureStepText).filter(Boolean);
1955
+ if (rawSteps.length === 0) throw new Error("Test case has no steps to capture");
1956
+ const _tdi = tc.testDataInline ?? {};
1957
+ const _tdiStartUrl = typeof _tdi["startUrl"] === "string" && _tdi["startUrl"].startsWith("http") ? _tdi["startUrl"] : "";
1958
+ const _recordedUrls = Array.isArray(_tdi["recordedUrls"]) ? _tdi["recordedUrls"].map(String) : [];
1959
+ const _recordedStartUrl = _recordedUrls.find((u) => u.startsWith("http")) ?? "";
1960
+ const baseUrl = config.baseUrl || _tdiStartUrl || _recordedStartUrl || tc?.screen?.project?.appUrl || crawlCfg?.appUrl || "";
1961
+ const _envTokenMap = {};
1962
+ for (const [k, v] of Object.entries(_tdi)) {
1963
+ if (typeof v === "string" && v.startsWith("http")) _envTokenMap[k] = v;
1964
+ }
1965
+ if (_recordedStartUrl && !_envTokenMap["startUrl"]) _envTokenMap["startUrl"] = _recordedStartUrl;
1966
+ const resolveStepText = (text) => text.replace(/\{\{\s*fze\.env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g, (_m, k) => _envTokenMap[k] ?? `[${k}]`).replace(/\[env:([A-Za-z_][A-Za-z0-9_]*)\]/g, (_m, k) => _envTokenMap[k] ?? `[${k}]`);
1967
+ const stepTexts = rawSteps.map(captureStepText).filter(Boolean).map(resolveStepText);
1945
1968
  if (stepTexts.length === 0) throw new Error("Test case has no steps to capture");
1946
- const baseUrl = config.baseUrl || tc?.screen?.project?.appUrl || crawlCfg?.appUrl || "";
1947
1969
  let plan = await generateExecutionPlan(tenantId, { ...tc, steps: stepTexts }, baseUrl).catch(() => []);
1948
1970
  plan = Array.isArray(plan) ? plan.filter(Boolean).map((step, idx) => ({ ...step, stepNumber: idx + 1 })) : [];
1949
1971
  if (plan.length === 0) plan = fallbackExecutionPlan(rawSteps);