"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.listrPackage = void 0;
const path_1 = __importDefault(require("path"));
const util_1 = require("util");
const core_utils_1 = require("@electron-forge/core-utils");
const tracer_1 = require("@electron-forge/tracer");
const get_1 = require("@electron/get");
const packager_1 = require("@electron/packager");
const chalk_1 = __importDefault(require("chalk"));
const debug_1 = __importDefault(require("debug"));
const fast_glob_1 = __importDefault(require("fast-glob"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const listr2_1 = require("listr2");
const forge_config_1 = __importDefault(require("../util/forge-config"));
const hook_1 = require("../util/hook");
const messages_1 = require("../util/messages");
const out_dir_1 = __importDefault(require("../util/out-dir"));
const read_package_json_1 = require("../util/read-package-json");
const require_search_1 = __importDefault(require("../util/require-search"));
const resolve_dir_1 = __importDefault(require("../util/resolve-dir"));
const d = (0, debug_1.default)('electron-forge:packager');
/**
 * Resolves hooks if they are a path to a file (instead of a `Function`).
 */
function resolveHooks(hooks, dir) {
    if (hooks) {
        return hooks.map((hook) => (typeof hook === 'string' ? (0, require_search_1.default)(dir, [hook]) : hook));
    }
    return [];
}
/**
 * Runs given hooks sequentially by mapping them to promises and iterating
 * through while awaiting
 */
function sequentialHooks(hooks) {
    return [
        async (buildPath, electronVersion, platform, arch, done) => {
            for (const hook of hooks) {
                try {
                    await (0, util_1.promisify)(hook)(buildPath, electronVersion, platform, arch);
                }
                catch (err) {
                    d('hook failed:', hook.toString(), err);
                    return done(err);
                }
            }
            done();
        },
    ];
}
function sequentialFinalizePackageTargetsHooks(hooks) {
    return [
        async (targets, done) => {
            for (const hook of hooks) {
                try {
                    await (0, util_1.promisify)(hook)(targets);
                }
                catch (err) {
                    return done(err);
                }
            }
            done();
        },
    ];
}
const listrPackage = (childTrace, { dir: providedDir = process.cwd(), interactive = false, arch = (0, get_1.getHostArch)(), platform = process.platform, outDir, }) => {
    const runner = new listr2_1.Listr([
        {
            title: 'Preparing to package application',
            task: childTrace({ name: 'package-prepare', category: '@electron-forge/core' }, async (_, ctx) => {
                const resolvedDir = await (0, resolve_dir_1.default)(providedDir);
                if (!resolvedDir) {
                    throw new Error('Failed to locate compilable Electron application');
                }
                ctx.dir = resolvedDir;
                ctx.forgeConfig = await (0, forge_config_1.default)(resolvedDir);
                ctx.packageJSON = await (0, read_package_json_1.readMutatedPackageJson)(resolvedDir, ctx.forgeConfig);
                if (!ctx.packageJSON.main) {
                    throw new Error('packageJSON.main must be set to a valid entry point for your Electron app');
                }
                ctx.calculatedOutDir = outDir || (0, out_dir_1.default)(resolvedDir, ctx.forgeConfig);
            }),
        },
        {
            title: 'Running packaging hooks',
            task: childTrace({ name: 'run-packaging-hooks', category: '@electron-forge/core' }, async (childTrace, { forgeConfig }, task) => {
                return (0, tracer_1.delayTraceTillSignal)(childTrace, task.newListr([
                    {
                        title: `Running ${chalk_1.default.yellow('generateAssets')} hook`,
                        task: childTrace({ name: 'run-generateAssets-hook', category: '@electron-forge/core' }, async (childTrace, _, task) => {
                            return (0, tracer_1.delayTraceTillSignal)(childTrace, task.newListr(await (0, hook_1.getHookListrTasks)(childTrace, forgeConfig, 'generateAssets', platform, arch)), 'run');
                        }),
                    },
                    {
                        title: `Running ${chalk_1.default.yellow('prePackage')} hook`,
                        task: childTrace({ name: 'run-prePackage-hook', category: '@electron-forge/core' }, async (childTrace, _, task) => {
                            return (0, tracer_1.delayTraceTillSignal)(childTrace, task.newListr(await (0, hook_1.getHookListrTasks)(childTrace, forgeConfig, 'prePackage', platform, arch)), 'run');
                        }),
                    },
                ]), 'run');
            }),
        },
        {
            title: 'Packaging application',
            task: childTrace({ name: 'packaging-application', category: '@electron-forge/core' }, async (childTrace, ctx, task) => {
                const { calculatedOutDir, forgeConfig, packageJSON } = ctx;
                const getTargetKey = (target) => `${target.platform}/${target.arch}`;
                task.output = 'Determining targets...';
                const signalCopyDone = new Map();
                const signalRebuildDone = new Map();
                const signalPackageDone = new Map();
                const rejects = [];
                const signalDone = (map, target) => {
                    map.get(getTargetKey(target))?.pop()?.();
                };
                const addSignalAndWait = async (map, target) => {
                    const targetKey = getTargetKey(target);
                    await new Promise((resolve, reject) => {
                        rejects.push(reject);
                        map.set(targetKey, (map.get(targetKey) || []).concat([resolve]));
                    });
                };
                let provideTargets;
                const targetsPromise = new Promise((resolve, reject) => {
                    provideTargets = resolve;
                    rejects.push(reject);
                });
                const rebuildTasks = new Map();
                const signalRebuildStart = new Map();
                const afterFinalizePackageTargetsHooks = [
                    (targets, done) => {
                        provideTargets(targets);
                        done();
                    },
                    ...resolveHooks(forgeConfig.packagerConfig.afterFinalizePackageTargets, ctx.dir),
                ];
                const pruneEnabled = !('prune' in forgeConfig.packagerConfig) || forgeConfig.packagerConfig.prune;
                const afterCopyHooks = [
                    async (buildPath, electronVersion, platform, arch, done) => {
                        signalDone(signalCopyDone, { platform, arch });
                        done();
                    },
                    async (buildPath, electronVersion, pPlatform, pArch, done) => {
                        const bins = await (0, fast_glob_1.default)(path_1.default.join(buildPath, '**/.bin/**/*'));
                        for (const bin of bins) {
                            await fs_extra_1.default.remove(bin);
                        }
                        done();
                    },
                    async (buildPath, electronVersion, pPlatform, pArch, done) => {
                        await (0, hook_1.runHook)(forgeConfig, 'packageAfterCopy', buildPath, electronVersion, pPlatform, pArch);
                        done();
                    },
                    async (buildPath, electronVersion, pPlatform, pArch, done) => {
                        const targetKey = getTargetKey({ platform: pPlatform, arch: pArch });
                        await (0, core_utils_1.listrCompatibleRebuildHook)(buildPath, electronVersion, pPlatform, pArch, forgeConfig.rebuildConfig, 
                        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                        await rebuildTasks.get(targetKey).pop());
                        signalRebuildDone.get(targetKey)?.pop()?.();
                        done();
                    },
                    async (buildPath, electronVersion, pPlatform, pArch, done) => {
                        const copiedPackageJSON = await (0, read_package_json_1.readMutatedPackageJson)(buildPath, forgeConfig);
                        if (copiedPackageJSON.config && copiedPackageJSON.config.forge) {
                            delete copiedPackageJSON.config.forge;
                        }
                        await fs_extra_1.default.writeJson(path_1.default.resolve(buildPath, 'package.json'), copiedPackageJSON, { spaces: 2 });
                        done();
                    },
                    ...resolveHooks(forgeConfig.packagerConfig.afterCopy, ctx.dir),
                ];
                const afterCompleteHooks = [
                    async (buildPath, electronVersion, pPlatform, pArch, done) => {
                        signalPackageDone.get(getTargetKey({ platform: pPlatform, arch: pArch }))?.pop()?.();
                        done();
                    },
                    ...resolveHooks(forgeConfig.packagerConfig.afterComplete, ctx.dir),
                ];
                const afterPruneHooks = [];
                if (pruneEnabled) {
                    afterPruneHooks.push(...resolveHooks(forgeConfig.packagerConfig.afterPrune, ctx.dir));
                }
                afterPruneHooks.push((async (buildPath, electronVersion, pPlatform, pArch, done) => {
                    await (0, hook_1.runHook)(forgeConfig, 'packageAfterPrune', buildPath, electronVersion, pPlatform, pArch);
                    done();
                }));
                const afterExtractHooks = [
                    (async (buildPath, electronVersion, pPlatform, pArch, done) => {
                        await (0, hook_1.runHook)(forgeConfig, 'packageAfterExtract', buildPath, electronVersion, pPlatform, pArch);
                        done();
                    }),
                ];
                afterExtractHooks.push(...resolveHooks(forgeConfig.packagerConfig.afterExtract, ctx.dir));
                const packageOpts = {
                    asar: false,
                    overwrite: true,
                    ignore: [/^\/out\//g],
                    ...forgeConfig.packagerConfig,
                    quiet: true,
                    dir: ctx.dir,
                    arch: arch,
                    platform,
                    afterFinalizePackageTargets: sequentialFinalizePackageTargetsHooks(afterFinalizePackageTargetsHooks),
                    afterComplete: sequentialHooks(afterCompleteHooks),
                    afterCopy: sequentialHooks(afterCopyHooks),
                    afterExtract: sequentialHooks(afterExtractHooks),
                    afterPrune: sequentialHooks(afterPruneHooks),
                    out: calculatedOutDir,
                    electronVersion: await (0, core_utils_1.getElectronVersion)(ctx.dir, packageJSON),
                };
                packageOpts.quiet = true;
                if (packageOpts.all) {
                    throw new Error('config.forge.packagerConfig.all is not supported by Electron Forge');
                }
                if (!packageJSON.version && !packageOpts.appVersion) {
                    (0, messages_1.warn)(interactive, chalk_1.default.yellow('Please set "version" or "config.forge.packagerConfig.appVersion" in your application\'s package.json so auto-updates work properly'));
                }
                if (packageOpts.prebuiltAsar) {
                    throw new Error('config.forge.packagerConfig.prebuiltAsar is not supported by Electron Forge');
                }
                d('packaging with options', packageOpts);
                ctx.packagerPromise = (0, packager_1.packager)(packageOpts);
                // Handle error by failing this task
                // rejects is populated by the reject handlers for every
                // signal based promise in every subtask
                ctx.packagerPromise.catch((err) => {
                    for (const reject of rejects) {
                        reject(err);
                    }
                });
                const targets = await targetsPromise;
                // Copy the resolved targets into the context for later
                ctx.targets = [...targets];
                // If we are targetting a universal build we need to add the "fake"
                // x64 and arm64 builds into the list of targets so that we can
                // show progress for those
                for (const target of targets) {
                    if (target.arch === 'universal') {
                        targets.push({
                            platform: target.platform,
                            arch: 'x64',
                            forUniversal: true,
                        }, {
                            platform: target.platform,
                            arch: 'arm64',
                            forUniversal: true,
                        });
                    }
                }
                // Populate rebuildTasks with promises that resolve with the rebuild tasks
                // that will eventually run
                for (const target of targets) {
                    // Skip universal tasks as they do not have rebuild sub-tasks
                    if (target.arch === 'universal')
                        continue;
                    const targetKey = getTargetKey(target);
                    rebuildTasks.set(targetKey, (rebuildTasks.get(targetKey) || []).concat([
                        new Promise((resolve) => {
                            signalRebuildStart.set(targetKey, (signalRebuildStart.get(targetKey) || []).concat([resolve]));
                        }),
                    ]));
                }
                d('targets:', targets);
                return (0, tracer_1.delayTraceTillSignal)(childTrace, task.newListr(targets.map((target) => target.arch === 'universal'
                    ? {
                        title: `Stitching ${chalk_1.default.cyan(`${target.platform}/x64`)} and ${chalk_1.default.cyan(`${target.platform}/arm64`)} into a ${chalk_1.default.green(`${target.platform}/universal`)} package`,
                        task: async () => {
                            await addSignalAndWait(signalPackageDone, target);
                        },
                        rendererOptions: {
                            timer: { ...listr2_1.PRESET_TIMER },
                        },
                    }
                    : {
                        title: `Packaging for ${chalk_1.default.cyan(target.arch)} on ${chalk_1.default.cyan(target.platform)}${target.forUniversal ? chalk_1.default.italic(' (for universal package)') : ''}`,
                        task: childTrace({
                            name: `package-app-${target.platform}-${target.arch}${target.forUniversal ? '-universal-tmp' : ''}`,
                            category: '@electron-forge/core',
                            extraDetails: { arch: target.arch, platform: target.platform },
                            newRoot: true,
                        }, async (childTrace, _, task) => {
                            return (0, tracer_1.delayTraceTillSignal)(childTrace, task.newListr([
                                {
                                    title: 'Copying files',
                                    task: childTrace({ name: 'copy-files', category: '@electron-forge/core' }, async () => {
                                        await addSignalAndWait(signalCopyDone, target);
                                    }),
                                },
                                {
                                    title: 'Preparing native dependencies',
                                    task: childTrace({ name: 'prepare-native-dependencies', category: '@electron-forge/core' }, async (_, __, task) => {
                                        signalRebuildStart.get(getTargetKey(target))?.pop()?.(task);
                                        await addSignalAndWait(signalRebuildDone, target);
                                    }),
                                    rendererOptions: {
                                        persistentOutput: true,
                                        bottomBar: Infinity,
                                        timer: { ...listr2_1.PRESET_TIMER },
                                    },
                                },
                                {
                                    title: 'Finalizing package',
                                    task: childTrace({ name: 'finalize-package', category: '@electron-forge/core' }, async () => {
                                        await addSignalAndWait(signalPackageDone, target);
                                    }),
                                },
                            ], { rendererOptions: { collapseSubtasks: true, collapseErrors: false } }), 'run');
                        }),
                        rendererOptions: {
                            timer: { ...listr2_1.PRESET_TIMER },
                        },
                    }), { concurrent: true, rendererOptions: { collapseSubtasks: false, collapseErrors: false } }), 'run');
            }),
        },
        {
            title: `Running ${chalk_1.default.yellow('postPackage')} hook`,
            task: childTrace({ name: 'run-postPackage-hook', category: '@electron-forge/core' }, async (childTrace, { packagerPromise, forgeConfig }, task) => {
                const outputPaths = await packagerPromise;
                d('outputPaths:', outputPaths);
                return (0, tracer_1.delayTraceTillSignal)(childTrace, task.newListr(await (0, hook_1.getHookListrTasks)(childTrace, forgeConfig, 'postPackage', {
                    arch,
                    outputPaths,
                    platform,
                })), 'run');
            }),
        },
    ], {
        concurrent: false,
        silentRendererCondition: !interactive,
        fallbackRendererCondition: Boolean(process.env.DEBUG) || Boolean(process.env.CI),
        rendererOptions: {
            collapseSubtasks: false,
            collapseErrors: false,
        },
        ctx: {},
    });
    return runner;
};
exports.listrPackage = listrPackage;
exports.default = (0, tracer_1.autoTrace)({ name: 'package()', category: '@electron-forge/core' }, async (childTrace, opts) => {
    const runner = (0, exports.listrPackage)(childTrace, opts);
    await runner.run();
    const outputPaths = await runner.ctx.packagerPromise;
    return runner.ctx.targets.map((target, index) => ({
        platform: target.platform,
        arch: target.arch,
        packagedPath: outputPaths[index],
    }));
});
//# sourceMappingURL=data:application/json;base64, 