UNPKG

@electron/get

Version:

Utility for downloading artifacts from different versions of Electron

100 lines 4.25 kB
import debug from 'debug'; import envPaths from 'env-paths'; import fs from 'graceful-fs'; import crypto from 'node:crypto'; import path from 'node:path'; import { pipeline } from 'node:stream/promises'; import url from 'node:url'; const d = debug('@electron/get:cache'); const defaultCacheRoot = envPaths('electron', { suffix: '', }).cache; export class Cache { cacheRoot; constructor(cacheRoot = defaultCacheRoot) { this.cacheRoot = cacheRoot; } static getCacheDirectory(downloadUrl) { const parsedDownloadUrl = url.parse(downloadUrl); // eslint-disable-next-line @typescript-eslint/no-unused-vars const { search, hash, pathname, ...rest } = parsedDownloadUrl; const strippedUrl = url.format({ ...rest, pathname: path.dirname(pathname || 'electron') }); return crypto.createHash('sha256').update(strippedUrl).digest('hex'); } getCachePath(downloadUrl, fileName) { return path.resolve(this.cacheRoot, Cache.getCacheDirectory(downloadUrl), fileName); } getPathForFileInCache(url, fileName) { const cachePath = this.getCachePath(url, fileName); if (fs.existsSync(cachePath)) { return cachePath; } return null; } async hashFile(path) { const hasher = crypto.createHash('sha256'); await pipeline(fs.createReadStream(path), hasher); return hasher.digest('hex'); } async putFileInCache(url, currentPath, fileName) { const attempt = async (attemptsLeft = 3) => { try { const cachePath = this.getCachePath(url, fileName); d(`Moving ${currentPath} to ${cachePath}`); if (!fs.existsSync(path.dirname(cachePath))) { await fs.promises.mkdir(path.dirname(cachePath), { recursive: true }); } if (fs.existsSync(cachePath)) { const [existingHash, currentHash] = await Promise.all([ this.hashFile(cachePath), this.hashFile(currentPath), ]); if (existingHash !== currentHash) { d('* Replacing existing file as it does not match our inbound file'); await fs.promises.rm(cachePath, { recursive: true, force: true }); } else { d('* Using existing file as the hash matches our inbound file, no need to replace'); return cachePath; } } try { await fs.promises.rename(currentPath, cachePath); } catch (err) { if (err.code === 'EXDEV') { // Cross-device link, fallback to copy and delete await fs.promises.cp(currentPath, cachePath, { force: true, recursive: true, verbatimSymlinks: true, }); await fs.promises.rm(currentPath, { force: true, recursive: true }); } else { throw err; } } return cachePath; } catch (err) { if (process.platform === 'win32' && err.code === 'EPERM') { // On windows this normally means we're fighting another instance of @electron/get // also trying to write this file to the cache d('Experienced error putting thing in cache', err); if (attemptsLeft > 0) { d('Trying again in a few seconds'); await new Promise((resolve) => { setTimeout(resolve, 2000); }); return await attempt(attemptsLeft - 1); } d('We have already tried too many times, giving up...'); } throw err; } }; return await attempt(); } } //# sourceMappingURL=Cache.js.map