code icon Code

Text Diff

Compute differences between texts or files

Source Code

import fs from "fs";
import path from "path";
import { diffLines, diffWords, createPatch } from "diff";

const [inputPathA, inputPathB, outputPath, format = "unified"] = process.argv.slice(2);

if (!inputPathA || !inputPathB || !outputPath) {
  console.error("Usage: inputPathA inputPathB outputPath [format]");
  process.exit(1);
}

try {
  console.log(`Computing diff...`);
  console.log(`  File A: ${inputPathA}`);
  console.log(`  File B: ${inputPathB}`);

  const textA = fs.readFileSync(inputPathA, "utf-8");
  const textB = fs.readFileSync(inputPathB, "utf-8");

  let output;
  let stats = { additions: 0, deletions: 0, changes: 0 };

  if (format === "unified") {
    // Generate unified diff patch
    output = createPatch(path.basename(inputPathA), textA, textB, "original", "modified");

    // Count changes from the patch
    const lines = output.split("\n");
    for (const line of lines) {
      if (line.startsWith("+") && !line.startsWith("+++")) stats.additions++;
      if (line.startsWith("-") && !line.startsWith("---")) stats.deletions++;
    }
  } else if (format === "json") {
    // Generate structured JSON diff
    const changes = diffLines(textA, textB);

    output = JSON.stringify(
      changes.map((part) => ({
        type: part.added ? "added" : part.removed ? "removed" : "unchanged",
        value: part.value,
        lines: part.count || 0,
      })),
      null,
      2
    );

    for (const part of changes) {
      if (part.added) stats.additions += part.count || 1;
      if (part.removed) stats.deletions += part.count || 1;
    }
  } else if (format === "words") {
    // Word-level diff
    const changes = diffWords(textA, textB);

    output = JSON.stringify(
      changes.map((part) => ({
        type: part.added ? "added" : part.removed ? "removed" : "unchanged",
        value: part.value,
      })),
      null,
      2
    );

    for (const part of changes) {
      if (part.added) stats.additions++;
      if (part.removed) stats.deletions++;
    }
  } else {
    console.error(`Unknown format: ${format}. Use: unified, json, words`);
    process.exit(1);
  }

  stats.changes = stats.additions + stats.deletions;

  // Ensure output directory exists
  const dir = path.dirname(outputPath);
  if (dir && dir !== ".") {
    fs.mkdirSync(dir, { recursive: true });
  }

  fs.writeFileSync(outputPath, output);

  const identical = stats.changes === 0;

  console.log(`\nāœ“ Computed diff`);
  console.log(`  Format: ${format}`);
  console.log(`  Additions: ${stats.additions}`);
  console.log(`  Deletions: ${stats.deletions}`);
  console.log(`  Identical: ${identical}`);
  console.log(`  Written to: ${outputPath}`);

  console.log(
    JSON.stringify({
      success: true,
      inputPathA,
      inputPathB,
      outputPath,
      format,
      identical,
      ...stats,
    })
  );
} catch (error) {
  console.error("Error:", error.message);
  process.exit(1);
}