1"use strict"; 2var __importDefault = (this && this.__importDefault) || function (mod) { 3 return (mod && mod.__esModule) ? mod : { "default": mod }; 4}; 5Object.defineProperty(exports, "__esModule", { value: true }); 6exports.createSourceId = exports.createContentsHashResultsAsync = exports.createDirHashResultsAsync = exports.createFileHashResultsAsync = exports.createFingerprintSourceAsync = exports.createFingerprintFromSourcesAsync = void 0; 7const crypto_1 = require("crypto"); 8const fs_1 = require("fs"); 9const promises_1 = __importDefault(require("fs/promises")); 10const minimatch_1 = __importDefault(require("minimatch")); 11const p_limit_1 = __importDefault(require("p-limit")); 12const path_1 = __importDefault(require("path")); 13const Profile_1 = require("../utils/Profile"); 14/** 15 * Create a `Fingerprint` from `HashSources` array 16 */ 17async function createFingerprintFromSourcesAsync(sources, projectRoot, options) { 18 const limiter = (0, p_limit_1.default)(options.concurrentIoLimit); 19 const fingerprintSources = await Promise.all(sources.map((source) => createFingerprintSourceAsync(source, limiter, projectRoot, options))); 20 const hasher = (0, crypto_1.createHash)(options.hashAlgorithm); 21 for (const source of fingerprintSources) { 22 if (source.hash != null) { 23 hasher.update(createSourceId(source)); 24 hasher.update(source.hash); 25 } 26 } 27 const hash = hasher.digest('hex'); 28 return { 29 sources: fingerprintSources, 30 hash, 31 }; 32} 33exports.createFingerprintFromSourcesAsync = createFingerprintFromSourcesAsync; 34/** 35 * Create a `FingerprintSource` from a `HashSource` 36 * This function will get a hash value and merge back to original source 37 */ 38async function createFingerprintSourceAsync(source, limiter, projectRoot, options) { 39 let result = null; 40 switch (source.type) { 41 case 'contents': 42 result = await createContentsHashResultsAsync(source, options); 43 break; 44 case 'file': 45 result = await createFileHashResultsAsync(source.filePath, limiter, projectRoot, options); 46 break; 47 case 'dir': 48 result = await (0, Profile_1.profile)(createDirHashResultsAsync, `createDirHashResultsAsync(${source.filePath})`)(source.filePath, limiter, projectRoot, options); 49 break; 50 default: 51 throw new Error('Unsupported source type'); 52 } 53 return { ...source, hash: result?.hex ?? null }; 54} 55exports.createFingerprintSourceAsync = createFingerprintSourceAsync; 56/** 57 * Create a `HashResult` from a file 58 */ 59async function createFileHashResultsAsync(filePath, limiter, projectRoot, options) { 60 // Backup code for faster hashing 61 /* 62 return limiter(async () => { 63 const hasher = createHash(options.hashAlgorithm); 64 65 const stat = await fs.stat(filePath); 66 hasher.update(`${stat.size}`); 67 68 const buffer = Buffer.alloc(4096); 69 const fd = await fs.open(filePath, 'r'); 70 await fd.read(buffer, 0, buffer.length, 0); 71 await fd.close(); 72 hasher.update(buffer); 73 console.log('stat', filePath, stat.size); 74 return { id: path.relative(projectRoot, filePath), hex: hasher.digest('hex') }; 75 }); 76 */ 77 return limiter(() => { 78 return new Promise((resolve, reject) => { 79 let resolved = false; 80 const hasher = (0, crypto_1.createHash)(options.hashAlgorithm); 81 const stream = (0, fs_1.createReadStream)(path_1.default.join(projectRoot, filePath)); 82 stream.on('close', () => { 83 if (!resolved) { 84 const hex = hasher.digest('hex'); 85 resolve({ id: filePath, hex }); 86 resolved = true; 87 } 88 }); 89 stream.on('error', (e) => { 90 reject(e); 91 }); 92 stream.on('data', (chunk) => { 93 hasher.update(chunk); 94 }); 95 }); 96 }); 97} 98exports.createFileHashResultsAsync = createFileHashResultsAsync; 99/** 100 * Indicate the given `dirPath` should be excluded by `dirExcludes` 101 */ 102function isExcludedDir(dirPath, dirExcludes) { 103 for (const exclude of dirExcludes) { 104 if ((0, minimatch_1.default)(dirPath, exclude)) { 105 return true; 106 } 107 } 108 return false; 109} 110/** 111 * Create `HashResult` for a dir. 112 * If the dir is excluded, returns null rather than a HashResult 113 */ 114async function createDirHashResultsAsync(dirPath, limiter, projectRoot, options, depth = 0) { 115 if (isExcludedDir(dirPath, options.dirExcludes)) { 116 return null; 117 } 118 const dirents = (await promises_1.default.readdir(path_1.default.join(projectRoot, dirPath), { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name)); 119 const promises = []; 120 for (const dirent of dirents) { 121 if (dirent.isDirectory()) { 122 const filePath = path_1.default.join(dirPath, dirent.name); 123 promises.push(createDirHashResultsAsync(filePath, limiter, projectRoot, options, depth + 1)); 124 } 125 else if (dirent.isFile()) { 126 const filePath = path_1.default.join(dirPath, dirent.name); 127 promises.push(createFileHashResultsAsync(filePath, limiter, projectRoot, options)); 128 } 129 } 130 const hasher = (0, crypto_1.createHash)(options.hashAlgorithm); 131 const results = await Promise.all(promises); 132 for (const result of results) { 133 if (result != null) { 134 hasher.update(result.id); 135 hasher.update(result.hex); 136 } 137 } 138 const hex = hasher.digest('hex'); 139 return { id: dirPath, hex }; 140} 141exports.createDirHashResultsAsync = createDirHashResultsAsync; 142/** 143 * Create `HashResult` for a `HashSourceContents` 144 */ 145async function createContentsHashResultsAsync(source, options) { 146 const hex = (0, crypto_1.createHash)(options.hashAlgorithm).update(source.contents).digest('hex'); 147 return { id: source.id, hex }; 148} 149exports.createContentsHashResultsAsync = createContentsHashResultsAsync; 150/** 151 * Create id from given source 152 */ 153function createSourceId(source) { 154 switch (source.type) { 155 case 'contents': 156 return source.id; 157 case 'file': 158 return source.filePath; 159 case 'dir': 160 return source.filePath; 161 default: 162 throw new Error('Unsupported source type'); 163 } 164} 165exports.createSourceId = createSourceId; 166//# sourceMappingURL=Hash.js.map