(cwd: string)
| 54 | } |
| 55 | |
| 56 | export async function detectProjectInfo(cwd: string): Promise<ProjectInfo> { |
| 57 | const info: ProjectInfo = { |
| 58 | languages: [], |
| 59 | hasGit: await fileExists(path.join(cwd, '.git')), |
| 60 | }; |
| 61 | |
| 62 | // Detect languages |
| 63 | for (const [lang, files] of Object.entries(LANGUAGE_FILES)) { |
| 64 | for (const f of files) { |
| 65 | if (await fileExists(path.join(cwd, f))) { |
| 66 | info.languages.push(lang); |
| 67 | break; |
| 68 | } |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | // Node-specific deeper detection |
| 73 | const pkgPath = path.join(cwd, 'package.json'); |
| 74 | if (await fileExists(pkgPath)) { |
| 75 | try { |
| 76 | const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8')); |
| 77 | const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; |
| 78 | |
| 79 | // Framework |
| 80 | for (const [fw, predicate] of FRAMEWORK_DETECTION) { |
| 81 | if (predicate(allDeps)) { |
| 82 | info.framework = fw; |
| 83 | break; |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | // Package manager |
| 88 | if (await fileExists(path.join(cwd, 'pnpm-lock.yaml'))) info.packageManager = 'pnpm'; |
| 89 | else if (await fileExists(path.join(cwd, 'yarn.lock'))) info.packageManager = 'yarn'; |
| 90 | else if (await fileExists(path.join(cwd, 'bun.lockb'))) info.packageManager = 'bun'; |
| 91 | else if (await fileExists(path.join(cwd, 'package-lock.json'))) info.packageManager = 'npm'; |
| 92 | else info.packageManager = 'npm'; |
| 93 | |
| 94 | // Test runner |
| 95 | if ('vitest' in allDeps) info.testRunner = 'vitest'; |
| 96 | else if ('jest' in allDeps) info.testRunner = 'jest'; |
| 97 | else if ('mocha' in allDeps) info.testRunner = 'mocha'; |
| 98 | else if (pkg.scripts?.test) info.testRunner = 'npm test'; |
| 99 | |
| 100 | // Linter |
| 101 | if ('eslint' in allDeps) info.linter = 'eslint'; |
| 102 | else if ('biome' in allDeps || '@biomejs/biome' in allDeps) info.linter = 'biome'; |
| 103 | |
| 104 | // Type checker |
| 105 | if ('typescript' in allDeps) info.typeChecker = 'tsc'; |
| 106 | } catch (err: any) { |
| 107 | // ENOENT (package.json vanished between the exists-check and read) is |
| 108 | // benign — just skip deeper detection. A parse error means the file is |
| 109 | // present but malformed; log it so the degraded detection is traceable. |
| 110 | if (err?.code !== 'ENOENT') { |
| 111 | logger.debug(`Failed to parse ${pkgPath}: ${err?.message ?? err}`); |
| 112 | } |
| 113 | } |
no test coverage detected