@avdl/folder-walker
Version:
A powerful, fluent, and configurable directory traversal utility for Node.js with TypeScript support
222 lines (175 loc) • 6.36 kB
Markdown
# @avdl/folder-walker
A powerful, fluent, and configurable directory traversal utility for Node.js with TypeScript support.
## Features
- 🚀 **Fluent API** - Chain methods for intuitive configuration
- 📁 **Flexible traversal** - Files, folders, or both
- 🔍 **Smart filtering** - By extension, pattern, size, and more
- 📖 **Content reading** - Optional file content loading
- ⚡ **High performance** - Async iterators and parallel processing
- 🛡️ **Type-safe** - Full TypeScript support with great inference
- 0️⃣ **Zero dependencies** - Lightweight and secure
## Installation
```bash
npm install @avdl/folder-walker
# or
yarn add @avdl/folder-walker
```
## Quick Start
```typescript
import { FolderWalker } from '@avdl/folder-walker';
// Basic usage - iterate over all files
const walker = new FolderWalker();
for await (const file of walker.files('.')) {
console.log(file.path);
}
// Advanced configuration with async operations
const configuredWalker = new FolderWalker()
.withDepth(3)
.withExtensions(['.ts', '.js'])
.withFileContents('utf-8')
.exclude(['node_modules', '.git'])
.onFile(async ({ path, content }) => {
// Async operations like saving to database, uploading, etc.
await processFile(path, content);
console.log(`Processed ${path}`);
});
await configuredWalker.execute('./src');
```
## API Reference
### Configuration Methods
- `withDepth(n)` - Set maximum traversal depth
- `withFilesOnly()` - Only traverse files
- `withFoldersOnly()` - Only traverse folders
- `withFilesAndFolders()` - Traverse both (default)
- `withFileContents(encoding)` - Read file contents
- `withExtensions(extensions)` - Filter by file extensions
- `withPattern(regex)` - Filter by filename pattern
- `exclude(patterns)` - Exclude paths
- `withHidden()` - Include hidden files
- `withSymlinks()` - Follow symbolic links
- `withMaxFileSize(bytes)` - Skip large files
- `withFilter(asyncFilter)` - Add custom async filter function
### Callbacks (Async Support)
- `onFile(handler)` - Process each file (supports async functions)
- `onFolder(handler)` - Process each folder (supports async functions)
- `onError(handler)` - Handle errors
### Result Collection
- `withFileCollector()` - Collect file handler results into array
- `withFileCollector(collectorFn)` - Transform collected results with custom function
- `withFolderCollector()` - Collect folder handler results into array
- `withFolderCollector(collectorFn)` - Transform collected results with custom function
### Execution
- `execute(path)` - Run with callbacks, returns void (streaming) or collected results
- `walker.files(path)` - Async iterator for files
- `walker.folders(path)` - Async iterator for folders
- `walker.entries(path)` - Async iterator for all entries
## Running Examples
Try the included examples to see folder-walker in action:
```bash
# List all available examples
npm run examples:list
# Run individual examples
npm run example:basic # Basic usage patterns
npm run example:async # Async operations
npm run example:filters # Custom filtering
npm run example:collectors # Type-safe collection
# Run all examples
npm run examples
```
## Examples
### Find all TypeScript files
```typescript
const walker = new FolderWalker()
.withExtensions(['.ts', '.tsx'])
.exclude(['node_modules', 'dist']);
for await (const file of walker.files('.')) {
console.log(file.path);
}
```
### Process markdown files with async operations
```typescript
await new FolderWalker()
.withExtensions(['.md'])
.withFileContents('utf-8')
.onFile(async ({ path, content }) => {
const wordCount = content.split(/\s+/).length;
await saveToDatabase(path, wordCount); // Async operation
console.log(`${path}: ${wordCount} words`);
})
.execute('./docs');
```
### Type-safe result collection
```typescript
// Streaming mode (no collection) - returns void
await new FolderWalker()
.onFile(({ path }) => {
console.log(`Processing: ${path}`); // void return
})
.execute('./src');
// Collect results - returns ProcessResult[]
const results = await new FolderWalker()
.onFile(({ path, stats }) => ({ path, size: stats.size })) // ProcessResult return
.withFileCollector() // Enable collection
.execute('./src');
// Custom collector transformation - returns ProjectSummary
const summary = await new FolderWalker()
.onFile(({ path, stats }) => ({ path, size: stats.size }))
.withFileCollector((files) => ({
totalFiles: files.length,
totalSize: files.reduce((sum, f) => sum + f.size, 0)
})) // Transform to summary
.execute('./src');
```
### Custom async filters
```typescript
await new FolderWalker()
.withFileContents('utf-8')
.withFilter(async (entry) => {
// Custom filter: files containing specific content
if (entry.isFile && entry.content) {
const hasKeyword = (entry.content as string).includes('TODO');
await logAnalysis(entry.path, hasKeyword); // Async operation
return hasKeyword;
}
return false;
})
.withFilter((entry) => {
// Multiple filters: also check file size
return entry.stats.size < 10000;
})
.onFile(({ path }) => console.log(`TODO found in: ${path}`))
.execute('./src');
```
### Concurrent file processing
```typescript
const tasks: Promise<void>[] = [];
await new FolderWalker()
.onFile(async ({ path, stats }) => {
if (stats.size > 1024 * 1024) { // 1MB
// Process large files concurrently
const task = processLargeFile(path);
tasks.push(task);
}
})
.execute('.');
// Wait for all concurrent operations
await Promise.all(tasks);
```
## Filter Order and Performance
Filters are applied in the following order for optimal performance:
1. **Built-in filters** (fastest, applied first):
- Hidden files (`withHidden()`)
- Include/exclude patterns
- File extensions (`withExtensions()`)
- Filename patterns (`withPattern()`)
- File size limits (`withMaxFileSize()`)
2. **Custom filters** (`withFilter()`):
- Applied in the order they were added
- Support both sync and async operations
- Can access full file metadata and content
**Performance Tips:**
- Place expensive filters (file content analysis) last
- Use built-in filters when possible (they're optimized)
- Custom filters receive fully-populated entry objects
## License
MIT