// ============================================================================ // AI Testing Suite - File Utilities // ============================================================================ import * as fs from 'fs'; import * as path from 'path'; import { ProjectFile } from '../types'; const IGNORE_DIRS = new Set([ 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '.nyc_output', '.cache', '.tmp', '__pycache__', '.tsbuildinfo', '.turbo', '.vercel', '.serverless', ]); const SOURCE_EXTENSIONS = new Set([ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', ]); const CONFIG_FILES = new Set([ 'package.json', 'tsconfig.json', 'jest.config.js', 'jest.config.ts', '.eslintrc.js', '.eslintrc.json', '.prettierrc', 'webpack.config.js', 'vite.config.ts', 'next.config.js', 'nest-cli.json', '.babelrc', 'rollup.config.js', 'turbo.json', 'vitest.config.ts', ]); const TEST_PATTERNS = [ /\.test\.(ts|tsx|js|jsx)$/, /\.spec\.(ts|tsx|js|jsx)$/, /__tests__\//, /\.e2e\.(ts|tsx|js|jsx)$/, ]; export function scanDirectory(dirPath: string): ProjectFile[] { const files: ProjectFile[] = []; walkDirectory(dirPath, dirPath, files); return files; } function walkDirectory(currentPath: string, rootPath: string, files: ProjectFile[]): void { let entries: fs.Dirent[]; try { entries = fs.readdirSync(currentPath, { withFileTypes: true }); } catch { return; } for (const entry of entries) { const fullPath = path.join(currentPath, entry.name); if (entry.isDirectory()) { if (!IGNORE_DIRS.has(entry.name) && !entry.name.startsWith('.')) { walkDirectory(fullPath, rootPath, files); } continue; } if (entry.isFile()) { const ext = path.extname(entry.name); const relativePath = path.relative(rootPath, fullPath); let stats: fs.Stats; try { stats = fs.statSync(fullPath); } catch { continue; } files.push({ path: fullPath, relativePath, name: entry.name, extension: ext, size: stats.size, language: getLanguage(ext), type: getFileType(entry.name, relativePath, ext), }); } } } function getLanguage(ext: string): ProjectFile['language'] { if (['.ts', '.tsx'].includes(ext)) return 'typescript'; if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext)) return 'javascript'; if (ext === '.json') return 'json'; if (['.yaml', '.yml'].includes(ext)) return 'yaml'; return 'other'; } function getFileType(name: string, relativePath: string, ext: string): ProjectFile['type'] { if (TEST_PATTERNS.some(p => p.test(relativePath))) return 'test'; if (CONFIG_FILES.has(name)) return 'config'; if (['.md', '.txt', '.rst'].includes(ext)) return 'documentation'; if (SOURCE_EXTENSIONS.has(ext)) return 'source'; return 'asset'; } export function readFileContent(filePath: string): string { try { return fs.readFileSync(filePath, 'utf-8'); } catch { return ''; } } export function writeFileContent(filePath: string, content: string): void { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(filePath, content, 'utf-8'); } export function ensureDir(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } export function getDirectories(dirPath: string): string[] { const dirs: string[] = []; function walk(current: string): void { let entries: fs.Dirent[]; try { entries = fs.readdirSync(current, { withFileTypes: true }); } catch { return; } for (const entry of entries) { if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith('.')) { const fullPath = path.join(current, entry.name); dirs.push(path.relative(dirPath, fullPath)); walk(fullPath); } } } walk(dirPath); return dirs; } export function countLines(filePath: string): number { try { const content = fs.readFileSync(filePath, 'utf-8'); return content.split('\n').length; } catch { return 0; } } export function findEntryPoints(projectPath: string): string[] { const possibleEntries = [ 'src/index.ts', 'src/index.js', 'src/main.ts', 'src/main.js', 'src/app.ts', 'src/app.js', 'src/server.ts', 'src/server.js', 'index.ts', 'index.js', 'main.ts', 'main.js', 'app.ts', 'app.js', 'server.ts', 'server.js', ]; const entries: string[] = []; for (const entry of possibleEntries) { const fullPath = path.join(projectPath, entry); if (fs.existsSync(fullPath)) { entries.push(entry); } } // Also check package.json main/bin const pkgPath = path.join(projectPath, 'package.json'); if (fs.existsSync(pkgPath)) { try { const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); if (pkg.main && !entries.includes(pkg.main)) { entries.push(pkg.main); } } catch { // ignore } } return entries; } export function findConfigFiles(projectPath: string): string[] { const configs: string[] = []; try { const topLevel = fs.readdirSync(projectPath); for (const name of topLevel) { if (CONFIG_FILES.has(name) || name.endsWith('.config.js') || name.endsWith('.config.ts')) { configs.push(name); } } } catch { // ignore } return configs; } export function detectPackageManager(projectPath: string): 'npm' | 'yarn' | 'pnpm' { if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm'; if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) return 'yarn'; return 'npm'; } export function loadPackageJson(projectPath: string): Record | null { const pkgPath = path.join(projectPath, 'package.json'); try { return JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); } catch { return null; } }