"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 sanitizeFileName_1 = require("app-builder-lib/out/util/sanitizeFileName");
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 = sanitizeFileName_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 builder_util_1.exec("hdiutil", ["resize", "-size", expandingFinalSize.toString(), tempDmg]);
        const volumePath = path.join("/Volumes", volumeName);
        if (await fs_1.exists(volumePath)) {
            builder_util_1.log.debug({ volumePath }, "unmounting previous disk image");
            await dmgUtil_1.detach(volumePath);
        }
        if (!(await 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 builder_util_1.spawn("hdiutil", addLogLevel(args));
        if (this.options.internetEnabled && parseInt(os_1.release().split(".")[0], 10) < 19) {
            await builder_util_1.exec("hdiutil", addLogLevel(["internet-enable"]).concat(artifactPath));
        }
        const licenseData = await 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 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 (!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 macCodeSign_1.findIdentity(certificateType, qualifier, keychainFile);
        if (identity == null) {
            identity = await 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 builder_util_1.exec("codesign", args);
    }
    computeVolumeName(arch, custom) {
        const appInfo = this.packager.appInfo;
        const shortVersion = this.packager.platformSpecificBuildOptions.bundleShortVersion || appInfo.version;
        const archString = 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 && 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 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 builder_util_1.retry(() => 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(fs_extra_1.stat(dmgFile));
    if (specification.icon != null) {
        asyncTaskManager.addTask(fs_1.statOrNull(specification.icon));
    }
    if (backgroundFile != null) {
        asyncTaskManager.addTask(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 env = {
        ...process.env,
        volumePath,
        appFileName: `${packager.appInfo.productFilename}.app`,
        iconSize: specification.iconSize || 80,
        iconTextSize: specification.iconTextSize || 12,
        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 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();
    await builder_util_1.exec(process.env.PYTHON_PATH || "/usr/bin/python", [path.join(dmgUtil_1.getDmgVendorPath(), "dmgbuild/core.py")], {
        cwd: dmgUtil_1.getDmgVendorPath(),
        env,
    });
    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);
        if (result.length !== 0) {
            result += ",\n";
        }
        result += `'${entryName}': (${c.x}, ${c.y})`;
        if (c.type === "link") {
            asyncTaskManager.addTask(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 (!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 fs_extra_1.stat(source)).isDirectory() ? fs_1.copyDir(source, destination) : 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 fs_1.exists(retinaFile)) {
        const tiffFile = await tmpDir.getTempFile({ suffix: ".tiff" });
        await builder_util_1.exec("tiffutil", ["-cathidpicheck", file, retinaFile, "-out", tiffFile]);
        return tiffFile;
    }
    return file;
}
//# sourceMappingURL=dmg.js.map