CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-dir-compare

Node JS directory compare library with extensive comparison options and TypeScript support

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

file-comparators.mddocs/

File Comparators

File content comparison handlers for binary and text-based comparison strategies with extensive customization options.

Capabilities

Built-in File Compare Handlers

Collection of pre-built file comparison handlers for different comparison strategies.

interface FileCompareHandlers {
  /**
   * Binary file content comparator (default).
   * Compares files byte-by-byte for exact binary equality.
   */
  defaultFileCompare: CompareFileHandler;
  
  /**
   * Line-based text file comparator with whitespace and line-ending options.
   * Supports ignoring whitespace differences, line endings, and empty lines.
   */
  lineBasedFileCompare: CompareFileHandler;
}

interface CompareFileHandler {
  compareSync: CompareFileSync;
  compareAsync: CompareFileAsync;
}

Default Binary File Comparator

Binary comparison for exact file content matching.

/**
 * Synchronous binary file comparison
 * @param path1 Left file path
 * @param stat1 Left file stats
 * @param path2 Right file path  
 * @param stat2 Right file stats
 * @param options Comparison options
 * @returns true if files are identical, false otherwise
 */
type CompareFileSync = (
  path1: string, 
  stat1: fs.Stats,
  path2: string, 
  stat2: fs.Stats, 
  options: Options
) => boolean;

/**
 * Asynchronous binary file comparison
 * @param path1 Left file path
 * @param stat1 Left file stats
 * @param path2 Right file path
 * @param stat2 Right file stats
 * @param options Comparison options
 * @returns Promise resolving to true if files are identical
 */
type CompareFileAsync = (
  path1: string, 
  stat1: fs.Stats,
  path2: string, 
  stat2: fs.Stats, 
  options: Options
) => Promise<boolean>;

Usage Examples:

import { compareSync, fileCompareHandlers } from "dir-compare";

// Use default binary comparator (this is the default behavior)
const binaryResult = compareSync("/dir1", "/dir2", {
  compareContent: true,
  compareFileSync: fileCompareHandlers.defaultFileCompare.compareSync,
  compareFileAsync: fileCompareHandlers.defaultFileCompare.compareAsync
});

// Binary comparison is exact - any byte difference results in 'distinct'
console.log(`Binary comparison found ${binaryResult.distinctFiles} different files`);

Line-Based Text File Comparator

Advanced text comparison with options to ignore whitespace and line-ending differences.

interface LineBasedOptions extends Options {
  /**
   * Ignore cr/lf line ending differences (default: false)
   * Similar to 'diff --strip-trailing-cr'
   */
  ignoreLineEnding?: boolean;
  
  /**
   * Ignore white spaces at beginning and end of lines (default: false)
   * Similar to 'diff -b'
   */
  ignoreWhiteSpaces?: boolean;
  
  /**
   * Ignore all white space differences (default: false)
   * Similar to 'diff -w'
   */
  ignoreAllWhiteSpaces?: boolean;
  
  /**
   * Ignore differences caused by empty lines (default: false)
   * Similar to 'diff -B'
   */
  ignoreEmptyLines?: boolean;
}

Usage Examples:

import { compareSync, compare, fileCompareHandlers } from "dir-compare";

// Basic line-based comparison
const textOptions = {
  compareContent: true,
  compareFileSync: fileCompareHandlers.lineBasedFileCompare.compareSync,
  compareFileAsync: fileCompareHandlers.lineBasedFileCompare.compareAsync,
  ignoreLineEnding: true,    // Ignore CRLF vs LF differences
  ignoreWhiteSpaces: true    // Ignore leading/trailing whitespace
};

const textResult = compareSync("/src", "/backup/src", textOptions);

// Advanced text comparison ignoring various whitespace differences
const advancedTextOptions = {
  compareContent: true,
  compareFileSync: fileCompareHandlers.lineBasedFileCompare.compareSync,
  compareFileAsync: fileCompareHandlers.lineBasedFileCompare.compareAsync,
  ignoreLineEnding: true,        // Ignore line ending differences
  ignoreWhiteSpaces: true,       // Ignore leading/trailing spaces
  ignoreAllWhiteSpaces: true,    // Ignore all whitespace differences
  ignoreEmptyLines: true,        // Ignore empty line differences
  includeFilter: "*.txt,*.md,*.js,*.ts" // Only compare text files
};

const flexibleResult = await compare("/docs1", "/docs2", advancedTextOptions);

// Process results focusing on content differences
flexibleResult.diffSet?.forEach(diff => {
  if (diff.state === 'distinct' && diff.reason === 'different-content') {
    console.log(`Content differs: ${diff.relativePath}/${diff.name1}`);
  }
});

