@lukasbach/scripts

compare-folders

Compares two folders and lists missing items and files that are different. Supports comparing files based on either file size, file size and edit date or file hash. For hashing, SHA1 is used which is fast and works well for large files.

You might want to use the verbose flag (-v) for folders with many large items to keep track of progress.

Usage

npx @lukasbach/scripts compare-folders

You can call the script directly if you have installed it globally:

npm i -g @lukasbach/scripts
ldo compare-folders

Options

  • --folder1, -a: What is the first folder?
  • --folder2, -b: What is the second folder?
  • --compare, -c: How should files be compared?
  • -v, --verbose: Verbose logging

You can also omit options, and will be asked for them interactively.

Add --yes to skip all confirmations.

Script source

View Source on GitHub

/**
 * Compares two folders and lists missing items and files that are different. Supports
 * comparing files based on either file size, file size and edit date or file hash. For
 * hashing, SHA1 is used which is fast and works well for large files.
 *
 * You might want to use the verbose flag (-v) for folders with many large items to keep
 * track of progress.
 */
import { compareErrors, compareFiles } from "./compare-utils.js";

const root1 = await ask.text("folder1,a", "What is the first folder?");
const root2 = await ask.text("folder2,b", "What is the second folder?");
const compareMethod = await ask.choice("compare,c", "How should files be compared?", ["size", "datesize", "hash"]);

let missingItems1 = 0;
let missingItems2 = 0;
let differentItems = 0;
let comparisons = 0;

const compareAtPath = async (filePath: string) => {
  comparisons++;
  if (!(await compareFiles(path.join(root1, filePath), path.join(root2, filePath), compareMethod))) {
    log.info(`File ${filePath} is different`);
    differentItems++;
  }
};

const traverseFolder = async (folder: string) => {
  log.verbose(`Traversing folder ${folder}`);
  for (const file of await fs.readdir(path.join(root1, folder))) {
    const filePath = path.join(folder, file);
    const stats = await fs.stat(path.join(root1, folder, file));

    if (!(await fs.exists(path.join(root2, filePath)))) {
      missingItems2++;
      log.info(`Item ${filePath} is missing in folder 2`);
      // eslint-disable-next-line no-continue
      continue;
    }

    if (stats.isDirectory()) {
      await traverseFolder(filePath);
    } else {
      await compareAtPath(filePath);
    }
  }

  for (const file of await fs.readdir(path.join(root2, folder))) {
    const filePath = path.join(folder, file);
    if (!(await fs.exists(path.join(root1, filePath)))) {
      missingItems1++;
      log.info(`Item ${filePath} is missing in folder 1`);
    }
  }
};

await traverseFolder("");

log.info(`${missingItems1} items missing in folder 1, ${missingItems2} items missing in folder 2`);
log.info(`${differentItems} items are different`);
log.info(`${comparisons} files compared`);
log.info(`${compareErrors} errors occured while creating file hashes.`);

if (missingItems1 === 0 && missingItems2 === 0 && differentItems === 0) {
  log.success("Folders are identical");
}