@detiq/crawler 1.1.10 → 1.1.11

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/job-queue.ts +136 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@detiq/crawler",
3
- "version": "1.1.10",
3
+ "version": "1.1.11",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public",
package/src/job-queue.ts CHANGED
@@ -2333,6 +2333,126 @@ async function processRepoScanJob(jobId: string, config: any) {
2333
2333
  }
2334
2334
  }
2335
2335
 
2336
+ // Fetch and parse framework config files (playwright.config.ts, cypress.config.ts, tsconfig.json)
2337
+ // Fetch and parse framework config files for all supported frameworks.
2338
+ // Pure regex/JSON — no LLM. Results override LLM inference for path detection.
2339
+ // Covers: playwright, cypress, jest, pytest, junit/maven/gradle, selenium-java/py,
2340
+ // webdriverio, cucumber — all frameworks in DetectedFramework.
2341
+ const configPaths: {
2342
+ testDir?: string; fixturesFolder?: string; baseURL?: string;
2343
+ tsconfigPaths?: Record<string, string[]>; tsconfigBaseUrl?: string;
2344
+ javaTestSourceDir?: string; jestModuleNameMapper?: Record<string, string>;
2345
+ } = {};
2346
+ try {
2347
+ const pathSet = new Set(treeResult.paths as string[]);
2348
+ const lang = framework.includes('java') ? 'java'
2349
+ : (framework.includes('py') || framework === 'pytest') ? 'python' : 'js';
2350
+
2351
+ const configFilesToFetch: Array<{ path: string; type: string }> = [];
2352
+ const tryAdd = (candidates: string[], type: string) => {
2353
+ for (const c of candidates) { if (pathSet.has(c)) { configFilesToFetch.push({ path: c, type }); break; } }
2354
+ };
2355
+
2356
+ if (lang === 'js') {
2357
+ if (framework.startsWith('playwright')) tryAdd(['playwright.config.ts', 'playwright.config.js'], 'playwright');
2358
+ if (framework.startsWith('cypress')) tryAdd(['cypress.config.ts', 'cypress.config.js'], 'cypress');
2359
+ if (framework === 'jest-ts' || framework === 'jest-js') {
2360
+ tryAdd(['jest.config.ts', 'jest.config.js', 'jest.config.mjs'], 'jest');
2361
+ tryAdd(['package.json'], 'package-json');
2362
+ }
2363
+ if (framework === 'webdriverio') tryAdd(['wdio.conf.ts', 'wdio.conf.js', 'wdio.config.ts', 'wdio.config.js'], 'wdio');
2364
+ if (framework === 'cucumber' || framework === 'gherkin') {
2365
+ tryAdd(['.cucumber.js', '.cucumber.cjs', 'cucumber.json', '.cucumber.yaml', '.cucumber.yml'], 'cucumber');
2366
+ tryAdd(['package.json'], 'package-json');
2367
+ }
2368
+ tryAdd(['tsconfig.json'], 'tsconfig');
2369
+ } else if (lang === 'python') {
2370
+ tryAdd(['pytest.ini'], 'pytest-ini');
2371
+ tryAdd(['pyproject.toml'], 'pyproject-toml');
2372
+ tryAdd(['setup.cfg'], 'setup-cfg');
2373
+ } else if (lang === 'java') {
2374
+ tryAdd(['pom.xml'], 'pom-xml');
2375
+ tryAdd(['build.gradle.kts', 'build.gradle'], 'build-gradle');
2376
+ tryAdd(['testng.xml', 'src/test/resources/testng.xml'], 'testng-xml');
2377
+ }
2378
+
2379
+ // Inline parsers — mirror parseFrameworkConfig from @detiq/agents (kept inline to avoid build dep)
2380
+ const normDir = (s: string) => s.replace(/^\.\//, '').replace(/\/$/, '');
2381
+ const globRoot = (pattern: string): string | null => {
2382
+ const root = pattern.replace(/\/\*\*.*$/, '').replace(/\/\*[^*].*$/, '');
2383
+ return (root && !root.startsWith('*') && !root.startsWith('{')) ? normDir(root) : null;
2384
+ };
2385
+
2386
+ const fetched = await Promise.all(
2387
+ configFilesToFetch.map(async ({ path: cfgPath, type }) => {
2388
+ const content = await fetchFileContent(token, conn.repoFullName, cfgPath).catch(() => null);
2389
+ if (!content) return null;
2390
+ const r: typeof configPaths = {};
2391
+
2392
+ if (type === 'playwright') {
2393
+ const m = content.match(/\btestDir\s*:\s*['"`]([^'"`]+)['"`]/);
2394
+ if (m) r.testDir = normDir(m[1]);
2395
+ const bm = content.match(/\bbaseURL\s*:\s*['"`]([^'"`]+)['"`]/);
2396
+ if (bm) r.baseURL = bm[1];
2397
+ } else if (type === 'cypress') {
2398
+ const sm = content.match(/\bspecPattern\s*:\s*['"`]([^'"`]+)['"`]/);
2399
+ if (sm) { const gr = globRoot(sm[1]); if (gr) r.testDir = gr; }
2400
+ const fm = content.match(/\bfixturesFolder\s*:\s*['"`]([^'"`]+)['"`]/);
2401
+ if (fm) r.fixturesFolder = normDir(fm[1]);
2402
+ const bm = content.match(/\bbaseUrl\s*:\s*['"`]([^'"`]+)['"`]/);
2403
+ if (bm) r.baseURL = bm[1];
2404
+ } else if (type === 'jest') {
2405
+ const rm = content.match(/\broots\s*:\s*\[([^\]]+)\]/);
2406
+ if (rm) { const fr = rm[1].match(/['"`]([^'"`]+)['"`]/)?.[1]; if (fr) r.testDir = fr.replace(/<rootDir>\/?/, '').replace(/\/$/, '') || 'src'; }
2407
+ if (!r.testDir) { const tm = content.match(/\btestMatch\s*:\s*\[([^\]]+)\]/); if (tm) { const fp = tm[1].match(/['"`]([^'"`]+)['"`]/)?.[1]; if (fp) { const gr = globRoot(fp.replace(/<rootDir>\/?/, '')); if (gr) r.testDir = gr; } } }
2408
+ const mm = content.match(/\bmoduleNameMapper\s*:\s*\{([^}]+)\}/s);
2409
+ if (mm) { const map: Record<string, string> = {}; for (const [, k, v] of mm[1].matchAll(/['"`]([^'"`]+)['"`]\s*:\s*['"`]([^'"`]+)['"`]/g)) map[k] = v; if (Object.keys(map).length) r.jestModuleNameMapper = map; }
2410
+ } else if (type === 'package-json') {
2411
+ try { const p = JSON.parse(content) as any; const j = p?.jest ?? {}; if (Array.isArray(j.roots) && j.roots[0]) r.testDir = String(j.roots[0]).replace(/<rootDir>\/?/, '').replace(/\/$/, '') || 'src'; if (!r.testDir && Array.isArray(j.testMatch) && j.testMatch[0]) { const gr = globRoot(String(j.testMatch[0]).replace(/<rootDir>\/?/, '')); if (gr) r.testDir = gr; } if (j.moduleNameMapper && typeof j.moduleNameMapper === 'object') r.jestModuleNameMapper = j.moduleNameMapper; } catch { /* ignore */ }
2412
+ } else if (type === 'tsconfig') {
2413
+ try { const stripped = content.replace(/\/\/[^\n]*/g, '').replace(/,(\s*[}\]])/g, '$1'); const p = JSON.parse(stripped) as any; const opts = p?.compilerOptions ?? {}; if (opts.paths && typeof opts.paths === 'object') r.tsconfigPaths = opts.paths; if (typeof opts.baseUrl === 'string') r.tsconfigBaseUrl = opts.baseUrl; } catch { /* ignore */ }
2414
+ } else if (type === 'pytest-ini') {
2415
+ const m = content.match(/^\s*testpaths\s*=\s*(.+)$/m);
2416
+ if (m) { const first = m[1].trim().split(/\s+/)[0]; if (first) r.testDir = normDir(first); }
2417
+ } else if (type === 'pyproject-toml') {
2418
+ const m = content.match(/testpaths\s*=\s*\[([^\]]+)\]/);
2419
+ if (m) { const first = m[1].match(/['"]([^'"]+)['"]/)?.[1]; if (first) r.testDir = normDir(first); }
2420
+ if (!r.testDir) { const m2 = content.match(/testpaths\s*=\s*['"]([^'"]+)['"]/); if (m2) r.testDir = normDir(m2[1]); }
2421
+ } else if (type === 'setup-cfg') {
2422
+ const m = content.match(/^\s*testpaths\s*=\s*(.+)$/m);
2423
+ if (m) { const first = m[1].trim().split(/[\s,]+/)[0]; if (first) r.testDir = normDir(first); }
2424
+ } else if (type === 'pom-xml') {
2425
+ const m = content.match(/<testSourceDirectory>\s*([^<]+)\s*<\/testSourceDirectory>/);
2426
+ if (m) r.javaTestSourceDir = normDir(m[1].trim());
2427
+ } else if (type === 'build-gradle') {
2428
+ const m = content.match(/srcDir[s]?\s*[=([]?\s*['"`]([^'"`]+)['"`]/) ?? content.match(/srcDir\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/);
2429
+ if (m) r.javaTestSourceDir = normDir(m[1]);
2430
+ } else if (type === 'wdio') {
2431
+ const sm = content.match(/\bspecs\s*:\s*\[([^\]]+)\]/);
2432
+ if (sm) { const fp = sm[1].match(/['"`]([^'"`]+)['"`]/)?.[1]; if (fp) { const gr = globRoot(fp); if (gr) r.testDir = gr; } }
2433
+ const bm = content.match(/\bbaseUrl\s*:\s*['"`]([^'"`]+)['"`]/);
2434
+ if (bm) r.baseURL = bm[1];
2435
+ } else if (type === 'cucumber') {
2436
+ const pm = content.match(/\bpaths\s*:\s*\[([^\]]+)\]/);
2437
+ if (pm) { const fp = pm[1].match(/['"`]([^'"`]+)['"`]/)?.[1]; if (fp) { const gr = globRoot(fp); if (gr) r.testDir = gr; } }
2438
+ if (!r.testDir) { try { const p = JSON.parse(content) as any; const paths = p?.default?.paths ?? p?.paths; if (Array.isArray(paths) && paths[0]) { const gr = globRoot(String(paths[0])); if (gr) r.testDir = gr; } } catch { /* not JSON */ } }
2439
+ }
2440
+ return r;
2441
+ })
2442
+ );
2443
+
2444
+ for (const parsed of fetched) {
2445
+ if (!parsed) continue;
2446
+ if (parsed.testDir && !configPaths.testDir) configPaths.testDir = parsed.testDir;
2447
+ if (parsed.fixturesFolder && !configPaths.fixturesFolder) configPaths.fixturesFolder = parsed.fixturesFolder;
2448
+ if (parsed.baseURL && !configPaths.baseURL) configPaths.baseURL = parsed.baseURL;
2449
+ if (parsed.tsconfigPaths && !configPaths.tsconfigPaths) configPaths.tsconfigPaths = parsed.tsconfigPaths;
2450
+ if (parsed.tsconfigBaseUrl && !configPaths.tsconfigBaseUrl) configPaths.tsconfigBaseUrl = parsed.tsconfigBaseUrl;
2451
+ if (parsed.javaTestSourceDir && !configPaths.javaTestSourceDir) configPaths.javaTestSourceDir = parsed.javaTestSourceDir;
2452
+ if (parsed.jestModuleNameMapper && !configPaths.jestModuleNameMapper) configPaths.jestModuleNameMapper = parsed.jestModuleNameMapper;
2453
+ }
2454
+ } catch { /* non-fatal */ }
2455
+
2336
2456
  // Extract style profile for aligned generation
2337
2457
  // For Java, include main source paths so LLM can detect page object location in src/main/java/
2338
2458
  // Fall back to existing styleProfile from DB if extraction fails or returns null.
@@ -2362,6 +2482,22 @@ async function processRepoScanJob(jobId: string, config: any) {
2362
2482
  } catch { /* non-fatal */ }
2363
2483
  }
2364
2484
 
2485
+ // Merge config file ground-truth into styleProfile (Priority 0 — highest, no LLM inference needed).
2486
+ // Java: javaTestSourceDir also sets testLocation (equivalent field for Java).
2487
+ if (styleProfile && Object.keys(configPaths).length > 0) {
2488
+ if (configPaths.testDir) styleProfile.configTestDir = configPaths.testDir;
2489
+ if (configPaths.javaTestSourceDir) {
2490
+ styleProfile.configTestDir = configPaths.javaTestSourceDir;
2491
+ // Also set testLocation if not already present from LLM scan
2492
+ if (!styleProfile.testLocation) styleProfile.testLocation = configPaths.javaTestSourceDir;
2493
+ }
2494
+ if (configPaths.fixturesFolder) styleProfile.configFixturesFolder = configPaths.fixturesFolder;
2495
+ if (configPaths.baseURL) styleProfile.configBaseURL = configPaths.baseURL;
2496
+ if (configPaths.tsconfigPaths) styleProfile.tsconfigPaths = configPaths.tsconfigPaths;
2497
+ if (configPaths.tsconfigBaseUrl) styleProfile.tsconfigBaseUrl = configPaths.tsconfigBaseUrl;
2498
+ if (configPaths.jestModuleNameMapper) styleProfile.jestModuleNameMapper = configPaths.jestModuleNameMapper;
2499
+ }
2500
+
2365
2501
  // Extract page object inventory — finds POM/page classes in non-test files
2366
2502
  // Fall back to existing inventory from DB if fetch fails.
2367
2503
  let pageObjectInventory: any[] = (conn.pageObjectInventory as any[]) ?? [];