import fs from 'fs/promises';
import path from 'path';
import { GitignoreHandler } from './gitignoreHandler.js';
export interface FileInfo {
absolutePath: string;
relativePath: string;
extension: string;
}
/**
* Recursively scans directories for code files
*/
export class FileScanner {
private gitignoreHandler: GitignoreHandler;
private supportedExtensions = new Set([
'.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
'.py', '.java', '.go', '.rs', '.c', '.cpp', '.cc', '.h', '.hpp',
'.php', '.rb', '.cs', '.swift', '.kt', '.scala', '.r'
]);
constructor(gitignoreHandler: GitignoreHandler) {
this.gitignoreHandler = gitignoreHandler;
}
/**
* Scan directory recursively for code files
*/
async scan(basePath: string): Promise<FileInfo[]> {
const files: FileInfo[] = [];
await this.scanRecursive(basePath, basePath, files);
return files;
}
private async scanRecursive(
basePath: string,
currentPath: string,
files: FileInfo[]
): Promise<void> {
let entries;
try {
entries = await fs.readdir(currentPath, { withFileTypes: true });
} catch (error) {
// Directory not accessible, skip it
return;
}
for (const entry of entries) {
const fullPath = path.join(currentPath, entry.name);
const relativePath = path.relative(basePath, fullPath);
// Check if should be ignored
if (this.gitignoreHandler.shouldIgnore(relativePath)) {
continue;
}
if (entry.isDirectory()) {
await this.scanRecursive(basePath, fullPath, files);
} else if (entry.isFile()) {
const ext = path.extname(entry.name).toLowerCase();
if (this.supportedExtensions.has(ext)) {
files.push({
absolutePath: fullPath,
relativePath,
extension: ext
});
}
}
}
}
}