import revHash from 'rev-hash' import { readFileSync, statSync } from 'fs' import { resolve } from 'path' /** * Calculate the rev hash for a file * * @param {string} file The path to the file to hash * @returns {string} */ function hashFile (file) { return revHash(readFileSync(file)) } /** * Calculate the rev hashes for a list of files * * @generator * @param {string[]} files The files to hash * @param {string} base A base path prepended to each file (ignored if a file path is absolute) * @yields {string} */ function* hashFileList (files, base = '/') { for (const file of files) { yield [ file, hashFile(resolve(base, file)) ] } } /** * Profile a list of files to compare against a later state * * @param {string[]} files The files to profile * @param {string} base A base path prepended to each file (ignored if a file path is absolute) * @return {object} */ export function profile (files, base = '/') { const map = {} for (const [ file, hash ] of hashFileList(files, base)) { map[file] = [ statSync(resolve(base, file)).size, hash ] } return map } /** * * @param {string[]} files The files to profile * @param {hashes} object A the file list characteristics generated earlier by profile() * @param {string} base A base path prepended to each file (ignored if a file path is absolute) * @return {object} */ export function diff (files, hashes, base = '/') { // Detect new files for (const file of files) { if (!(file in hashes)) { return { type: 'added', file } } } // Detect missing files for (const file in hashes) { if (!files.includes(file)) { return { type: 'removed', file } } } // Check sizes for (const file of files) { if (statSync(resolve(base, file)).size !== hashes[file][0]) { return { type: 'changed', file } } } // Check contents for (const [ file, hash ] of hashFileList(files, base)) { if (hash !== hashes[file][1]) { return { type: 'changed', file } } } return null }