UNPKG

@validkeys/ollypop-ts

Version:

Automatic TypeScript barrel file generator CLI.

143 lines 5.51 kB
import { promises as fs } from 'fs'; import { glob } from 'glob'; import path from 'path'; export class FileScanner { options; constructor(options = {}) { this.options = { followSymlinks: false, preserveExtensions: false, extensions: ['.ts', '.tsx'], validateExports: false, dryRun: false, ...options, }; } async scanSources(sources, excludePatterns = []) { const allFiles = []; for (const source of sources) { const files = await this.scanSource(source, excludePatterns); allFiles.push(...files); } const uniqueFiles = allFiles.filter((file, index, array) => array.findIndex((f) => f.path === file.path) === index); return uniqueFiles; } async scanSource(source, excludePatterns) { const { path: sourcePath, recursive = false, maxDepth, pattern = '*.ts', directoryPattern = false, indexFile = 'index.js', } = source; if (directoryPattern) { return this.scanDirectories(sourcePath, indexFile, excludePatterns, recursive, maxDepth); } let globPattern; if (recursive) { const depthPattern = maxDepth ? `{,${'/'.repeat(maxDepth - 1)}}` : '**'; globPattern = path.join(sourcePath, depthPattern, pattern); } else { globPattern = path.join(sourcePath, pattern); } const files = await glob(globPattern, { ignore: excludePatterns, absolute: false, }); const filteredFiles = files.filter((file) => { const ext = path.extname(file); return this.options.extensions.includes(ext); }); const fileInfos = []; for (const file of filteredFiles) { const fileInfo = await this.createFileInfo(file); if (fileInfo) { fileInfos.push(fileInfo); } } return fileInfos; } async scanDirectories(sourcePath, indexFile, excludePatterns, recursive = false, maxDepth) { const directories = []; try { const entries = await fs.readdir(sourcePath, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; const dirPath = path.join(sourcePath, entry.name); const indexPath = path.join(dirPath, indexFile); const isExcluded = excludePatterns.some((pattern) => { const globPattern = pattern.replace(/\*\*/g, '**'); return path.relative(process.cwd(), dirPath).includes(globPattern.replace('**/', '')); }); if (isExcluded) continue; try { await fs.access(indexPath); const relativeIndexPath = path.relative(process.cwd(), indexPath); const relativeFromOutput = path.relative(path.dirname(indexPath.replace(entry.name + '/' + indexFile, '')), indexPath); directories.push({ path: indexPath, name: entry.name, extension: path.extname(indexFile), relativePath: relativeIndexPath, directory: dirPath, }); } catch { } } if (recursive) { for (const entry of entries) { if (!entry.isDirectory()) continue; const dirPath = path.join(sourcePath, entry.name); const currentDepth = 1; if (!maxDepth || currentDepth < maxDepth) { const nestedDirs = await this.scanDirectories(dirPath, indexFile, excludePatterns, true, maxDepth ? maxDepth - 1 : undefined); directories.push(...nestedDirs); } } } } catch (error) { console.warn(`⚠️ Could not scan directory: ${sourcePath}`); } return directories; } async createFileInfo(filePath) { try { const stats = await fs.stat(filePath); if (!stats.isFile()) { return null; } const parsed = path.parse(filePath); const relativePath = path.relative(process.cwd(), filePath); return { path: filePath, name: parsed.name, extension: parsed.ext, relativePath, directory: parsed.dir, }; } catch (error) { console.warn(`⚠️ Could not process file: ${filePath}`); return null; } } async validateFile(filePath) { if (!this.options.validateExports) { return true; } try { const content = await fs.readFile(filePath, 'utf-8'); const hasExports = /export\s+/.test(content); if (!hasExports) { console.warn(`⚠️ File has no exports: ${filePath}`); return false; } return true; } catch (error) { console.warn(`⚠️ Could not validate file: ${filePath}`); return false; } } } //# sourceMappingURL=scanner.js.map