68 lines
2 KiB
JavaScript
68 lines
2 KiB
JavaScript
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 };
|
|
}
|