import fs from "node:fs"; import path from "node:path"; /** 디렉터리를 재귀적으로 복사한다(대상은 미리 비어 있어야 한다). */ export function copyDirRecursive(src, dest) { fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src, { withFileTypes: true })) { const s = path.join(src, entry.name); const d = path.join(dest, entry.name); if (entry.isDirectory()) { copyDirRecursive(s, d); } else if (entry.isFile()) { fs.mkdirSync(path.dirname(d), { recursive: true }); fs.copyFileSync(s, d); } } } /** * 작업 디렉터리 전체를 스냅샷한다. { "상대/경로": { size, mtimeMs } } 형태. */ export function snapshotDir(root) { const out = {}; function walk(dir) { let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } for (const entry of entries) { const full = path.join(dir, entry.name); if (entry.isDirectory()) { walk(full); } else if (entry.isFile()) { const rel = path.relative(root, full).split(path.sep).join("/"); try { const st = fs.statSync(full); out[rel] = { size: st.size, mtimeMs: st.mtimeMs }; } catch { // 스냅샷 도중 파일이 사라지면(임시 파일 등) 무시한다. } } } } walk(root); return out; } /** * before -> after 스냅샷을 비교해 생성·수정된 파일 목록과 개수를 낸다. * 삭제된 파일은 개수에 넣지 않는다(설계상 "생성·수정"만 센다). */ export function diffSnapshots(before, after) { const added = []; const modified = []; for (const rel of Object.keys(after)) { const b = before[rel]; const a = after[rel]; if (!b) { added.push(rel); } else if (b.size !== a.size || b.mtimeMs !== a.mtimeMs) { modified.push(rel); } } const removed = Object.keys(before).filter((rel) => !(rel in after)); return { added, modified, removed, count: added.length + modified.length }; }