UNPKG

asset-hash

Version:

Very fast asset hashing function for using e.g. during front-end deployments.

254 lines (214 loc) 7 kB
/*! asset-hash v4.1.0 by Sebastian Werner <s.werner@sebastian-software.de> */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); require('core-js/modules/es.error.cause.js'); var fs = require('fs'); var path = require('path'); var crypto = require('crypto'); var xxhash = require('xxhash-wasm'); var farmhash = require('farmhash'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; } var xxhash__default = /*#__PURE__*/_interopDefaultLegacy(xxhash); function baseEncodeFactory(charset) { const radix = BigInt(charset.length); const decode = str => { let result = BigInt(0); for (const chr of str) { const index = BigInt(charset.indexOf(chr)); result = radix * result + index; } return result; }; const encode = (number, maxLength = Infinity) => { let result = ""; while (number > 0) { const mod = number % radix; result = charset[Number(mod)] + result; number = (number - mod) / radix; if (result.length === maxLength) { return result; } } return result || `0`; }; return { decode, encode }; } const baseEncoder = { base26: baseEncodeFactory("abcdefghijklmnopqrstuvwxyz"), base32: baseEncodeFactory("123456789abcdefghjkmnpqrstuvwxyz"), // no 0lio base36: baseEncodeFactory("0123456789abcdefghijklmnopqrstuvwxyz"), base49: baseEncodeFactory("abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"), // no lIO base52: baseEncodeFactory("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), base58: baseEncodeFactory("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"), // no 0lIO base62: baseEncodeFactory("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") }; function computeDigest(rawDigest, options = {}) { let output = null; const { encoding, maxLength } = options; const rawIsNumber = typeof rawDigest === "number" || typeof rawDigest === "bigint"; const rawIsBuffer = rawDigest instanceof Buffer; // Fast-path for number => hex if (rawIsNumber && encoding === "hex") { output = rawDigest.toString(16); } else { if (encoding === "base26" || encoding === "base32" || encoding === "base36" || encoding === "base49" || encoding === "base52" || encoding === "base58" || encoding === "base62") { const valueAsBigInt = rawDigest instanceof Buffer ? BigInt(`0x${rawDigest.toString("hex")}`) : typeof rawDigest === "number" ? BigInt(rawDigest) : BigInt(rawDigest); return baseEncoder[encoding].encode(valueAsBigInt, maxLength); } const valueAsBuffer = rawIsBuffer ? rawDigest : Buffer.from(rawDigest.toString()); output = valueAsBuffer.toString(encoding); } return maxLength != null ? output.slice(0, maxLength) : output; } let xxHashInstance; let hasherReady = false; async function initHashClasses() { if (hasherReady) { return; } // Allow failing initialization for platforms < Node v16 try { xxHashInstance = await xxhash__default(); } catch {} hasherReady = true; } /** * Make Node.js crypto hash signature compatible to Hash */ function cryptoEnvelope(hash) { const envelopeHash = { update: input => { hash.update(input); return envelopeHash; }, digest: () => hash.digest() }; return envelopeHash; } function farmhashEnvelope(hash) { const data = []; const envelopeHash = { update: input => { data.push(input); return envelopeHash; }, digest: () => { const stringOrNumber = hash(Buffer.concat(data)); return BigInt(stringOrNumber); } }; return envelopeHash; } /** * Creates hasher instance */ function createHasher(algorithm) { if (!hasherReady) { throw new Error("Hasher was not correctly initialized. Call `await initHashClasses()` first."); } let hasher; if (algorithm === "xxhash32" || algorithm === "xxhash64") { if (!xxHashInstance) { throw new Error("Invalid xxhash WASM instance!"); } hasher = algorithm === "xxhash32" ? xxHashInstance.create32() : xxHashInstance.create64(); } else if (algorithm === "farmhash32" || algorithm === "farmhash64") { hasher = farmhashEnvelope(algorithm === "farmhash32" ? farmhash.fingerprint32 : farmhash.fingerprint64); } else { hasher = cryptoEnvelope(crypto.createHash(algorithm)); } return hasher; } const NODE_MAJOR_VERSION = parseInt(process.versions.node.split('.')[0], 10); const NODE_SUPPORTS_BIGINT_SINCE = 16; const DEFAULT_ALGORITHM = NODE_MAJOR_VERSION < NODE_SUPPORTS_BIGINT_SINCE ? "farmhash64" : "xxhash64"; const DEFAULT_ENCODING = "base52"; const DEFAULT_MAX_LENGTH = 8; class Hasher { algorithm = DEFAULT_ALGORITHM; encoding = DEFAULT_ENCODING; maxLength = Infinity; constructor(options = {}) { if (options.algorithm) { this.algorithm = options.algorithm; } if (options.encoding) { this.encoding = options.encoding; } if (options.maxLength) { this.maxLength = options.maxLength; } this.hasher = createHasher(this.algorithm); } update(data) { const buffer = data instanceof Buffer ? data : Buffer.from(data.toString(), "utf-8"); this.hasher.update(buffer); return this; } digest(encoding, maxLength) { return computeDigest(this.hasher.digest(), { encoding: encoding || this.encoding, maxLength: maxLength || this.maxLength }); } } /** * Get hash from file * * @param fileName Filename of file to hash */ function getHash(fileName, options) { const { algorithm, encoding, maxLength } = options || {}; return new Promise(async (resolve, reject) => { try { await initHashClasses(); const hasher = createHasher(algorithm || DEFAULT_ALGORITHM); fs.createReadStream(fileName).on("data", data => { hasher.update(data); }).on("error", error => { reject(error); }).on("end", () => { try { const digest = computeDigest(hasher.digest(), { encoding: encoding || DEFAULT_ENCODING, maxLength: maxLength || DEFAULT_MAX_LENGTH }); digest ? resolve(digest) : reject(new Error("Unexpected error while generating digest!")); } catch (error) { reject(error); } }); } catch (error) { reject(error); } }); } /** * Get new filename based upon hash and extension of file * * @param fileName Filename of file to hash */ async function getHashedName(fileName, options) { const hashed = await getHash(fileName, options); const extension = path.extname(fileName); return hashed + extension; } exports.DEFAULT_ALGORITHM = DEFAULT_ALGORITHM; exports.DEFAULT_ENCODING = DEFAULT_ENCODING; exports.DEFAULT_MAX_LENGTH = DEFAULT_MAX_LENGTH; exports.Hasher = Hasher; exports.getHash = getHash; exports.getHashedName = getHashedName; exports.initHashClasses = initHashClasses; //# sourceMappingURL=index.cjs.js.map