功能说明
该示例为某个目录下的文件生成内容哈希清单,作为缓存键与完整性凭据。文件名先确定性排序,使同一目录在任意机器上得到相同顺序,随后逐文件计算 SHA-256 并加上算法前缀。比较两份清单即可判断输入是否发生变化,从而决定是否跳过后续昂贵步骤。
关键代码
hashFile 读取内容并计算摘要,前缀标明所用算法,便于将来迁移时区分。buildManifest 用 readdir 的 withFileTypes 过滤出普通文件并按名称排序,保证清单稳定。比较函数先比长度再逐项比对路径与哈希,任一不同即视为已变。清单结构可直接序列化为 JSON 持久化。
使用注意
该实现只遍历单层目录,递归子目录需补充遍历逻辑,但务必保持排序确定性,否则不同平台的目录顺序差异会造成假变更。内容哈希稳于时间戳,能正确识别仅被“触碰”而内容未变的文件。哈希算法与编码一旦固定就应写入清单,更换算法会使历史缓存在不改变输入的情况下失配。清单只覆盖内容完整性,不包含权限与符号链接等元数据。
代码示例
import { createHash } from 'node:crypto';
import { readFile, readdir } from 'node:fs/promises';
import { join } from 'node:path';
export async function hashFile(path: string): Promise<string> {
const content = await readFile(path);
return 'sha256:' + createHash('sha256').update(content).digest('hex');
}
export async function buildManifest(root: string): Promise<Array<{ path: string; hash: string }>> {
const entries = await readdir(root, { withFileTypes: true });
const files = entries.filter((e) => e.isFile()).map((e) => e.name).sort();
const manifest: Array<{ path: string; hash: string }> = [];
for (const name of files) {
manifest.push({ path: name, hash: await hashFile(join(root, name)) });
}
return manifest;
}
export function manifestUnchanged(a: readonly { path: string; hash: string }[], b: readonly { path: string; hash: string }[]): boolean {
if (a.length !== b.length) return false;
return a.every((entry, i) => {
const other = b[i];
return other !== undefined && entry.path === other.path && entry.hash === other.hash;
});
}