import * as fs from 'fs'; import * as path from 'path'; import ignore from 'ignore'; import { glob } from 'glob'; export class FileUtils { private static instance: FileUtils; private ig: ReturnType; private constructor() { this.ig = ignore(); } static getInstance(): FileUtils { if (!FileUtils.instance) { FileUtils.instance = new FileUtils(); } return FileUtils.instance; } async loadGitignore(dir: string): Promise { const gitignorePath = path.join(dir, '.gitignore'); if (await this.fileExists(gitignorePath)) { const content = await this.readFile(gitignorePath); const patterns = content .split('\n') .filter(line => line.trim() && !line.trim().startsWith('#')); this.ig.add(patterns); } } addCustomPatterns(patterns: string[]): void { this.ig.add(patterns); } isIgnored(filePath: string): boolean { const relativePath = path.relative(process.cwd(), filePath); return this.ig.ignores(relativePath); } resolveDirectory(dir: string): string { if (path.isAbsolute(dir)) { return dir; } return path.resolve(process.cwd(), dir); } async getFiles( dir: string, includes: string[], excludes: string[] ): Promise { const allFiles: string[] = []; for (const pattern of includes) { const files = await glob(pattern, { cwd: dir, ignore: excludes, absolute: true, }); allFiles.push(...files); } const uniqueFiles = [...new Set(allFiles)].filter( file => !this.isIgnored(file) ); return uniqueFiles.sort(); } async fileExists(filePath: string): Promise { try { await fs.promises.access(filePath, fs.constants.F_OK); return true; } catch { return false; } } async readFile(filePath: string): Promise { return fs.promises.readFile(filePath, 'utf-8'); } async writeFile( filePath: string, content: string ): Promise { const dir = path.dirname(filePath); if (!(await this.fileExists(dir))) { await fs.promises.mkdir(dir, { recursive: true }); } await fs.promises.writeFile(filePath, content, 'utf-8'); } async getFileSize(filePath: string): Promise { const stats = await fs.promises.stat(filePath); return stats.size; } async getDirectoryContents( dir: string ): Promise { try { const entries = await fs.promises.readdir(dir, { withFileTypes: true, }); return entries.map(entry => entry.name); } catch { return []; } } getFileExtension(filePath: string): string { return path.extname(filePath).toLowerCase(); } isTextFile(filePath: string): boolean { const textExtensions = [ '.ts', '.js', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.h', '.hpp', '.json', '.yaml', '.yml', '.xml', '.html', '.css', '.scss', '.md', '.txt', '.sql', '.sh', '.bash', ]; return textExtensions.includes(this.getFileExtension(filePath)); } }