Flexible filtering system using glob patterns and custom filter handlers to control which files and directories are included in comparisons.
Collection of built-in filtering mechanisms for controlling comparison scope.
interface FilterHandlers {
/**
* Default minimatch-based glob filter.
* Uses includeFilter and excludeFilter options with minimatch patterns.
*/
defaultFilterHandler: FilterHandler;
}
/**
* Filter handler function type
* @param entry Filesystem entry to evaluate
* @param relativePath Path relative to comparison root
* @param options Comparison options
* @returns true to include entry in comparison, false to exclude
*/
type FilterHandler = (entry: Entry, relativePath: string, options: Options) => boolean;Default filtering using minimatch glob patterns with include and exclude options.
interface GlobFilterOptions extends Options {
/**
* Comma-separated minimatch patterns for files/directories to include.
* Only entries matching these patterns will be compared.
*/
includeFilter?: string;
/**
* Comma-separated minimatch patterns for files/directories to exclude.
* Entries matching these patterns will be skipped.
*/
excludeFilter?: string;
}Basic Glob Pattern Usage:
import { compareSync } from "dir-compare";
// Exclude common build artifacts and version control
const basicFilter = {
compareContent: true,
excludeFilter: ".git,node_modules,*.log,build,dist"
};
const result1 = compareSync("/project1", "/project2", basicFilter);
// Include only specific file types
const sourceCodeOnly = {
compareContent: true,
includeFilter: "*.js,*.ts,*.json,*.md",
excludeFilter: "*.test.js,*.spec.ts" // Exclude test files
};
const result2 = compareSync("/src1", "/src2", sourceCodeOnly);
// Complex patterns using minimatch syntax
const advancedPatterns = {
compareContent: true,
includeFilter: "src/**/*.{js,ts},docs/**/*.md,*.json",
excludeFilter: "**/test/**,**/*.test.*,**/node_modules/**,**/.git/**"
};
const result3 = compareSync("/workspace1", "/workspace2", advancedPatterns);Glob Pattern Examples:
// Pattern examples and their meanings
const patternExamples = {
// Exact matches
excludeFilter: ".git,package-lock.json",
// Wildcard matches
excludeFilter: "*.log,*.tmp",
// Directory patterns
excludeFilter: "node_modules,build,dist",
// Path-based patterns
excludeFilter: "/tests/expected,**/coverage/**",
// Globstar patterns (match any depth)
excludeFilter: "**/node_modules/**,**/*.log",
// Multiple file extensions
includeFilter: "*.{js,ts,jsx,tsx}",
// Specific subdirectories
includeFilter: "src/**/*.js,lib/**/*.ts",
// Combined include/exclude
includeFilter: "**/*.js",
excludeFilter: "**/*.test.js,**/node_modules/**"
};Create specialized filtering logic for complex scenarios.
/**
* Custom filter handler example for file size limits
*/
function createSizeFilter(maxSizeBytes: number): FilterHandler {
return (entry: Entry, relativePath: string, options: Options): boolean => {
// Always include directories for traversal
if (entry.isDirectory) {
return true;
}
// Filter files by size
return entry.stat.size <= maxSizeBytes;
};
}
/**
* Custom filter handler example for file age
*/
function createAgeFilter(maxAgeMs: number): FilterHandler {
const cutoffDate = new Date(Date.now() - maxAgeMs);
return (entry: Entry, relativePath: string, options: Options): boolean => {
// Include directories
if (entry.isDirectory) {
return true;
}
// Filter files by modification time
return entry.stat.mtime >= cutoffDate;
};
}
/**
* Extension-based filter with case handling
*/
function createExtensionFilter(allowedExtensions: string[], ignoreCase = true): FilterHandler {
const extensions = ignoreCase
? allowedExtensions.map(ext => ext.toLowerCase())
: allowedExtensions;
return (entry: Entry, relativePath: string, options: Options): boolean => {
if (entry.isDirectory) {
return true;
}
const ext = path.extname(entry.name);
const checkExt = ignoreCase ? ext.toLowerCase() : ext;
return extensions.includes(checkExt);
};
}Usage with Custom Filters:
import { compareSync, filterHandlers } from "dir-compare";
import path from "path";
// Combine size and extension filtering
const sizeFilter = createSizeFilter(10 * 1024 * 1024); // 10MB limit
const jsFilter = createExtensionFilter(['.js', '.ts', '.json']);
// Composite filter combining multiple criteria
const compositeFilter: FilterHandler = (entry, relativePath, options) => {
// Apply default glob filtering first
if (!filterHandlers.defaultFilterHandler(entry, relativePath, options)) {
return false;
}
// Apply size filter
if (!sizeFilter(entry, relativePath, options)) {
return false;
}
// Apply extension filter
return jsFilter(entry, relativePath, options);
};
const result = compareSync("/large-project1", "/large-project2", {
compareContent: true,
excludeFilter: "node_modules,*.log",
filterHandler: compositeFilter
});
// Age-based filtering for recent changes
const recentChangesFilter = createAgeFilter(7 * 24 * 60 * 60 * 1000); // 7 days
const recentResult = compareSync("/backup", "/current", {
compareContent: true,
compareDate: true,
filterHandler: recentChangesFilter
});Example implementation for .gitignore-compatible filtering.
/**
* Creates a filter handler that respects .gitignore rules
* (This is a conceptual example - actual implementation would use globby or similar)
*/
function createGitignoreFilter(rootPath1: string, rootPath2: string): FilterHandler {
// Load .gitignore files from both directories
const loadGitignoreRules = (rootPath: string): string[] => {
try {
const gitignorePath = path.join(rootPath, '.gitignore');
const content = fs.readFileSync(gitignorePath, 'utf8');
return content.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'));
} catch {
return [];
}
};
const rules1 = loadGitignoreRules(rootPath1);
const rules2 = loadGitignoreRules(rootPath2);
const allRules = [...new Set([...rules1, ...rules2])];
return (entry: Entry, relativePath: string, options: Options): boolean => {
// Apply default filtering first
if (!filterHandlers.defaultFilterHandler(entry, relativePath, options)) {
return false;
}
// Check against gitignore rules
const entryPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
for (const rule of allRules) {
// Simplified gitignore matching (real implementation would be more complex)
if (minimatch(entryPath, rule)) {
return false;
}
}
return true;
};
}Usage:
import { compareSync } from "dir-compare";
// Use gitignore-style filtering
const gitOptions = {
compareContent: true,
compareSize: true,
filterHandler: createGitignoreFilter("/repo1", "/repo2"),
// Regular filters still apply after gitignore rules
excludeFilter: "*.tmp",
includeFilter: "**/*" // Include all files not filtered by gitignore
};
const gitResult = compareSync("/repo1", "/repo2", gitOptions);import { compare, filterHandlers } from "dir-compare";
// Early directory pruning for performance
const performanceFilter: FilterHandler = (entry, relativePath, options) => {
// Skip large directories early
const skipDirs = ['node_modules', '.git', 'build', 'dist', 'coverage'];
if (entry.isDirectory && skipDirs.includes(entry.name)) {
return false;
}
// Skip large files early
if (!entry.isDirectory && entry.stat.size > 100 * 1024 * 1024) { // 100MB
return false;
}
// Apply default filtering for other cases
return filterHandlers.defaultFilterHandler(entry, relativePath, options);
};
// Efficient large directory comparison
const efficientOptions = {
compareSize: true, // Fast size comparison first
compareContent: false, // Skip slow content comparison
skipEmptyDirs: true, // Skip empty directories
filterHandler: performanceFilter,
excludeFilter: "*.iso,*.dmg,*.zip,*.tar,*.gz" // Skip archives
};
const largeResult = await compare("/large-dir1", "/large-dir2", efficientOptions);import { FilterHandler } from "dir-compare";
/**
* Creates a filter chain that applies multiple filters in sequence
*/
function createFilterChain(...filters: FilterHandler[]): FilterHandler {
return (entry: Entry, relativePath: string, options: Options): boolean => {
return filters.every(filter => filter(entry, relativePath, options));
};
}
// Example filter chain
const comprehensiveFilter = createFilterChain(
filterHandlers.defaultFilterHandler, // Apply glob patterns
createSizeFilter(50 * 1024 * 1024), // 50MB size limit
createAgeFilter(30 * 24 * 60 * 60 * 1000), // 30 days age limit
createExtensionFilter(['.js', '.ts', '.json', '.md']) // Specific extensions
);
const chainResult = compareSync("/filtered1", "/filtered2", {
compareContent: true,
includeFilter: "src/**,docs/**",
excludeFilter: "**/*.test.*",
filterHandler: comprehensiveFilter
});import { compareSync, FilterHandler } from "dir-compare";
// Logging filter for debugging what gets included/excluded
function createLoggingFilter(baseFilter: FilterHandler): FilterHandler {
return (entry: Entry, relativePath: string, options: Options): boolean => {
const result = baseFilter(entry, relativePath, options);
const entryPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
console.log(`Filter ${result ? 'INCLUDE' : 'EXCLUDE'}: ${entryPath} (${entry.isDirectory ? 'dir' : 'file'})`);
return result;
};
}
// Debug filtering behavior
const debugFilter = createLoggingFilter(filterHandlers.defaultFilterHandler);
const debugResult = compareSync("/debug1", "/debug2", {
compareContent: true,
excludeFilter: "*.log,temp/**",
filterHandler: debugFilter
});