import path from "path"; import stream from "stream"; import { promisify } from "util"; import fs from "fs-extra"; import tempy from "tempy"; import got from "got"; import sumchecker from "sumchecker"; import decompress from "decompress"; import urlParse from "url-parse"; export default async function downloader(downloadUrl, checksumUrl, options) { // normalize options and set defaults options = { filename: options.filename ?? urlParse(downloadUrl).pathname.split("/").pop(), extract: !!options.extract, tempDir: options.tempDir ? path.resolve(process.cwd(), options.tempDir) : tempy.directory(), destDir: options.destDir ? path.resolve(process.cwd(), options.destDir) : path.resolve(process.cwd(), "download"), cleanDestDir: !!options.cleanDestDir, algorithm: options.algorithm ?? "sha256", encoding: options.encoding ?? "binary", }; try { // simultaneously download the desired file and its checksums await Promise.all([ downloadFile(downloadUrl, path.join(options.tempDir, options.filename)), downloadFile(checksumUrl, path.join(options.tempDir, "checksums.txt")), ]); // validate the checksum of the download await checkChecksum(options.tempDir, options.filename, "checksums.txt", options.algorithm, options.encoding); // optionally clear the target directory of existing files if (options.cleanDestDir) { await fs.remove(options.destDir); } // ensure the target directory exists await fs.mkdirp(options.destDir); if (options.extract) { // decompress download and move resulting files to final destination await decompress(path.join(options.tempDir, options.filename), options.destDir); return options.destDir; } else { // move verified download to final destination as-is await fs.copy(path.join(options.tempDir, options.filename), path.join(options.destDir, options.filename)); return path.join(options.destDir, options.filename); } } finally { // delete temporary directory await fs.remove(options.tempDir); } } // Download any file to any destination. Returns a promise. async function downloadFile(url, dest) { const pipeline = promisify(stream.pipeline); return await pipeline( got.stream(url, { followRedirect: true }), // GitHub releases redirect to unpredictable URLs fs.createWriteStream(dest), ); } // Check da checksum. async function checkChecksum(baseDir, downloadFile, checksumFile, algorithm, encoding) { const checker = new sumchecker.ChecksumValidator(algorithm, path.join(baseDir, checksumFile), { defaultTextEncoding: encoding, }); return await checker.validate(baseDir, downloadFile); }