dir3
Version:
Generate directory tree files with customizable ignore patterns
184 lines (183 loc) • 6.53 kB
JavaScript
import { promises as fs } from 'fs';
import path from 'path';
import inquirer from 'inquirer';
class DirTreeGenerator {
constructor() {
this.ignoreFile = '.dirtree.ignore';
this.outputFile = 'directory-tree.txt';
this.ignorePatterns = [];
}
async init() {
try {
await this.checkAndCreateIgnoreFile();
await this.loadIgnorePatterns();
const tree = await this.generateTree('.');
await this.writeTreeToFile(tree);
console.log(`Directory tree generated successfully in ${this.outputFile}`);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error('Error generating directory tree:', errorMessage);
process.exit(1);
}
}
async checkAndCreateIgnoreFile() {
try {
await fs.access(this.ignoreFile);
console.log(`Using existing ${this.ignoreFile}`);
}
catch {
const answers = await inquirer.prompt([
{
type: 'confirm',
name: 'createIgnoreFile',
message: `${this.ignoreFile} not found. Create with default ignore patterns?`,
default: true
}
]);
if (answers.createIgnoreFile) {
await this.createDefaultIgnoreFile();
console.log(`Created ${this.ignoreFile} with default patterns`);
}
else {
const customAnswers = await inquirer.prompt([
{
type: 'input',
name: 'customPatterns',
message: 'Enter custom ignore patterns (comma-separated, or leave empty):\n Alternatively close this process and create a .dirtree.ignore file manually.',
default: ''
}
]);
const patterns = customAnswers.customPatterns
? customAnswers.customPatterns.split(',').map(p => p.trim()).filter(p => p)
: [];
await this.createIgnoreFile(patterns);
console.log(`Created ${this.ignoreFile} with custom patterns`);
}
}
}
async createDefaultIgnoreFile() {
const content = DirTreeGenerator.defaultIgnores.join('\n') + '\n';
await fs.writeFile(this.ignoreFile, content, 'utf8');
}
async createIgnoreFile(patterns) {
const content = patterns.length > 0 ? patterns.join('\n') + '\n' : '';
await fs.writeFile(this.ignoreFile, content, 'utf8');
}
async loadIgnorePatterns() {
try {
const content = await fs.readFile(this.ignoreFile, 'utf8');
this.ignorePatterns = content
.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
.map(pattern => this.createRegexFromPattern(pattern));
}
catch {
this.ignorePatterns = [];
}
}
createRegexFromPattern(pattern) {
const escaped = pattern
.replace(/\./g, '\\.')
.replace(/\*/g, '.*')
.replace(/\?/g, '.');
return new RegExp(`^${escaped}$`);
}
shouldIgnore(itemName, itemPath) {
return this.ignorePatterns.some(regex => {
return regex.test(itemName) || regex.test(path.basename(itemPath));
});
}
async generateTree(dirPath, prefix = '', isLast = true, depth = 0) {
if (depth > 50) {
return prefix + '└── [MAX DEPTH REACHED]\n';
}
let result = '';
try {
const items = await fs.readdir(dirPath, { withFileTypes: true });
const filteredItems = [];
for (const item of items) {
const itemPath = path.join(dirPath, item.name);
if (this.shouldIgnore(item.name, itemPath)) {
continue;
}
filteredItems.push({
name: item.name,
path: itemPath,
isDirectory: item.isDirectory()
});
}
filteredItems.sort((a, b) => {
if (a.isDirectory && !b.isDirectory)
return -1;
if (!a.isDirectory && b.isDirectory)
return 1;
return a.name.localeCompare(b.name);
});
for (let i = 0; i < filteredItems.length; i++) {
const item = filteredItems[i];
if (!item)
continue;
const isLastItem = i === filteredItems.length - 1;
const connector = isLastItem ? '└── ' : '├── ';
const icon = item.isDirectory ? '📁 ' : '📄 ';
result += prefix + connector + icon + item.name + '\n';
if (item.isDirectory) {
const nextPrefix = prefix + (isLastItem ? ' ' : '│ ');
result += await this.generateTree(item.path, nextPrefix, isLastItem, depth + 1);
}
}
}
catch {
result += prefix + '└── [ERROR: Cannot read directory]\n';
}
return result;
}
async writeTreeToFile(tree) {
const header = `Directory Tree - Generated with dirtree on ${new Date().toISOString()}\n` +
`Root: ${path.resolve('.')}\n` +
`Ignore file: ${this.ignoreFile}\n` +
'─'.repeat(80) + '\n\n';
const content = header + '📁 .\n' + tree;
await fs.writeFile(this.outputFile, content, 'utf8');
}
}
DirTreeGenerator.defaultIgnores = [
'node_modules',
'.git',
'.svn',
'.hg',
'dist',
'build',
'out',
'target',
'.next',
'.nuxt',
'__pycache__',
'.pytest_cache',
'venv',
'env',
'.venv',
'.env',
'vendor',
'.idea',
'.vscode',
'*.log',
'.DS_Store',
'Thumbs.db',
'coverage',
'.nyc_output',
'tmp',
'temp',
'.cache',
'.parcel-cache'
];
const generator = new DirTreeGenerator();
generator.init().catch(error => {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
console.error('Fatal error:', errorMessage);
process.exit(1);
});
export default DirTreeGenerator;