app-builder-lib
Version:
electron-builder lib
524 lines • 28.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCacheDirectory = getCacheDirectory;
exports.extractArchive = extractArchive;
exports.downloadBuilderToolset = downloadBuilderToolset;
exports.downloadElectronArtifactZip = downloadElectronArtifactZip;
exports.downloadElectronArtifact = downloadElectronArtifact;
exports.getBinariesMirrorUrl = getBinariesMirrorUrl;
const get = require("@electron/get");
const get_1 = require("@electron/get");
const builder_util_1 = require("builder-util");
const _7zip_1 = require("../toolsets/7zip");
const multiProgress_1 = require("electron-publish/out/multiProgress");
const fs_1 = require("fs");
const fs = require("fs/promises");
const crypto = require("crypto");
const os = require("os");
const path = require("path");
const lockfile = require("proper-lockfile");
const promises_1 = require("stream/promises");
const tar = require("tar");
const unzipper = require("unzipper");
const builder_util_runtime_1 = require("builder-util-runtime");
const cacheState_1 = require("./cacheState");
function hashUrlSafe(input, length = 6) {
let hash = 5381;
for (let i = 0; i < input.length; i++) {
hash = ((hash << 5) + hash) ^ input.charCodeAt(i);
}
hash >>>= 0;
const out = hash.toString(36);
return out.length >= length ? out.slice(0, length) : out.padStart(length, "0");
}
function getCacheDirectory(options) {
var _a, _b, _c, _d, _e;
const { isAvoidSystemOnWindows = true, allowEnvVarOverride } = options;
const env = (_a = process.env.ELECTRON_BUILDER_CACHE) === null || _a === void 0 ? void 0 : _a.trim();
if (allowEnvVarOverride && env && path.parse(env).root) {
return env;
}
const appName = "electron-builder";
const platform = os.platform();
const homeDir = os.homedir();
if (platform === "darwin") {
return path.join(homeDir, "Library", "Caches", appName);
}
if (platform === "win32") {
const localAppData = (_b = process.env.LOCALAPPDATA) === null || _b === void 0 ? void 0 : _b.trim();
const username = (_d = (_c = process.env.USERNAME) === null || _c === void 0 ? void 0 : _c.trim()) === null || _d === void 0 ? void 0 : _d.toLowerCase();
// https://github.com/electron-userland/electron-builder/issues/1164
const isSystemUser = isAvoidSystemOnWindows && (((_e = localAppData === null || localAppData === void 0 ? void 0 : localAppData.toLowerCase()) === null || _e === void 0 ? void 0 : _e.includes("\\windows\\system32\\")) || username === "system");
if (!localAppData || isSystemUser) {
return path.join(os.tmpdir(), `${appName}-cache`);
}
return path.join(localAppData, appName, "Cache");
}
const xdgCache = process.env.XDG_CACHE_HOME;
return xdgCache && path.parse(xdgCache).root ? path.join(xdgCache, appName) : path.join(homeDir, ".cache", appName);
}
function resolveCacheMode() {
var _a;
const varName = "ELECTRON_DOWNLOAD_CACHE_MODE";
const cacheOverride = (_a = process.env[varName]) === null || _a === void 0 ? void 0 : _a.trim();
if (cacheOverride && Number(cacheOverride) in get_1.ElectronDownloadCacheMode) {
const mode = Number(cacheOverride);
builder_util_1.log.debug({ mode }, `cache mode overridden via env var ${varName}`);
return mode;
}
return get_1.ElectronDownloadCacheMode.ReadWrite;
}
async function extractZipStreaming(file, dir) {
var _a;
// Pass 1: read central directory once to collect Unix modes (one seek to EOF)
const zipDir = await unzipper.Open.file(file);
const entryModes = new Map();
for (const entry of zipDir.files) {
const mode = (entry.externalFileAttributes >> 16) & 0xffff;
if (mode > 0) {
entryModes.set(entry.path, mode);
}
}
const isSymlink = (mode) => (mode & 0o170000) === 0o120000;
// Pass 2: stream from byte 0 — no per-entry seeks, no Docker hang
const entries = (0, fs_1.createReadStream)(file).pipe(unzipper.Parse({ forceStream: true }));
for await (const entry of entries) {
const destPath = path.resolve(dir, entry.path);
if (!destPath.startsWith(dir + path.sep) && destPath !== dir) {
throw new Error(`Path traversal blocked: ${entry.path}`);
}
const mode = (_a = entryModes.get(entry.path)) !== null && _a !== void 0 ? _a : 0;
if (mode > 0 && isSymlink(mode)) {
const target = (await entry.buffer()).toString();
if (path.isAbsolute(target)) {
throw new Error(`Absolute symlink target blocked: ${target}`);
}
const resolvedTarget = path.resolve(path.dirname(destPath), target);
if (!resolvedTarget.startsWith(dir + path.sep) && resolvedTarget !== dir) {
throw new Error(`Symlink target escapes extraction dir: ${target}`);
}
await fs.mkdir(path.dirname(destPath), { recursive: true });
await fs.symlink(target, destPath);
}
else if (entry.type === "Directory") {
await fs.mkdir(destPath, { recursive: true });
entry.autodrain();
}
else {
await fs.mkdir(path.dirname(destPath), { recursive: true });
await (0, promises_1.pipeline)(entry, (0, fs_1.createWriteStream)(destPath));
if (mode > 0) {
await fs.chmod(destPath, mode & 0o7777);
}
}
}
}
async function extractArchive(file, dir) {
const tmpDir = `${dir}.tmp`;
await fs.mkdir(tmpDir, { recursive: true });
const release = await lockfile.lock(tmpDir, {
// 100 retries (not 15) so concurrent callers wait out a slow first extraction instead of failing
// with ELOCKED; the update heartbeat keeps an in-progress holder's lock fresh against `stale`.
retries: { retries: 100, minTimeout: 1000, maxTimeout: 5000 },
stale: 120000, // Increased from 60s to allow long-running extractions
});
try {
// rm + mkdir happen AFTER acquiring the lock so no concurrent caller can clear our tmpDir mid-extraction
await fs.rm(tmpDir, { recursive: true, force: true });
await fs.mkdir(tmpDir, { recursive: true });
// Guard against the transient window in @electron/get's non-atomic putFileInCache (remove → move).
// A concurrent worker may have deleted and not yet replaced the source archive; wait briefly for it.
for (let i = 0; !(await (0, builder_util_1.exists)(file)); i++) {
if (i >= 4) {
throw Object.assign(new Error(`Source archive not found after retries: ${file}`), { code: "ENOENT", path: file });
}
builder_util_1.log.warn({ file, attempt: i + 1 }, "source archive transiently missing, retrying");
await new Promise(r => setTimeout(r, 300 * (i + 1)));
}
if (file.endsWith(".tar.gz") || file.endsWith(".tgz")) {
await tar.extract({ file, cwd: tmpDir, strip: 1 });
}
else if (file.endsWith(".tar.xz") || file.endsWith(".txz")) {
// node-tar cannot decompress xz, so use 7za to turn the .tar.xz into a .tar, then extract that tar.
const cmd7za = await (0, _7zip_1.getPath7za)();
const xzOutDir = `${tmpDir}.xz`;
await fs.rm(xzOutDir, { recursive: true, force: true });
await fs.mkdir(xzOutDir, { recursive: true });
try {
await (0, builder_util_1.exec)(cmd7za, ["x", "-bd", file, (0, builder_util_1.to7zaOutputSwitch)((0, builder_util_1.sanitizeDirPath)(xzOutDir)), "-y"]);
const innerTar = (await fs.readdir(xzOutDir)).find(f => f.endsWith(".tar"));
if (innerTar == null) {
throw new Error(`xz decompression of ${path.basename(file)} produced no .tar archive`);
}
await tar.extract({ file: path.join(xzOutDir, innerTar), cwd: tmpDir, strip: 1 });
}
finally {
await fs.rm(xzOutDir, { recursive: true, force: true });
}
}
else if (file.endsWith(".zip")) {
await extractZipStreaming(file, tmpDir);
}
else if (file.endsWith(".7z")) {
const cmd7za = await (0, _7zip_1.getPath7za)();
try {
await (0, builder_util_1.exec)(cmd7za, ["x", "-bd", file, (0, builder_util_1.to7zaOutputSwitch)((0, builder_util_1.sanitizeDirPath)(tmpDir)), "-y"]);
}
catch (e) {
// Check if extraction actually failed or just had benign warnings
const files = await fs.readdir(tmpDir);
if (files.length === 0) {
builder_util_1.log.warn({ file, tmpDir, error: e.message }, "7z extraction produced no output");
throw new Error(`7z extraction failed for ${file}: ${e.message}`);
}
// If files were extracted despite the error, log and continue
builder_util_1.log.warn({ error: e.message, filesExtracted: files.length }, "7z reported error but extracted files");
}
}
else {
throw new Error(`Unsupported archive format: ${path.basename(file)}`);
}
// Verify extraction produced files
const extractedFiles = await fs.readdir(tmpDir);
if (extractedFiles.length === 0) {
throw new Error(`Extraction of ${path.basename(file)} produced no files`);
}
await fs.rm(dir, { recursive: true, force: true });
await fs.rename(tmpDir, dir);
}
finally {
await release().catch(err => builder_util_1.log.warn({ err }, "failed to release lockfile"));
}
}
async function downloadArtifactToFile(config, label) {
var _a, _b, _c, _d;
// Serialize concurrent downloads of the same artifact across vitest workers to prevent @electron/get's
// non-atomic putFileInCache (remove + move) from racing with a concurrent reader.
const artifactLockKey = crypto
.createHash("sha256")
.update(JSON.stringify({ v: config.version, p: config.platform, a: config.arch, n: config.artifactName }))
.digest("hex")
.slice(0, 20);
const artifactLockPath = path.join(os.tmpdir(), `eb-dl-${artifactLockKey}.lock`);
// This lock is taken when the artifact is not yet in @electron/get's download cache. 100 retries
// matches the other toolset locks; stale (10min) + proper-lockfile's update heartbeat keep a live
// downloader's lock fresh so the longer wait never falsely steals an in-progress download.
const releaseArtifactLock = await lockfile.lock(artifactLockPath, {
retries: { retries: 100, minTimeout: 500, maxTimeout: 5000 },
stale: 600000,
realpath: false,
});
let lastLoggedMilestone = -1;
const state = { bar: undefined };
const downloadOptions = {
timeout: { request: 10 * 60 * 1000 }, // prevent indefinite hang on stalled connections
...config.downloadOptions,
agent: (_b = (_a = config.downloadOptions) === null || _a === void 0 ? void 0 : _a.agent) !== null && _b !== void 0 ? _b : (0, builder_util_1.buildGotProxyAgent)(),
getProgressCallback: info => {
// @electron/get passes downloadOptions (including this callback) to its internal
// SHASUMS256.txt validation download. That file is tiny (<1 MB) and fires at 100%
// immediately, producing a spurious bar even when the artifact itself is cached.
// Skip progress display for any download whose total size is known and small.
if (info.total && info.total < 1000000) {
return Promise.resolve();
}
if (!state.bar && process.stdout.isTTY) {
builder_util_1.log.info({ label }, "downloading");
state.bar = new multiProgress_1.MultiProgress().createBar(`${" ".repeat(builder_util_1.PADDING + 2)}[:bar] :percent | ${label}`, { total: 100 });
}
const pct = info.percent != null ? Math.floor(info.percent * 100) : 0;
if (state.bar) {
state.bar.update(pct);
}
else {
// log every 25% milestone for non-TTY environments (e.g. CI logs) to provide some visibility into download progress without overwhelming logs with too many updates
const percentCompleted = info.transferred && info.total ? Math.floor((info.transferred / info.total) * 100) : Math.floor(pct / 25) * 25;
if (percentCompleted > lastLoggedMilestone && percentCompleted > 0) {
lastLoggedMilestone = percentCompleted;
builder_util_1.log.info({ label, progress: `${percentCompleted}%` }, percentCompleted !== 100 ? "downloading" : "downloaded");
}
}
return Promise.resolve();
},
};
const configWithProgress = { ...config, downloadOptions };
try {
let filePath;
try {
filePath = await (0, builder_util_runtime_1.retry)(() => get.downloadArtifact(configWithProgress), {
retries: 3,
interval: 2000,
backoff: 2000,
shouldRetry: (e) => {
var _a;
if (e instanceof builder_util_runtime_1.HttpError) {
return e.isServerError();
}
if (typeof ((_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.statusCode) === "number") {
return e.response.statusCode >= 500;
}
return typeof (e === null || e === void 0 ? void 0 : e.code) === "string" && ["ENOTFOUND", "ETIMEDOUT", "ECONNRESET", "EPIPE", "ENOENT"].includes(e.code);
},
});
}
catch (err) {
if (typeof (err === null || err === void 0 ? void 0 : err.message) === "string" && err.message.includes("dest already exists")) {
filePath = await get.downloadArtifact(configWithProgress);
}
else {
throw err;
}
}
if (!(await (0, builder_util_1.exists)(filePath))) {
builder_util_1.log.warn({ filePath, label }, "cached artifact missing from disk; retrying with cache write");
filePath = await get.downloadArtifact({ ...configWithProgress, cacheMode: get_1.ElectronDownloadCacheMode.WriteOnly });
}
if (!state.bar && lastLoggedMilestone === -1) {
builder_util_1.log.info({ label }, "using cached artifact");
}
return filePath;
}
finally {
(_c = state.bar) === null || _c === void 0 ? void 0 : _c.update(100);
(_d = state.bar) === null || _d === void 0 ? void 0 : _d.terminate();
await releaseArtifactLock().catch(err => builder_util_1.log.warn({ err }, "failed to release artifact download lock"));
}
}
/**
* Checks electron-builder's own archive cache for a previously downloaded archive.
* Validates the SHA-256 checksum when one is known. Returns the cached path on hit,
* null on miss or checksum mismatch (mismatch also deletes the stale file).
*/
async function resolveFromArchiveCache(archiveCachePath, label, expectedSha256) {
if (!(await (0, builder_util_1.exists)(archiveCachePath))) {
return null;
}
if (expectedSha256) {
const hash = await new Promise((resolve, reject) => {
const h = crypto.createHash("sha256");
const s = (0, fs_1.createReadStream)(archiveCachePath);
s.on("error", reject);
s.on("data", (chunk) => h.update(chunk));
s.on("end", () => resolve(h.digest("hex")));
});
if (hash !== expectedSha256) {
builder_util_1.log.warn({ file: label, archiveCachePath }, "cached archive checksum mismatch — removing and re-downloading");
await fs.rm(archiveCachePath).catch(() => { });
return null;
}
}
builder_util_1.log.debug({ file: label, archiveCachePath }, "using cached archive — skipping download");
return archiveCachePath;
}
/**
* Copies a freshly downloaded archive into electron-builder's own archive cache so that
* future builds (including offline environments) can skip the network request entirely.
*/
async function persistToArchiveCache(sourcePath, archiveCachePath) {
try {
await fs.mkdir(path.dirname(archiveCachePath), { recursive: true });
await fs.copyFile(sourcePath, archiveCachePath);
}
catch (err) {
builder_util_1.log.warn({ err, archiveCachePath }, "failed to persist archive to cache — will re-download next time");
}
}
/**
* Core download + extract engine. Handles lockfile, cache-hit short-circuit,
* progress bar, extraction (.zip or .tar.gz), and completion marker.
* Both public download functions delegate here after building their respective configs.
*/
async function downloadAndExtract(config, extractDir, label, archiveCachePath) {
var _a;
await fs.mkdir(extractDir, { recursive: true });
// Pre-lock fast path: only short-circuit for a definitively complete and valid cache.
// Do NOT clean up here — concurrent processes could race to delete under each other.
const stateData = await (0, cacheState_1.readCacheStateFile)(extractDir);
if ((stateData === null || stateData === void 0 ? void 0 : stateData.state) === cacheState_1.CacheState.complete) {
const isValid = await (0, cacheState_1.validateCacheDirectory)(extractDir, stateData.fileCount);
if (isValid) {
builder_util_1.log.debug({ file: label, path: extractDir }, "using cached artifact - cache valid");
return extractDir;
}
}
// Be patient: when many targets/builds contend on the same toolset cache concurrently, all but the
// first wait here while the winner downloads+extracts (a large bundle can take >1min). 15 retries
// (~67s) is too few and waiters fail with ELOCKED before the winner finishes; 100 matches the
// patience of withToolsetLock. proper-lockfile's update heartbeat keeps a live holder's lock fresh,
// so increasing waiter retries never falsely steals an in-progress extraction.
const release = await lockfile.lock(extractDir, {
retries: { retries: 100, minTimeout: 1000, maxTimeout: 5000 },
stale: 120000,
});
let downloadedFile = null;
try {
// Re-check after acquiring lock: another worker may have completed while we waited.
const stateDataAfterLock = await (0, cacheState_1.readCacheStateFile)(extractDir);
if ((stateDataAfterLock === null || stateDataAfterLock === void 0 ? void 0 : stateDataAfterLock.state) === cacheState_1.CacheState.complete) {
const isValid = await (0, cacheState_1.validateCacheDirectory)(extractDir, stateDataAfterLock.fileCount);
if (isValid) {
builder_util_1.log.debug({ file: label, path: extractDir }, "using cached artifact - skipping download/extract");
return extractDir;
}
builder_util_1.log.warn({ extractDir }, "Cache marked complete but files invalid, clearing");
}
else if ((stateDataAfterLock === null || stateDataAfterLock === void 0 ? void 0 : stateDataAfterLock.state) === cacheState_1.CacheState.corrupted) {
builder_util_1.log.warn({ extractDir }, "Corrupted cache detected, cleaning up and re-extracting");
}
// Cleanup inside the lock — skip lock files since we currently hold them
await (0, cacheState_1.cleanupCacheDirectory)(extractDir, { skipLockFiles: true });
await fs.mkdir(extractDir, { recursive: true });
// Check electron-builder's own archive cache before touching @electron/get.
// The cache lives alongside the extract dir: <cacheDir>/<releaseName>/<filename>.
// This lets repeated builds (or offline environments) skip the download entirely once
// the archive has been fetched at least once.
if (archiveCachePath) {
downloadedFile = await resolveFromArchiveCache(archiveCachePath, label, (_a = config.checksums) === null || _a === void 0 ? void 0 : _a[label]);
}
if (!downloadedFile) {
builder_util_1.log.debug({ file: label }, "downloading");
downloadedFile = await downloadArtifactToFile(config, label);
if (!downloadedFile) {
throw new Error(`Failed to download artifact: ${label}`);
}
// Persist the downloaded archive so future builds (and offline environments) can
// skip the network request entirely.
if (archiveCachePath) {
await persistToArchiveCache(downloadedFile, archiveCachePath);
}
}
await (0, cacheState_1.writeCacheState)(extractDir, cacheState_1.CacheState.downloaded);
// Mark extracting so stale-lock detection can recover a crashed mid-extraction process
await (0, cacheState_1.writeCacheState)(extractDir, cacheState_1.CacheState.extracting);
// Extract while holding the lock to prevent concurrent interference
await extractArchive(downloadedFile, extractDir);
// Compute metadata with a full recursive walk now that tmpDir has been renamed to extractDir
const { fileCount, extractedSize } = await (0, cacheState_1.computeCacheMetadata)(extractDir);
// Write complete state with metadata BEFORE releasing the lock; throwOnError=true so a
// failed write (disk full, permissions) propagates instead of silently producing a no-op cache
await (0, cacheState_1.writeCacheState)(extractDir, cacheState_1.CacheState.complete, { fileCount, extractedSize }, true);
}
finally {
await release().catch(err => builder_util_1.log.warn({ err }, "failed to release lockfile"));
}
return extractDir;
}
// ─── Public API ───────────────────────────────────────────────────────────────
/**
* Downloads a generic artifact (.tar.gz or .zip) from a GitHub release.
* Used for electron-builder-binaries tools (appimage, etc.).
*/
async function downloadBuilderToolset(options) {
const { releaseName, filenameWithExt, checksums, githubOrgRepo = "electron-userland/electron-builder-binaries", overrideUrl } = options;
if (/[/\\]|\.\./.test(filenameWithExt)) {
throw new Error(`downloadBuilderToolset: unsafe filenameWithExt "${filenameWithExt}" — must be a plain filename with no path separators or traversal sequences`);
}
const baseUrl = getBinariesMirrorUrl(githubOrgRepo);
const fullUrl = overrideUrl ? `${overrideUrl}/${filenameWithExt}` : `${baseUrl}${releaseName}/${filenameWithExt}`;
const suffix = hashUrlSafe(fullUrl, 5);
const folderName = `${filenameWithExt.replace(/\.(tar\.gz|tgz|tar\.xz|txz|zip|7z)$/, "")}-${suffix}`;
const extractDir = path.join(getCacheDirectory({ allowEnvVarOverride: true }), releaseName, folderName);
// Use resolveAssetURL so @electron/get's ELECTRON_MIRROR env var check cannot override
// the builder-binaries URL we've already resolved (see getArtifactRemoteURL in @electron/get).
const mirrorOptions = {
resolveAssetURL: async () => Promise.resolve(fullUrl),
};
// Predictable archive cache: <cacheDir>/<releaseName>/<filename>, next to the extract dir.
// downloadAndExtract checks here before touching @electron/get and persists the archive here
// after every successful download, so subsequent builds never need a network round-trip.
const archiveCachePath = path.join(getCacheDirectory({ allowEnvVarOverride: true }), releaseName, filenameWithExt);
const config = {
version: "9.9.9", // must be >1.3.2 to bypass @electron/get validation shortcut
artifactName: filenameWithExt,
cacheRoot: path.resolve(getCacheDirectory({ allowEnvVarOverride: true }), "downloads"),
cacheMode: resolveCacheMode(),
...(checksums != null ? { checksums } : { unsafelyDisableChecksums: true }),
mirrorOptions,
isGeneric: true,
};
return downloadAndExtract(config, extractDir, filenameWithExt, archiveCachePath);
}
// Keys present in ElectronGetOptions but absent from ElectronDownloadOptions.
// Used to discriminate between the two config shapes at runtime.
const ELECTRON_GET_EXCLUSIVE_KEYS = ["mirrorOptions", "force", "unsafelyDisableChecksums"];
function isElectronGetOptions(dl) {
return ELECTRON_GET_EXCLUSIVE_KEYS.some(k => Object.hasOwnProperty.call(dl, k));
}
/**
* Downloads and extracts an electron platform artifact (e.g. ffmpeg) using @electron/get.
* Deduplicates concurrent calls for the same artifact within the same process.
*/
function buildElectronArtifactConfig(options) {
const { electronDownload, arch, version, platformName: platform, artifactName, cacheDir: cacheRoot } = options;
let artifactConfig = { cacheRoot, platform, arch, version, artifactName };
if (electronDownload != null) {
if (isElectronGetOptions(electronDownload)) {
const { mirrorOptions, ...rest } = electronDownload;
artifactConfig = { ...artifactConfig, ...rest, cacheRoot, mirrorOptions };
}
else {
const { mirror, customDir, cache, customFilename, isVerifyChecksum, strictSSL, platform: overridePlatform, arch: overrideArch } = electronDownload;
// strictSSL: false disables TLS certificate validation for all
// electron/tool downloads. This option exists only for air-gapped or
// self-signed-cert environments; ensure your build network is fully trusted.
if (strictSSL === false) {
builder_util_1.log.warn({ option: "electronDownload.strictSSL" }, "strictSSL is false — TLS certificate validation is DISABLED for Electron downloads. Only use this option in a trusted, isolated build environment.");
}
artifactConfig = {
...artifactConfig,
unsafelyDisableChecksums: isVerifyChecksum === false,
cacheRoot: cache !== null && cache !== void 0 ? cache : cacheRoot,
mirrorOptions: {
mirror: mirror || undefined,
customDir: customDir || undefined,
customFilename: customFilename || undefined,
},
...(strictSSL === false ? { downloadOptions: { https: { rejectUnauthorized: false } } } : {}),
};
if (overridePlatform != null) {
artifactConfig.platform = overridePlatform;
}
if (overrideArch != null) {
artifactConfig.arch = overrideArch;
}
}
}
artifactConfig.cacheMode = resolveCacheMode();
return artifactConfig;
}
/**
* Downloads the electron artifact zip via @electron/get (with caching) and returns the zip file path.
* Use when you need to extract the zip yourself (e.g. directly to appOutDir to preserve empty dirs and symlinks).
*/
function downloadElectronArtifactZip(options) {
const config = buildElectronArtifactConfig(options);
return downloadArtifactToFile(config, config.artifactName);
}
async function downloadElectronArtifact(options) {
const { arch, version, platformName: platform, artifactName } = options;
const artifactConfig = buildElectronArtifactConfig(options);
const suffix = hashUrlSafe(JSON.stringify(artifactConfig), 5);
const folderName = `${artifactName}-v${version}-${platform}-${arch}-${suffix}`;
const extractDir = path.join(getCacheDirectory({ allowEnvVarOverride: true }), `${artifactName}-v${version}`, folderName);
return downloadAndExtract(artifactConfig, extractDir, artifactName);
}
/**
* Get the binaries mirror URL from environment variables.
* Supports various npm config formats and falls back to GitHub.
*/
function getBinariesMirrorUrl(githubOrgRepo) {
const allowHttp = process.env["ELECTRON_BUILDER_BINARIES_ALLOW_HTTP"] === "true";
for (const envVar of [
"NPM_CONFIG_ELECTRON_BUILDER_BINARIES_MIRROR",
"npm_config_electron_builder_binaries_mirror",
"npm_package_config_electron_builder_binaries_mirror",
"ELECTRON_BUILDER_BINARIES_MIRROR",
]) {
const url = (0, builder_util_1.parseValidEnvVarUrl)(envVar, allowHttp);
if (url) {
return url.endsWith("/") ? url : `${url}/`;
}
}
return `https://github.com/${githubOrgRepo}/releases/download/`;
}
//# sourceMappingURL=electronGet.js.map