"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("@pnpm/constants");
const link_bins_1 = require("@pnpm/link-bins");
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const lockfile_walker_1 = require("@pnpm/lockfile-walker");
const logger_1 = require("@pnpm/logger");
const matcher_1 = require("@pnpm/matcher");
const pkgid_to_filename_1 = require("@pnpm/pkgid-to-filename");
const symlink_dependency_1 = require("@pnpm/symlink-dependency");
const dp = require("dependency-path");
const path = require("path");
const R = require("ramda");
async function hoistByLockfile(opts) {
    var _a, _b;
    if (!opts.lockfile.packages)
        return {};
    const { directDeps, step } = lockfile_walker_1.default(opts.lockfile, Object.keys(opts.lockfile.importers));
    const deps = [
        {
            children: directDeps
                .reduce((acc, { alias, depPath }) => {
                if (!acc[alias]) {
                    acc[alias] = depPath;
                }
                return acc;
            }, {}),
            depPath: '',
            depth: -1,
        },
        ...await getDependencies(step, 0),
    ];
    const getAliasHoistType = createGetAliasHoistType(opts.publicHoistPattern, opts.privateHoistPattern);
    const hoistedDependencies = await hoistGraph(deps, (_b = (_a = opts.lockfile.importers['.']) === null || _a === void 0 ? void 0 : _a.specifiers) !== null && _b !== void 0 ? _b : {}, {
        getAliasHoistType,
    });
    await symlinkHoistedDependencies(hoistedDependencies, {
        lockfile: opts.lockfile,
        lockfileDir: opts.lockfileDir,
        privateHoistedModulesDir: opts.privateHoistedModulesDir,
        publicHoistedModulesDir: opts.publicHoistedModulesDir,
        virtualStoreDir: opts.virtualStoreDir,
    });
    // Here we only link the bins of the privately hoisted modules.
    // The bins of the publicly hoisted modules will be linked together with
    // the bins of the project's direct dependencies.
    // This is possible because the publicly hoisted modules
    // are in the same directory as the regular dependencies.
    await linkAllBins(opts.privateHoistedModulesDir);
    return hoistedDependencies;
}
exports.default = hoistByLockfile;
function createGetAliasHoistType(publicHoistPattern, privateHoistPattern) {
    const publicMatcher = matcher_1.default(publicHoistPattern);
    const privateMatcher = matcher_1.default(privateHoistPattern);
    return (alias) => {
        if (publicMatcher(alias))
            return 'public';
        if (privateMatcher(alias))
            return 'private';
        return false;
    };
}
async function linkAllBins(modulesDir) {
    const bin = path.join(modulesDir, '.bin');
    const warn = (message, code) => {
        if (code === 'BINARIES_CONFLICT')
            return;
        logger_1.default.warn({ message, prefix: path.join(modulesDir, '../..') });
    };
    try {
        await link_bins_1.default(modulesDir, bin, { allowExoticManifests: true, warn });
    }
    catch (err) {
        // Some packages generate their commands with lifecycle hooks.
        // At this stage, such commands are not generated yet.
        // For now, we don't hoist such generated commands.
        // Related issue: https://github.com/pnpm/pnpm/issues/2071
    }
}
async function getDependencies(step, depth) {
    const deps = [];
    const nextSteps = [];
    for (const { pkgSnapshot, depPath, next } of step.dependencies) {
        const allDeps = {
            ...pkgSnapshot.dependencies,
            ...pkgSnapshot.optionalDependencies,
        };
        deps.push({
            children: Object.entries(allDeps).reduce((children, [alias, ref]) => {
                children[alias] = dp.refToRelative(ref, alias);
                return children;
            }, {}),
            depPath,
            depth,
        });
        nextSteps.push(next());
    }
    for (const depPath of step.missing) {
        // It might make sense to fail if the depPath is not in the skipped list from .modules.yaml
        // However, the skipped list currently contains package IDs, not dep paths.
        logger_1.default.debug({ message: `No entry for "${depPath}" in ${constants_1.WANTED_LOCKFILE}` });
    }
    return (await Promise.all(nextSteps.map((nextStep) => getDependencies(nextStep, depth + 1)))).reduce((acc, deps) => [...acc, ...deps], deps);
}
async function hoistGraph(depNodes, currentSpecifiers, opts) {
    const hoistedAliases = new Set(R.keys(currentSpecifiers));
    const hoistedDependencies = {};
    depNodes
        // sort by depth and then alphabetically
        .sort((a, b) => {
        const depthDiff = a.depth - b.depth;
        return depthDiff === 0 ? a.depPath.localeCompare(b.depPath) : depthDiff;
    })
        // build the alias map and the id map
        .forEach((depNode) => {
        for (const [childAlias, childPath] of Object.entries(depNode.children)) {
            const hoist = opts.getAliasHoistType(childAlias);
            if (!hoist)
                continue;
            // if this alias has already been taken, skip it
            if (hoistedAliases.has(childAlias)) {
                continue;
            }
            hoistedAliases.add(childAlias);
            if (!hoistedDependencies[childPath]) {
                hoistedDependencies[childPath] = {};
            }
            hoistedDependencies[childPath][childAlias] = hoist;
        }
    });
    return hoistedDependencies;
}
async function symlinkHoistedDependencies(hoistedDependencies, opts) {
    await Promise.all(Object.entries(hoistedDependencies)
        .map(async ([depPath, pkgAliases]) => {
        const pkgSnapshot = opts.lockfile.packages[depPath];
        if (!pkgSnapshot) {
            logger_1.globalWarn(`Failed to find "${depPath}" in lockfile during hoisting. `
                + `Next aliases will not be hoisted: ${Object.keys(pkgAliases).join(', ')}`);
            return;
        }
        const pkgName = lockfile_utils_1.nameVerFromPkgSnapshot(depPath, pkgSnapshot).name;
        const modules = path.join(opts.virtualStoreDir, pkgid_to_filename_1.default(depPath, opts.lockfileDir), 'node_modules');
        const depLocation = path.join(modules, pkgName);
        await Promise.all(Object.entries(pkgAliases).map(async ([pkgAlias, hoistType]) => {
            const targetDir = hoistType === 'public'
                ? opts.publicHoistedModulesDir : opts.privateHoistedModulesDir;
            await symlink_dependency_1.default(depLocation, targetDir, pkgAlias);
        }));
    }));
}
//# sourceMappingURL=index.js.map