OpenCode 实践课:4 分钟用 Node.js 写出一个命令行文件差异对比工具

项目介绍

当你想快速对比两个代码文件的差异,又不想打开 Git 或 VS Code 的时候,一个命令行 diff 工具就派上用场了。今天我们用 OpenCode + Node.js,从零写一个支持彩色输出的文件差异对比工具,类似 git diff 的迷你版。

  • 技术栈:Node.js(零依赖,纯标准库)
  • 核心算法:LCS(最长公共子序列)实现文本差异检测
  • 功能:对比两个文本文件,红色标记删除行,绿色标记新增行,底部输出统计

准备工作

  • 安装 Node.js(v16+)
  • 安装并配置 OpenCode(opencode.ai

实践过程

第一步:让 OpenCode 创建项目骨架

我先告诉 OpenCode 要做什么:

> "创建一个 Node.js 命令行工具 diff.js,读取两个文件路径作为参数,比较内容差异并输出"

OpenCode 马上生成了基础结构——用 fs.readFileSync 读取文件、按行分割、用 process.argv 获取命令行参数。骨架不到 20 行,省去了手写文件读取和参数解析的琐碎工作。

关键要点:向 OpenCode 描述需求时,先说清楚输入(两个文件路径)、输出(差异对比结果),它生成的基础代码基本不用改。

第二步:让 OpenCode 实现 LCS diff 算法

这是工具的核心。我对 OpenCode 说:

> "用最长公共子序列算法对比两个字符串数组,输出增/删/相同的操作序列"

OpenCode 生成了一段标准的 LCS 动态规划实现:二维 DP 表计算公共子序列长度,然后回溯重建差异操作序列。代码约 30 行,算法正确,直接可用。

function lcsDiff(a, b) {
    const m = a.length, n = b.length;
    const dp = Array.from({length: m + 1}, () => new Array(n + 1).fill(0));
    for (let i = 1; i <= m; i++)
        for (let j = 1; j <= n; j++)
            dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]);
    // 回溯构建差异序列...
}

关键要点:OpenCode 擅长实现经典算法,你只需要给出准确的算法名称和输入输出格式,它会生成经过验证的实现。

第三步:让 OpenCode 添加 ANSI 彩色输出

我对 OpenCode 说:

> "给差异输出加上颜色:新增行用绿色(\x1b[32m),删除行用红色(\x1b[31m),同时输出增删行数统计"

OpenCode 在现有的打印函数中插入了 ANSI 转义码,新增行前缀 + 用绿色,删除行前缀 - 用红色,底部自动追加统计行。一行代码都不用我改。

关键要点:功能叠加是 OpenCode 的强项。先有核心逻辑,再逐步"对话式"添加功能,每一步 OpenCode 都能准确理解上下文。

第四步:完善边界处理和最终测试

我对 OpenCode 说:

> "加上参数校验——参数不足时打印用法,文件不存在时报错退出"""

OpenCode 补上了 fs.existsSync 检查,参数不足时输出 用法: node diff.js <文件1> <文件2>。至此工具完整可用。

我用两个测试文件验证:

$ node diff.js a.txt b.txt
--- a.txt
+++ b.txt
  hello world
+ console.log("new line")
  foo bar
- old deprecated line
  end
@@ 共 1 行新增, 1 行删除 @@

完全符合预期。从第一行对话到最终交付,整个过程不到 4 分钟。

完整代码

#!/usr/bin/env node
const fs = require('fs');

function readLines(path) {
    return fs.readFileSync(path, 'utf-8').split('\n');
}

function lcsDiff(a, b) {
    const m = a.length, n = b.length;
    const dp = Array.from({length: m + 1}, () => new Array(n + 1).fill(0));
    for (let i = 1; i <= m; i++)
        for (let j = 1; j <= n; j++)
            dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]);

    const result = [];
    let i = m, j = n;
    while (i > 0 || j > 0) {
        if (i > 0 && j > 0 && a[i-1] === b[j-1]) {
            result.unshift({type: 'same', text: a[i-1], oldNo: i, newNo: j});
            i--; j--;
        } else if (j > 0 && (i === 0 || dp[i][j-1] >= dp[i-1][j])) {
            result.unshift({type: 'add', text: b[j-1], oldNo: 0, newNo: j});
            j--;
        } else {
            result.unshift({type: 'del', text: a[i-1], oldNo: i, newNo: 0});
            i--;
        }
    }
    return result;
}

function printDiff(diffs) {
    const R = '\x1b[31m', G = '\x1b[32m', E = '\x1b[0m';
    let out = '', addCount = 0, delCount = 0;
    for (const d of diffs) {
        if (d.type === 'add') {
            out += `${G}+ ${d.text}${E}\n`;
            addCount++;
        } else if (d.type === 'del') {
            out += `${R}- ${d.text}${E}\n`;
            delCount++;
        } else {
            out += `  ${d.text}\n`;
        }
    }
    if (addCount === 0 && delCount === 0) out += '两个文件完全相同。\n';
    return { out, addCount, delCount };
}

function main() {
    const [f1, f2] = process.argv.slice(2);
    if (!f1 || !f2) {
        console.log('用法: node diff.js <文件1> <文件2>');
        process.exit(1);
    }
    if (!fs.existsSync(f1) || !fs.existsSync(f2)) {
        console.log('错误: 文件不存在');
        process.exit(1);
    }
    const a = readLines(f1), b = readLines(f2);
    const diffs = lcsDiff(a, b);
    const { out, addCount, delCount } = printDiff(diffs);
    console.log(`--- ${f1}\n+++ ${f2}`);
    console.log(out);
    console.log(`@@ 共 ${addCount} 行新增, ${delCount} 行删除 @@`);
}

main();

小结

整个项目 75 行代码,从构思到开发完成不到 4 分钟。OpenCode 承担了全部编码工作——我只需要用自然语言描述每一步需求,它负责翻译成可运行的代码。相比手动写,省去了查文档、调试语法错误的时间,尤其是 LCS 算法这种需要仔细处理边界条件的逻辑,交给 AI 生成又快又准。

这就是 OpenCode 的正确打开方式:你负责"做什么",它负责"怎么做"