Custom File Comparators

Create custom file comparison logic for specialized needs.

/**
 * Custom file comparator example for JSON files
 */
function createJsonComparator(): CompareFileHandler {
  const compareJsonSync: CompareFileSync = (path1, stat1, path2, stat2, options) => {
    try {
      const content1 = fs.readFileSync(path1, 'utf8');
      const content2 = fs.readFileSync(path2, 'utf8');
      
      const json1 = JSON.parse(content1);
      const json2 = JSON.parse(content2);
      
      return JSON.stringify(json1) === JSON.stringify(json2);
    } catch (error) {
      // Fall back to binary comparison on parse errors
      return fileCompareHandlers.defaultFileCompare.compareSync(path1, stat1, path2, stat2, options);
    }
  };

  const compareJsonAsync: CompareFileAsync = async (path1, stat1, path2, stat2, options) => {
    try {
      const [content1, content2] = await Promise.all([
        fs.promises.readFile(path1, 'utf8'),
        fs.promises.readFile(path2, 'utf8')
      ]);
      
      const json1 = JSON.parse(content1);
      const json2 = JSON.parse(content2);
      
      return JSON.stringify(json1) === JSON.stringify(json2);
    } catch (error) {
      return fileCompareHandlers.defaultFileCompare.compareAsync(path1, stat1, path2, stat2, options);
    }
  };

  return {
    compareSync: compareJsonSync,
    compareAsync: compareJsonAsync
  };
}

Usage with Custom Comparators:

import { compareSync } from "dir-compare";

// Use custom JSON comparator
const jsonComparator = createJsonComparator();

const jsonComparisonOptions = {
  compareContent: true,
  compareFileSync: jsonComparator.compareSync,
  compareFileAsync: jsonComparator.compareAsync,
  includeFilter: "*.json" // Only compare JSON files
};

const result = compareSync("/config1", "/config2", jsonComparisonOptions);

// Custom size-based comparator (files equal if size matches)
const sizeOnlyComparator: CompareFileHandler = {
  compareSync: (path1, stat1, path2, stat2, options) => {
    return stat1.size === stat2.size;
  },
  compareAsync: async (path1, stat1, path2, stat2, options) => {
    return stat1.size === stat2.size;
  }
};

const sizeBasedResult = compareSync("/images1", "/images2", {
  compareContent: true,
  compareFileSync: sizeOnlyComparator.compareSync,
  includeFilter: "*.jpg,*.png"
});

File Comparator Performance Considerations

import { compare, fileCompareHandlers } from "dir-compare";

// For large files, consider size pre-check
const efficientOptions = {
  compareSize: true,         // Quick size check first
  compareContent: true,      // Only do content comparison if sizes match
  compareFileAsync: fileCompareHandlers.defaultFileCompare.compareAsync,
  excludeFilter: "*.iso,*.dmg,*.zip" // Exclude large binary files
};

// Line-based comparison is slower but more flexible for text files
const textFileOptions = {
  compareContent: true,
  compareFileAsync: fileCompareHandlers.lineBasedFileCompare.compareAsync,
  ignoreWhiteSpaces: true,
  ignoreLineEnding: true,
  includeFilter: "*.txt,*.md,*.js,*.ts,*.html,*.css" // Text files only
};

const mixedResult = await compare("/project1", "/project2", {
  compareSize: true,    // Fast initial check
  compareContent: true, // Detailed content check
  // Use default binary comparator (will be set automatically)
});

Error Handling in File Comparators

import { compareSync, fileCompareHandlers } from "dir-compare";

// Robust comparison with error handling
const robustOptions = {
  compareContent: true,
  handlePermissionDenied: true, // Continue on access errors
  compareFileSync: (path1, stat1, path2, stat2, options) => {
    try {
      // Use default binary comparator
      return fileCompareHandlers.defaultFileCompare.compareSync(path1, stat1, path2, stat2, options);
    } catch (error) {
      console.warn(`Failed to compare ${path1} and ${path2}:`, error.message);
      // Consider files different if comparison fails
      return false;
    }
  }
};

const result = compareSync("/fragile/dir1", "/fragile/dir2", robustOptions);

// Check for comparison errors
result.diffSet?.forEach(diff => {
  if (diff.permissionDeniedState !== 'access-ok') {
    console.log(`Access error: ${diff.relativePath} - ${diff.permissionDeniedState}`);
  }
});

Install with Tessl CLI

npx tessl i tessl/npm-dir-compare

docs

comparison-functions.md

extension-points.md

file-comparators.md

filters-and-patterns.md

index.md

tile.json