UNPKG

cmake-ts

Version:

cmake-js rewrite in typescript to support advanced build configurations

158 lines (131 loc) 4.53 kB
import crypto from "crypto" import { tmpdir } from "os" import { basename, dirname, join } from "path" import { ensureDir, readFile, remove } from "fs-extra" import { DownloaderHelper } from "node-downloader-helper" import type { ExtractOptions as TarExtractOptions } from "tar" import extractTar from "tar/lib/extract.js" import { logger } from "./logger.js" import { retry } from "./retry.js" export type HashType = "sha256" | "sha512" | "sha1" | "md5" | "sha384" | "sha224" export type DownloadCoreOptions = { hashType?: HashType hashSum?: string timeout?: number } export type DownloadOptions = DownloadCoreOptions & { path?: string } export type DownloadFileOptions = DownloadCoreOptions & { path: string } export type DownloadTgzOptions = DownloadOptions & { removeAfterExtract?: boolean extractOptions?: TarExtractOptions } type DownloadResult = { filePath: string hash: string | undefined } /** Downloads a file to a temporary location and returns the file path and hash */ async function download(url: string, opts: DownloadOptions) { try { const filePath = opts.path ?? join(tmpdir(), "cmake-ts", `${Math.random()}`, basename(url)) const fileName = basename(filePath) const fileDir = dirname(filePath) logger.debug(`Downloading ${url} to ${filePath}`) await ensureDir(fileDir) const downloader = new DownloaderHelper(url, fileDir, { fileName, timeout: opts.timeout ?? -1, override: true, retry: { maxRetries: 3, delay: 1000, }, }) // Create a promise that will reject if an error occurs const downloadPromise = new Promise<void>((resolve, reject) => { downloader.on("error", (err) => { reject(err) }) downloader.on("end", () => { resolve() }) }) const result: DownloadResult = { filePath, hash: undefined, } // Start the download and wait for it to complete or error await Promise.all([downloader.start(), downloadPromise]) // calculate hash after download is complete result.hash = opts.hashType !== undefined ? await calculateHash(filePath, opts.hashType) : undefined return result } catch (err) { throw new Error(`Failed to download ${url}: ${err}`) } } /** Calculates the hash of a file */ export async function calculateHash(filePath: string, hashType: HashType) { const fileBuffer = await readFile(filePath) const shasum = crypto.createHash(hashType) shasum.update(fileBuffer) return shasum.digest("hex") } /** Downloads content from a URL and returns it as a string */ export async function downloadToString(url: string, options: DownloadCoreOptions = {}): Promise<string> { const { filePath } = await download(url, options) try { return await readFile(filePath, "utf8") } finally { await remove(filePath).catch((err) => { // Ignore errors logger.debug("Ignoring error removing temporary file", filePath, err) }) } } /** Downloads a file from a URL to a specified path */ export async function downloadFile(url: string, options: DownloadFileOptions): Promise<string | undefined> { const { hash } = await download(url, options) // Verify hash if needed if (!isHashSumValid(hash, options)) { throw new Error(`Checksum mismatch for download ${url}. Expected ${options.hashSum}, got ${hash}`) } return hash } /** Downloads and extracts a .tgz file */ export async function downloadTgz(url: string, options: DownloadTgzOptions): Promise<string | undefined> { const { filePath, hash } = await download(url, options) try { // Verify hash if needed if (!isHashSumValid(hash, options)) { throw new Error(`Checksum mismatch for download ${url}. Expected ${options.hashSum}, got ${hash}`) } // Extract the tgz file await retry(() => extractTar({ file: filePath, ...options.extractOptions, }), ) return hash } finally { if (options.removeAfterExtract ?? true) { await remove(filePath).catch((err) => { // Ignore errors logger.debug("Ignoring error removing temporary file", filePath, err) }) } } } /** Checks if the calculated hash matches the expected hash */ function isHashSumValid(sum: string | undefined, options: DownloadOptions): boolean { // No hash type or hash sum is valid return ( options.hashType === undefined || options.hashSum === undefined || // Check if the hash sum is valid options.hashSum === sum ) }