/** * 递归对比两个目录,列出差异文件(排除 node_modules/.git/dist 等)。 * 用法: node compare-dirs.js */ const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const EXCLUDE_DIRS = new Set(['node_modules', '.git', 'temp', 'test-results', 'make', 'dist', 'tools', 'tests', 'stress', 'hf-trend', '.sisyphus', '.opencode', 'output', 'test-outputs']); const EXCLUDE_FILES = new Set(['PERFORMANCE_ANALYSIS.md', 'PERFORMANCE_STRESS_TEST_2026-08-03.md', 'HF_TOKEN.md']); const [A, B] = process.argv.slice(2); if (!A || !B) { console.error('need two dirs'); process.exit(1); } const md5 = (p) => crypto.createHash('md5').update(fs.readFileSync(p)).digest('hex'); function walk(d, base) { const out = new Map(); if (!fs.existsSync(d)) return out; for (const f of fs.readdirSync(d)) { const fp = path.join(d, f); if (fs.statSync(fp).isDirectory()) { if (EXCLUDE_DIRS.has(f)) continue; for (const [rel, h] of walk(fp, base)) out.set(rel, h); } else { if (EXCLUDE_FILES.has(f)) continue; const rel = path.relative(base, fp).replace(/\\/g, '/'); out.set(rel, md5(fp)); } } return out; } const mapA = walk(A, A); const mapB = walk(B, B); const all = new Set([...mapA.keys(), ...mapB.keys()]); const changed = []; const onlyA = []; const onlyB = []; for (const rel of all) { const ha = mapA.get(rel); const hb = mapB.get(rel); if (ha && hb) { if (ha !== hb) changed.push(rel); } else if (ha && !hb) onlyA.push(rel); else onlyB.push(rel); } changed.sort(); onlyA.sort(); onlyB.sort(); console.log(`=== A=${A}`); console.log(`=== B=${B}`); console.log(`\n[不同内容] ${changed.length} 个:`); for (const f of changed) console.log(` M ${f}`); console.log(`\n[仅 A 有] ${onlyA.length} 个:`); for (const f of onlyA) console.log(` A ${f}`); console.log(`\n[仅 B 有] ${onlyB.length} 个:`); for (const f of onlyB) console.log(` B ${f}`);