Node JS directory compare library with extensive comparison options and TypeScript support
npx @tessl/cli install tessl/npm-dir-compare@5.0.0dir-compare provides comprehensive Node.js directory and file comparison functionality with extensive customization options. It offers both synchronous and asynchronous comparison methods with detailed statistics, difference reporting, and extensible architecture through custom comparators and filters.
npm install dir-compareimport { compareSync, compare, Options, Result } from "dir-compare";For CommonJS:
const { compareSync, compare } = require("dir-compare");Additional handler imports:
import {
fileCompareHandlers,
compareNameHandlers,
filterHandlers
} from "dir-compare";import { compareSync, compare, Options, Result } from "dir-compare";
// Synchronous comparison
const options: Options = {
compareSize: true,
compareContent: true,
excludeFilter: ".git,node_modules"
};
const result: Result = compareSync("/path/to/dir1", "/path/to/dir2", options);
console.log(`Directories are ${result.same ? 'identical' : 'different'}`);
console.log(`Statistics: ${result.equal} equal, ${result.distinct} distinct, ${result.differences} total differences`);
// Asynchronous comparison
compare("/path/to/dir1", "/path/to/dir2", options)
.then(result => {
console.log("Comparison completed:", result.same);
result.diffSet?.forEach(diff =>
console.log(`${diff.relativePath}: ${diff.state}`)
);
})
.catch(error => console.error("Comparison failed:", error));dir-compare is built around several key architectural components:
Primary functions for comparing directories and files with comprehensive options and detailed results.
function compareSync(path1: string, path2: string, options?: Options): Result;
function compare(path1: string, path2: string, options?: Options): Promise<Result>;Built-in and extensible file content comparison handlers supporting binary and line-based comparison strategies.
interface FileCompareHandlers {
defaultFileCompare: CompareFileHandler;
lineBasedFileCompare: CompareFileHandler;
}
interface CompareFileHandler {
compareSync: CompareFileSync;
compareAsync: CompareFileAsync;
}Flexible filtering system using glob patterns and custom filter handlers to control which files and directories are included in comparisons.
interface FilterHandlers {
defaultFilterHandler: FilterHandler;
}
type FilterHandler = (entry: Entry, relativePath: string, options: Options) => boolean;Comprehensive extension system allowing custom result builders, name comparators, and filter handlers for specialized comparison needs.
type ResultBuilder = (
entry1: Entry | undefined,
entry2: Entry | undefined,
state: DifferenceState,
level: number,
relativePath: string,
options: Options,
statistics: InitialStatistics,
diffSet: DiffSet | undefined,
reason: Reason | undefined,
permissionDeniedState: PermissionDeniedState
) => void;
type CompareNameHandler = (name1: string, name2: string, options: Options) => 0 | 1 | -1;interface Result extends Statistics {
diffSet?: DiffSet;
}
interface Statistics extends InitialStatistics {
same: boolean;
differences: number;
total: number;
differencesFiles: number;
totalFiles: number;
differencesDirs: number;
totalDirs: number;
}
interface InitialStatistics {
distinct: number;
equal: number;
left: number;
right: number;
distinctFiles: number;
equalFiles: number;
leftFiles: number;
rightFiles: number;
distinctDirs: number;
equalDirs: number;
leftDirs: number;
rightDirs: number;
brokenLinks: BrokenLinksStatistics;
permissionDenied: PermissionDeniedStatistics;
symlinks?: SymlinkStatistics;
}interface Entry {
name: string;
absolutePath: string;
path: string;
origin: EntryOrigin;
stat: fs.Stats;
lstat: fs.Stats;
isDirectory: boolean;
isSymlink: boolean;
isBrokenLink: boolean;
isPermissionDenied: boolean;
}
interface Difference {
path1?: string;
path2?: string;
relativePath: string;
name1?: string;
name2?: string;
state: DifferenceState;
permissionDeniedState: PermissionDeniedState;
type1: DifferenceType;
type2: DifferenceType;
size1?: number;
size2?: number;
date1?: Date;
date2?: Date;
level: number;
reason: Reason;
}type DiffSet = Array<Difference>;
type EntryOrigin = 'left' | 'right';
type DifferenceState = "equal" | "left" | "right" | "distinct";
type PermissionDeniedState = "access-ok" | "access-error-both" | "access-error-left" | "access-error-right";
type DifferenceType = "missing" | "file" | "directory" | "broken-link";
type Reason = undefined | "different-size" | "different-date" | "different-content" | "broken-link" | "different-symlink" | "permission-denied";interface BrokenLinksStatistics {
leftBrokenLinks: number;
rightBrokenLinks: number;
distinctBrokenLinks: number;
totalBrokenLinks: number;
}
interface PermissionDeniedStatistics {
leftPermissionDenied: number;
rightPermissionDenied: number;
distinctPermissionDenied: number;
totalPermissionDenied: number;
}
interface SymlinkStatistics {
distinctSymlinks: number;
equalSymlinks: number;
leftSymlinks: number;
rightSymlinks: number;
differencesSymlinks: number;
totalSymlinks: number;
}