"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DmgTarget = void 0; const app_builder_lib_1 = require("app-builder-lib"); const macCodeSign_1 = require("app-builder-lib/out/codeSign/macCodeSign"); const differentialUpdateInfoBuilder_1 = require("app-builder-lib/out/targets/differentialUpdateInfoBuilder"); const appBuilder_1 = require("app-builder-lib/out/util/appBuilder"); const filename_1 = require("app-builder-lib/out/util/filename"); const builder_util_1 = require("builder-util"); const fs_1 = require("builder-util/out/fs"); const fs_extra_1 = require("fs-extra"); const path = require("path"); const dmgLicense_1 = require("./dmgLicense"); const dmgUtil_1 = require("./dmgUtil"); const os_1 = require("os"); class DmgTarget extends app_builder_lib_1.Target { constructor(packager, outDir) { super("dmg"); this.packager = packager; this.outDir = outDir; this.options = this.packager.config.dmg || Object.create(null); } async build(appPath, arch) { const packager = this.packager; // tslint:disable-next-line:no-invalid-template-strings const artifactName = packager.expandArtifactNamePattern(this.options, "dmg", arch, "${productName}-" + (packager.platformSpecificBuildOptions.bundleShortVersion || "${version}") + "-${arch}.${ext}", true, packager.platformSpecificBuildOptions.defaultArch); const artifactPath = path.join(this.outDir, artifactName); await packager.info.callArtifactBuildStarted({ targetPresentableName: "DMG", file: artifactPath, arch, }); const volumeName = (0, filename_1.sanitizeFileName)(this.computeVolumeName(arch, this.options.title)); const tempDmg = await createStageDmg(await packager.getTempFile(".dmg"), appPath, volumeName); const specification = await this.computeDmgOptions(); // https://github.com/electron-userland/electron-builder/issues/2115 const backgroundFile = specification.background == null ? null : await transformBackgroundFileIfNeed(specification.background, packager.info.tempDirManager); const finalSize = await computeAssetSize(packager.info.cancellationToken, tempDmg, specification, backgroundFile); const expandingFinalSize = finalSize * 0.1 + finalSize; await (0, builder_util_1.exec)("hdiutil", ["resize", "-size", expandingFinalSize.toString(), tempDmg]); const volumePath = path.join("/Volumes", volumeName); if (await (0, fs_1.exists)(volumePath)) { builder_util_1.log.debug({ volumePath }, "unmounting previous disk image"); await (0, dmgUtil_1.detach)(volumePath); } if (!(await (0, dmgUtil_1.attachAndExecute)(tempDmg, true, () => customizeDmg(volumePath, specification, packager, backgroundFile)))) { return; } // dmg file must not exist otherwise hdiutil failed (https://github.com/electron-userland/electron-builder/issues/1308#issuecomment-282847594), so, -ov must be specified const args = ["convert", tempDmg, "-ov", "-format", specification.format, "-o", artifactPath]; if (specification.format === "UDZO") { args.push("-imagekey", `zlib-level=${process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL || "9"}`); } await (0, builder_util_1.spawn)("hdiutil", addLogLevel(args)); if (this.options.internetEnabled && parseInt((0, os_1.release)().split(".")[0], 10) < 19) { await (0, builder_util_1.exec)("hdiutil", addLogLevel(["internet-enable"]).concat(artifactPath)); } const licenseData = await (0, dmgLicense_1.addLicenseToDmg)(packager, artifactPath); if (packager.packagerOptions.effectiveOptionComputed != null) { await packager.packagerOptions.effectiveOptionComputed({ licenseData }); } if (this.options.sign === true) { await this.signDmg(artifactPath); } const safeArtifactName = packager.computeSafeArtifactName(artifactName, "dmg"); const updateInfo = this.options.writeUpdateInfo === false ? null : await (0, differentialUpdateInfoBuilder_1.createBlockmap)(artifactPath, this, packager, safeArtifactName); await packager.info.callArtifactBuildCompleted({ file: artifactPath, safeArtifactName, target: this, arch, packager, isWriteUpdateInfo: updateInfo != null, updateInfo, }); } async signDmg(artifactPath) { if (!(0, macCodeSign_1.isSignAllowed)(false)) { return; } const packager = this.packager; const qualifier = packager.platformSpecificBuildOptions.identity; // explicitly disabled if set to null if (qualifier === null) { // macPackager already somehow handle this situation, so, here just return return; } const keychainFile = (await packager.codeSigningInfo.value).keychainFile; const certificateType = "Developer ID Application"; let identity = await (0, macCodeSign_1.findIdentity)(certificateType, qualifier, keychainFile); if (identity == null) { identity = await (0, macCodeSign_1.findIdentity)("Mac Developer", qualifier, keychainFile); if (identity == null) { return; } } const args = ["--sign", identity.hash]; if (keychainFile != null) { args.push("--keychain", keychainFile); } args.push(artifactPath); await (0, builder_util_1.exec)("codesign", args); } computeVolumeName(arch, custom) { const appInfo = this.packager.appInfo; const shortVersion = this.packager.platformSpecificBuildOptions.bundleShortVersion || appInfo.version; const archString = (0, builder_util_1.getArchSuffix)(arch, this.packager.platformSpecificBuildOptions.defaultArch); if (custom == null) { return `${appInfo.productFilename} ${shortVersion}${archString}`; } return custom .replace(/\${arch}/g, archString) .replace(/\${shortVersion}/g, shortVersion) .replace(/\${version}/g, appInfo.version) .replace(/\${name}/g, appInfo.name) .replace(/\${productName}/g, appInfo.productName); } // public to test async computeDmgOptions() { const packager = this.packager; const specification = { ...this.options }; if (specification.icon == null && specification.icon !== null) { specification.icon = await packager.getIconPath(); } if (specification.icon != null && (0, builder_util_1.isEmptyOrSpaces)(specification.icon)) { throw new builder_util_1.InvalidConfigurationError("dmg.icon cannot be specified as empty string"); } const background = specification.background; if (specification.backgroundColor != null) { if (background != null) { throw new builder_util_1.InvalidConfigurationError("Both dmg.backgroundColor and dmg.background are specified — please set the only one"); } } else if (background == null) { specification.background = await (0, dmgUtil_1.computeBackground)(packager); } else { specification.background = path.resolve(packager.info.projectDir, background); } if (specification.format == null) { if (process.env.ELECTRON_BUILDER_COMPRESSION_LEVEL != null) { ; specification.format = "UDZO"; } else if (packager.compression === "store") { specification.format = "UDRO"; } else { specification.format = packager.compression === "maximum" ? "UDBZ" : "UDZO"; } } if (specification.contents == null) { specification.contents = [ { x: 130, y: 220, }, { x: 410, y: 220, type: "link", path: "/Applications", }, ]; } return specification; } } exports.DmgTarget = DmgTarget; async function createStageDmg(tempDmg, appPath, volumeName) { //noinspection SpellCheckingInspection const imageArgs = addLogLevel(["create", "-srcfolder", appPath, "-volname", volumeName, "-anyowners", "-nospotlight", "-format", "UDRW"]); if (builder_util_1.log.isDebugEnabled) { imageArgs.push("-debug"); } let filesystem = ["HFS+", "-fsargs", "-c c=64,a=16,e=16"]; if (process.arch === "arm64") { // Apple Silicon `hdiutil` dropped support for HFS+, so we force the latest type // https://github.com/electron-userland/electron-builder/issues/4606 filesystem = ["APFS"]; builder_util_1.log.warn(null, "Detected arm64 process, HFS+ is unavailable. Creating dmg with APFS - supports Mac OSX 10.12+"); } imageArgs.push("-fs", ...filesystem); imageArgs.push(tempDmg); // The reason for retrying up to ten times is that hdiutil create in some cases fail to unmount due to "resource busy". // https://github.com/electron-userland/electron-builder/issues/5431 await (0, builder_util_1.retry)(() => (0, builder_util_1.spawn)("hdiutil", imageArgs), 5, 1000); return tempDmg; } function addLogLevel(args) { args.push(process.env.DEBUG_DMG === "true" ? "-verbose" : "-quiet"); return args; } async function computeAssetSize(cancellationToken, dmgFile, specification, backgroundFile) { const asyncTaskManager = new builder_util_1.AsyncTaskManager(cancellationToken); asyncTaskManager.addTask((0, fs_extra_1.stat)(dmgFile)); if (specification.icon != null) { asyncTaskManager.addTask((0, fs_1.statOrNull)(specification.icon)); } if (backgroundFile != null) { asyncTaskManager.addTask((0, fs_extra_1.stat)(backgroundFile)); } let result = 32 * 1024; for (const stat of await asyncTaskManager.awaitTasks()) { if (stat != null) { result += stat.size; } } return result; } async function customizeDmg(volumePath, specification, packager, backgroundFile) { const window = specification.window; const isValidIconTextSize = !!specification.iconTextSize && specification.iconTextSize >= 10 && specification.iconTextSize <= 16; const iconTextSize = isValidIconTextSize ? specification.iconTextSize : 12; const env = { ...process.env, volumePath, appFileName: `${packager.appInfo.productFilename}.app`, iconSize: specification.iconSize || 80, iconTextSize, PYTHONIOENCODING: "utf8", }; if (specification.backgroundColor != null || specification.background == null) { env.backgroundColor = specification.backgroundColor || "#ffffff"; if (window != null) { env.windowX = (window.x == null ? 100 : window.x).toString(); env.windowY = (window.y == null ? 400 : window.y).toString(); env.windowWidth = (window.width || 540).toString(); env.windowHeight = (window.height || 380).toString(); } } else { delete env.backgroundColor; } const args = ["dmg", "--volume", volumePath]; if (specification.icon != null) { args.push("--icon", (await packager.getResource(specification.icon))); } if (backgroundFile != null) { args.push("--background", backgroundFile); } const data = await (0, appBuilder_1.executeAppBuilderAsJson)(args); if (data.backgroundWidth != null) { env.windowWidth = window == null ? null : window.width; env.windowHeight = window == null ? null : window.height; if (env.windowWidth == null) { env.windowWidth = data.backgroundWidth.toString(); } if (env.windowHeight == null) { env.windowHeight = data.backgroundHeight.toString(); } if (env.windowX == null) { env.windowX = 400; } if (env.windowY == null) { env.windowY = Math.round((1440 - env.windowHeight) / 2).toString(); } } Object.assign(env, data); const asyncTaskManager = new builder_util_1.AsyncTaskManager(packager.info.cancellationToken); env.iconLocations = await computeDmgEntries(specification, volumePath, packager, asyncTaskManager); await asyncTaskManager.awaitTasks(); const executePython = async (execName) => { let pythonPath = process.env.PYTHON_PATH; if (!pythonPath) { pythonPath = (await (0, builder_util_1.exec)("which", [execName])).trim(); } await (0, builder_util_1.exec)(pythonPath, [path.join((0, dmgUtil_1.getDmgVendorPath)(), "dmgbuild/core.py")], { cwd: (0, dmgUtil_1.getDmgVendorPath)(), env, }); }; try { await executePython("python3"); } catch (error) { await executePython("python"); } return packager.packagerOptions.effectiveOptionComputed == null || !(await packager.packagerOptions.effectiveOptionComputed({ volumePath, specification, packager })); } async function computeDmgEntries(specification, volumePath, packager, asyncTaskManager) { let result = ""; for (const c of specification.contents) { if (c.path != null && c.path.endsWith(".app") && c.type !== "link") { builder_util_1.log.warn({ path: c.path, reason: "actual path to app will be used instead" }, "do not specify path for application"); } const entryPath = c.path || `${packager.appInfo.productFilename}.app`; const entryName = c.name || path.basename(entryPath); const escapedEntryName = entryName.replace(/['\\]/g, match => `\\${match}`); if (result.length !== 0) { result += ",\n"; } result += `'${escapedEntryName}': (${c.x}, ${c.y})`; if (c.type === "link") { asyncTaskManager.addTask((0, builder_util_1.exec)("ln", ["-s", `/${entryPath.startsWith("/") ? entryPath.substring(1) : entryPath}`, `${volumePath}/${entryName}`])); } // use c.path instead of entryPath (to be sure that this logic is not applied to .app bundle) https://github.com/electron-userland/electron-builder/issues/2147 else if (!(0, builder_util_1.isEmptyOrSpaces)(c.path) && (c.type === "file" || c.type === "dir")) { const source = await packager.getResource(c.path); if (source == null) { builder_util_1.log.warn({ entryPath, reason: "doesn't exist" }, "skipped DMG item copying"); continue; } const destination = `${volumePath}/${entryName}`; asyncTaskManager.addTask(c.type === "dir" || (await (0, fs_extra_1.stat)(source)).isDirectory() ? (0, fs_1.copyDir)(source, destination) : (0, fs_1.copyFile)(source, destination)); } } return result; } async function transformBackgroundFileIfNeed(file, tmpDir) { if (file.endsWith(".tiff") || file.endsWith(".TIFF")) { return file; } const retinaFile = file.replace(/\.([a-z]+)$/, "@2x.$1"); if (await (0, fs_1.exists)(retinaFile)) { const tiffFile = await tmpDir.getTempFile({ suffix: ".tiff" }); await (0, builder_util_1.exec)("tiffutil", ["-cathidpicheck", file, retinaFile, "-out", tiffFile]); return tiffFile; } return file; } //# sourceMappingURL=dmg.js.map