import { createHash } from "crypto"; import path from "path"; /** * 파일의 SHA-256 해시를 계산 */ export async function computeFileHash(filePath: string): Promise { try { const file = Bun.file(filePath); const exists = await file.exists(); if (!exists) { throw new Error(`File not found: ${filePath}`); } const content = await file.text(); return createHash("sha256").update(content).digest("hex"); } catch (error) { if (error instanceof Error && error.message.startsWith("File not found")) { throw error; } throw new Error( `Failed to compute hash for ${filePath}: ${error instanceof Error ? error.message : String(error)}`, { cause: error } ); } } /** * 여러 파일의 해시를 수집 * @param rootDir 프로젝트 루트 디렉토리 * @param relativePaths 상대 경로 배열 * @returns 상대 경로 → 해시 맵 */ export async function collectFileHashes( rootDir: string, relativePaths: string[] ): Promise> { const hashes: Record = {}; await Promise.all( relativePaths.map(async (relativePath) => { const absolutePath = path.join(rootDir, relativePath); try { const hash = await computeFileHash(absolutePath); hashes[relativePath] = hash; } catch { // 파일이 없는 경우 무시 (스냅샷 시점에 없었을 수 있음) } }) ); return hashes; } /** * 디렉토리 내 모든 파일 경로를 재귀적으로 수집 */ export async function collectFilePaths( dirPath: string, basePath: string = dirPath ): Promise { const paths: string[] = []; try { const entries = await Array.fromAsync( new Bun.Glob("**/*").scan({ cwd: dirPath, onlyFiles: true, }) ); for (const entry of entries) { const relativePath = path.relative(basePath, path.join(dirPath, entry)); paths.push(relativePath); } } catch { // 디렉토리가 없는 경우 빈 배열 반환 } return paths; }