UNPKG

@overwolf/ow-app-builder-lib

Version:
197 lines 9.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AsarPackager = void 0; const asar_1 = require("@electron/asar"); const builder_util_1 = require("builder-util"); const fs_1 = require("builder-util/out/fs"); const fs = require("fs-extra"); const fs_extra_1 = require("fs-extra"); const os_1 = require("os"); const path = require("path"); const appFileCopier_1 = require("../util/appFileCopier"); const unpackDetector_1 = require("./unpackDetector"); /** @internal */ class AsarPackager { constructor(packager, config) { this.packager = packager; this.config = config; this.fileCopier = new fs_1.FileCopier(); this.outFile = path.join(config.resourcePath, `app.asar`); this.tmpDir = packager.info.tempDirManager; this.cancellationToken = packager.info.cancellationToken; } async pack(fileSets) { this.rootForAppFilesWithoutAsar = await this.tmpDir.getTempDir({ prefix: "asar-app" }); const orderedFileSets = [ // Write dependencies first to minimize offset changes to asar header ...fileSets.slice(1), // Finish with the app files that change most often fileSets[0], ].map(orderFileSet); const { unpackedPaths, copiedFiles } = await this.detectAndCopy(orderedFileSets); const unpackGlob = unpackedPaths.length > 1 ? `{${unpackedPaths.join(",")}}` : unpackedPaths.pop(); await this.executeElectronAsar(copiedFiles, unpackGlob); } async executeElectronAsar(copiedFiles, unpackGlob) { let ordering = this.config.options.ordering || undefined; if (!ordering) { // `copiedFiles` are already ordered due to `orderedFileSets` input, so we just map to their relative paths (via substring) within the asar. const filesSorted = copiedFiles.map(file => file.substring(this.rootForAppFilesWithoutAsar.length)); ordering = await this.tmpDir.getTempFile({ prefix: "asar-ordering", suffix: ".txt" }); await fs.writeFile(ordering, filesSorted.join("\n")); } const options = { unpack: unpackGlob, unpackDir: unpackGlob, ordering, dot: true, }; // override logger temporarily to clean up console (electron/asar does some internal logging that blogs up the default electron-builder logs) const consoleLogger = console.log; console.log = (...args) => { if (args[0] === "Ordering file has 100% coverage.") { return; // no need to log, this means our ordering logic is working correctly } builder_util_1.log.info({ args }, "logging @electron/asar"); }; await (0, asar_1.createPackageWithOptions)(this.rootForAppFilesWithoutAsar, this.outFile, options); console.log = consoleLogger; } async detectAndCopy(fileSets) { var _a; const taskManager = new builder_util_1.AsyncTaskManager(this.cancellationToken); const unpackedPaths = new Set(); const copiedFiles = new Set(); const createdSourceDirs = new Set(); const links = []; const symlinkType = (0, os_1.platform)() === "win32" ? "junction" : "file"; const matchUnpacker = (file, dest, stat, tmpUnpackedPaths) => { var _a, _b; if ((_b = (_a = this.config).unpackPattern) === null || _b === void 0 ? void 0 : _b.call(_a, file, stat)) { builder_util_1.log.debug({ file }, "unpacking"); tmpUnpackedPaths.add(dest); return; } }; const writeFileOrProcessSymlink = async (options) => { const { transformedData, file, destination, stat, fileSet } = options; if (!stat.isFile() && !stat.isSymbolicLink()) { return; } copiedFiles.add(destination); const dir = path.dirname(destination); if (!createdSourceDirs.has(dir)) { await (0, fs_extra_1.mkdir)(dir, { recursive: true }); createdSourceDirs.add(dir); } // write any data if provided, skip symlink check if (transformedData != null) { return fs.writeFile(destination, transformedData, { mode: stat.mode }); } const realPathFile = await fs.realpath(file); const realPathRelative = path.relative(fileSet.src, realPathFile); const isOutsidePackage = realPathRelative.startsWith(".."); if (isOutsidePackage) { builder_util_1.log.error({ source: builder_util_1.log.filePath(file), realPathFile: builder_util_1.log.filePath(realPathFile) }, `unable to copy, file is symlinked outside the package`); throw new Error(`Cannot copy file (${path.basename(file)}) symlinked to file (${path.basename(realPathFile)}) outside the package as that violates asar security integrity`); } // not a symlink, copy directly if (file === realPathFile) { return this.fileCopier.copy(file, destination, stat); } // okay, it must be a symlink. evaluate link to be relative to source file in asar let link = await (0, fs_extra_1.readlink)(file); if (path.isAbsolute(link)) { link = path.relative(path.dirname(file), link); } links.push({ file: destination, link }); }; for (const fileSet of fileSets) { if (this.config.options.smartUnpack !== false) { (0, unpackDetector_1.detectUnpackedDirs)(fileSet, unpackedPaths); } // Don't use Promise.all, we need to retain order of execution/iteration through the ordered fileset const tmpUnpackedPaths = new Set(); for (let i = 0; i < fileSet.files.length; i++) { const file = fileSet.files[i]; const transformedData = (_a = fileSet.transformedFiles) === null || _a === void 0 ? void 0 : _a.get(i); const stat = fileSet.metadata.get(file); const relative = path.relative(this.config.defaultDestination, (0, appFileCopier_1.getDestinationPath)(file, fileSet)); const destination = path.resolve(this.rootForAppFilesWithoutAsar, relative); matchUnpacker(file, destination, stat, tmpUnpackedPaths); taskManager.addTask(writeFileOrProcessSymlink({ transformedData, file, destination, stat, fileSet })); if (taskManager.tasks.length > fs_1.MAX_FILE_REQUESTS) { await taskManager.awaitTasks(); } } if (tmpUnpackedPaths.size === fileSet.files.length) { const relative = path.relative(this.config.defaultDestination, fileSet.destination); unpackedPaths.add(relative); } else { // add all tmpUnpackedPaths to unpackedPaths for (const it of tmpUnpackedPaths) { unpackedPaths.add(it); } } } // finish copy then set up all symlinks await taskManager.awaitTasks(); for (const it of links) { taskManager.addTask((0, fs_extra_1.symlink)(it.link, it.file, symlinkType)); if (taskManager.tasks.length > fs_1.MAX_FILE_REQUESTS) { await taskManager.awaitTasks(); } } await taskManager.awaitTasks(); return { unpackedPaths: Array.from(unpackedPaths), copiedFiles: Array.from(copiedFiles), }; } } exports.AsarPackager = AsarPackager; function orderFileSet(fileSet) { const sortedFileEntries = Array.from(fileSet.files.entries()); sortedFileEntries.sort(([, a], [, b]) => { if (a === b) { return 0; } // Place addons last because their signature changes per build const isAAddon = a.endsWith(".node"); const isBAddon = b.endsWith(".node"); if (isAAddon && !isBAddon) { return 1; } if (isBAddon && !isAAddon) { return -1; } // Otherwise order by name return a < b ? -1 : 1; }); let transformedFiles; if (fileSet.transformedFiles) { transformedFiles = new Map(); const indexMap = new Map(); for (const [newIndex, [oldIndex]] of sortedFileEntries.entries()) { indexMap.set(oldIndex, newIndex); } for (const [oldIndex, value] of fileSet.transformedFiles) { const newIndex = indexMap.get(oldIndex); if (newIndex === undefined) { const file = fileSet.files[oldIndex]; throw new Error(`Internal error: ${file} was lost while ordering asar`); } transformedFiles.set(newIndex, value); } } const { src, destination, metadata } = fileSet; return { src, destination, metadata, files: sortedFileEntries.map(([, file]) => file), transformedFiles, }; } //# sourceMappingURL=asarUtil.js.map