@validkeys/ollypop-ts
Version:
Automatic TypeScript barrel file generator CLI.
143 lines • 5.51 kB
JavaScript
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