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