@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/Dockerfile +5 -0
- package/dist/crawler.js +20 -7
- package/dist/crawler.js.map +2 -2
- package/dist/job-queue.js +29 -7
- package/dist/job-queue.js.map +2 -2
- package/package.json +1 -1
- package/src/crawler.ts +27 -6
- package/src/job-queue.ts +134 -92
- package/types/job-queue.d.ts +3 -1
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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);
|