@overwolf/ow-app-builder-lib
Version:
ow-electron-builder lib
197 lines • 9.25 kB
JavaScript
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
;