"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const build_modules_1 = require("@pnpm/build-modules");
const constants_1 = require("@pnpm/constants");
const core_loggers_1 = require("@pnpm/core-loggers");
const error_1 = require("@pnpm/error");
const headless_1 = require("@pnpm/headless");
const lifecycle_1 = require("@pnpm/lifecycle");
const link_bins_1 = require("@pnpm/link-bins");
const lockfile_file_1 = require("@pnpm/lockfile-file");
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const logger_1 = require("@pnpm/logger");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const read_modules_dir_1 = require("@pnpm/read-modules-dir");
const resolve_dependencies_1 = require("@pnpm/resolve-dependencies");
const types_1 = require("@pnpm/types");
const utils_1 = require("@pnpm/utils");
const rimraf = require("@zkochan/rimraf");
const dp = require("dependency-path");
const isInnerLink = require("is-inner-link");
const isSubdir = require("is-subdir");
const p_every_1 = require("p-every");
const pFilter = require("p-filter");
const p_limit_1 = require("p-limit");
const path = require("path");
const R = require("ramda");
const semver = require("semver");
const getContext_1 = require("../getContext");
const getSpecFromPackageJson_1 = require("../getSpecFromPackageJson");
const lock_1 = require("../lock");
const lockfilesEqual_1 = require("../lockfilesEqual");
const parseWantedDependencies_1 = require("../parseWantedDependencies");
const safeIsInnerLink_1 = require("../safeIsInnerLink");
const save_1 = require("../save");
const removeDeps_1 = require("../uninstall/removeDeps");
const getPref_1 = require("../utils/getPref");
const extendInstallOptions_1 = require("./extendInstallOptions");
const getPreferredVersions_1 = require("./getPreferredVersions");
const link_1 = require("./link");
const lockfile_1 = require("./lockfile");
async function install(manifest, opts) {
    const importers = await mutateModules([
        {
            buildIndex: 0,
            manifest,
            mutation: 'install',
            prefix: opts.prefix || process.cwd(),
        },
    ], opts);
    return importers[0].manifest;
}
exports.install = install;
async function mutateModules(importers, maybeOpts) {
    const reporter = maybeOpts && maybeOpts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    const opts = await extendInstallOptions_1.default(maybeOpts);
    if (!opts.include.dependencies && opts.include.optionalDependencies) {
        throw new error_1.default('OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies');
    }
    const ctx = await getContext_1.default(importers, opts);
    for (const { manifest, prefix } of ctx.importers) {
        if (!manifest) {
            throw new Error(`No package.json found in "${prefix}"`);
        }
    }
    let result;
    if (opts.lock) {
        result = await lock_1.default(ctx.lockfileDirectory, _install, {
            locks: opts.locks,
            prefix: ctx.lockfileDirectory,
            stale: opts.lockStaleDuration,
            storeController: opts.storeController,
        });
    }
    else {
        result = await _install();
    }
    if (reporter) {
        logger_1.streamParser.removeListener('data', reporter);
    }
    return result;
    async function _install() {
        const installsOnly = importers.every((importer) => importer.mutation === 'install');
        if (!opts.lockfileOnly &&
            !opts.update &&
            installsOnly &&
            (opts.frozenLockfile ||
                opts.preferFrozenLockfile &&
                    (!opts.pruneLockfileImporters || Object.keys(ctx.wantedLockfile.importers).length === ctx.importers.length) &&
                    ctx.existsWantedLockfile &&
                    ctx.wantedLockfile.lockfileVersion === constants_1.LOCKFILE_VERSION &&
                    await p_every_1.default(ctx.importers, async (importer) => !hasLocalTarballDepsInRoot(ctx.wantedLockfile, importer.id) &&
                        lockfile_utils_1.satisfiesPackageJson(ctx.wantedLockfile, importer.manifest, importer.id) &&
                        linkedPackagesAreUpToDate(importer.manifest, ctx.wantedLockfile.importers[importer.id], importer.prefix, opts.localPackages)))) {
            if (!ctx.existsWantedLockfile) {
                if (ctx.importers.some((importer) => pkgHasDependencies(importer.manifest))) {
                    throw new Error(`Headless installation requires a ${constants_1.WANTED_LOCKFILE} file`);
                }
            }
            else {
                logger_1.default.info({ message: 'Lockfile is up-to-date, resolution step is skipped', prefix: opts.lockfileDirectory });
                await headless_1.default({
                    currentEngine: {
                        nodeVersion: opts.nodeVersion,
                        pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
                    },
                    currentLockfile: ctx.currentLockfile,
                    engineStrict: opts.engineStrict,
                    extraBinPaths: opts.extraBinPaths,
                    force: opts.force,
                    hoistedAliases: ctx.hoistedAliases,
                    hoistPattern: ctx.hoistPattern,
                    ignoreScripts: opts.ignoreScripts,
                    importers: ctx.importers,
                    include: opts.include,
                    independentLeaves: opts.independentLeaves,
                    lockfileDirectory: ctx.lockfileDirectory,
                    ownLifecycleHooksStdio: opts.ownLifecycleHooksStdio,
                    packageManager: opts.packageManager,
                    pendingBuilds: ctx.pendingBuilds,
                    pruneStore: opts.pruneStore,
                    rawNpmConfig: opts.rawNpmConfig,
                    registries: opts.registries,
                    shamefullyHoist: ctx.shamefullyHoist,
                    sideEffectsCacheRead: opts.sideEffectsCacheRead,
                    sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
                    skipped: ctx.skipped,
                    store: opts.store,
                    storeController: opts.storeController,
                    unsafePerm: opts.unsafePerm,
                    userAgent: opts.userAgent,
                    wantedLockfile: ctx.wantedLockfile,
                });
                return importers;
            }
        }
        const importersToInstall = [];
        const importersToBeInstalled = ctx.importers.filter(({ mutation }) => mutation === 'install');
        const scriptsOpts = {
            extraBinPaths: opts.extraBinPaths,
            rawNpmConfig: opts.rawNpmConfig,
            stdio: opts.ownLifecycleHooksStdio,
            unsafePerm: opts.unsafePerm || false,
        };
        if (!opts.ignoreScripts) {
            await lifecycle_1.runLifecycleHooksConcurrently(['preinstall'], importersToBeInstalled, opts.childConcurrency, scriptsOpts);
        }
        // TODO: make it concurrent
        for (const importer of ctx.importers) {
            switch (importer.mutation) {
                case 'uninstallSome':
                    importersToInstall.push(Object.assign(Object.assign({ pruneDirectDependencies: false }, importer), { newPkgRawSpecs: [], removePackages: importer.dependencyNames, updatePackageJson: true, wantedDeps: [] }));
                    break;
                case 'install': {
                    await installCase(importer);
                    break;
                }
                case 'installSome': {
                    await installSome(importer);
                    break;
                }
                case 'unlink': {
                    const packageDirs = await read_modules_dir_1.default(importer.modulesDir);
                    const externalPackages = await pFilter(packageDirs, (packageDir) => isExternalLink(ctx.storePath, importer.modulesDir, packageDir));
                    const allDeps = utils_1.getAllDependenciesFromPackage(importer.manifest);
                    const packagesToInstall = [];
                    for (const pkgName of externalPackages) {
                        await rimraf(path.join(importer.modulesDir, pkgName));
                        if (allDeps[pkgName]) {
                            packagesToInstall.push(pkgName);
                        }
                    }
                    if (!packagesToInstall.length)
                        return importers;
                    // TODO: install only those that were unlinked
                    // but don't update their version specs in package.json
                    await installCase(Object.assign(Object.assign({}, importer), { mutation: 'install' }));
                    break;
                }
                case 'unlinkSome': {
                    const packagesToInstall = [];
                    const allDeps = utils_1.getAllDependenciesFromPackage(importer.manifest);
                    for (const depName of importer.dependencyNames) {
                        try {
                            if (!await isExternalLink(ctx.storePath, importer.modulesDir, depName)) {
                                logger_1.default.warn({
                                    message: `${depName} is not an external link`,
                                    prefix: importer.prefix,
                                });
                                continue;
                            }
                        }
                        catch (err) {
                            if (err['code'] !== 'ENOENT')
                                throw err; // tslint:disable-line:no-string-literal
                        }
                        await rimraf(path.join(importer.modulesDir, depName));
                        if (allDeps[depName]) {
                            packagesToInstall.push(depName);
                        }
                    }
                    if (!packagesToInstall.length)
                        return importers;
                    // TODO: install only those that were unlinked
                    // but don't update their version specs in package.json
                    await installSome(Object.assign(Object.assign({}, importer), { mutation: 'installSome', dependencySelectors: packagesToInstall }), false);
                    break;
                }
            }
        }
        async function installCase(importer) {
            const wantedDeps = utils_1.getWantedDependencies(importer.manifest);
            if (ctx.wantedLockfile && ctx.wantedLockfile.importers) {
                forgetResolutionsOfPrevWantedDeps(ctx.wantedLockfile.importers[importer.id], wantedDeps);
            }
            const scripts = !opts.ignoreScripts && importer.manifest && importer.manifest.scripts || {};
            if (opts.ignoreScripts && importer.manifest && importer.manifest.scripts &&
                (importer.manifest.scripts.preinstall || importer.manifest.scripts.prepublish ||
                    importer.manifest.scripts.install ||
                    importer.manifest.scripts.postinstall ||
                    importer.manifest.scripts.prepare)) {
                ctx.pendingBuilds.push(importer.id);
            }
            if (scripts['prepublish']) { // tslint:disable-line:no-string-literal
                logger_1.default.warn({
                    message: '`prepublish` scripts are deprecated. Use `prepare` for build steps and `prepublishOnly` for upload-only.',
                    prefix: importer.prefix,
                });
            }
            importersToInstall.push(Object.assign(Object.assign({ pruneDirectDependencies: false }, importer), { newPkgRawSpecs: [], updatePackageJson: false, wantedDeps }));
        }
        async function installSome(importer, updatePackageJson = true) {
            const currentPrefs = opts.ignoreCurrentPrefs ? {} : utils_1.getAllDependenciesFromPackage(importer.manifest);
            const optionalDependencies = importer.targetDependenciesField ? {} : importer.manifest.optionalDependencies || {};
            const devDependencies = importer.targetDependenciesField ? {} : importer.manifest.devDependencies || {};
            const wantedDeps = parseWantedDependencies_1.default(importer.dependencySelectors, {
                allowNew: importer.allowNew !== false,
                currentPrefs,
                defaultTag: opts.tag,
                dev: importer.targetDependenciesField === 'devDependencies',
                devDependencies,
                optional: importer.targetDependenciesField === 'optionalDependencies',
                optionalDependencies,
            });
            importersToInstall.push(Object.assign(Object.assign({ pruneDirectDependencies: false }, importer), { newPkgRawSpecs: wantedDeps.map(({ raw }) => raw), updatePackageJson,
                wantedDeps }));
        }
        const equalLockfiles = lockfilesEqual_1.default(ctx.currentLockfile, ctx.wantedLockfile);
        const currentLockfileIsUpToDate = !ctx.existsWantedLockfile || equalLockfiles;
        // Unfortunately, the private lockfile may differ from the public one.
        // A user might run named installations on a project that has a pnpm-lock.yaml file before running a noop install
        const makePartialCurrentLockfile = !installsOnly && (ctx.existsWantedLockfile && !ctx.existsCurrentLockfile ||
            // TODO: this operation is quite expensive. We'll have to find a better solution to do this.
            // maybe in pnpm v2 it won't be needed. See: https://github.com/pnpm/pnpm/issues/841
            !equalLockfiles);
        const result = await installInContext(importersToInstall, ctx, Object.assign(Object.assign({}, opts), { currentLockfileIsUpToDate,
            makePartialCurrentLockfile, update: opts.update || !installsOnly, updateLockfileMinorVersion: true }));
        if (!opts.ignoreScripts) {
            await lifecycle_1.runLifecycleHooksConcurrently(['install', 'postinstall', 'prepublish', 'prepare'], importersToBeInstalled, opts.childConcurrency, scriptsOpts);
        }
        return result;
    }
}
exports.mutateModules = mutateModules;
async function isExternalLink(store, modules, pkgName) {
    const link = await isInnerLink(modules, pkgName);
    // checking whether the link is pointing to the store is needed
    // because packages are linked to store when independent-leaves = true
    return !link.isInner && !isSubdir(store, link.target);
}
function pkgHasDependencies(manifest) {
    return Boolean(R.keys(manifest.dependencies).length ||
        R.keys(manifest.devDependencies).length ||
        R.keys(manifest.optionalDependencies).length);
}
async function partitionLinkedPackages(dependencies, opts) {
    const nonLinkedDependencies = [];
    const linkedAliases = new Set();
    for (const dependency of dependencies) {
        if (!dependency.alias || opts.localPackages && opts.localPackages[dependency.alias]) {
            nonLinkedDependencies.push(dependency);
            continue;
        }
        const isInnerLink = await safeIsInnerLink_1.default(opts.modulesDir, dependency.alias, {
            hideAlienModules: opts.lockfileOnly === false,
            prefix: opts.prefix,
            storePath: opts.storePath,
            virtualStoreDir: opts.virtualStoreDir,
        });
        if (isInnerLink === true) {
            nonLinkedDependencies.push(dependency);
            continue;
        }
        // This info-log might be better to be moved to the reporter
        logger_1.default.info({
            message: `${dependency.alias} is linked to ${opts.modulesDir} from ${isInnerLink}`,
            prefix: opts.prefix,
        });
        linkedAliases.add(dependency.alias);
    }
    return {
        linkedAliases,
        nonLinkedDependencies,
    };
}
// If the specifier is new, the old resolution probably does not satisfy it anymore.
// By removing these resolutions we ensure that they are resolved again using the new specs.
function forgetResolutionsOfPrevWantedDeps(importer, wantedDeps) {
    if (!importer.specifiers)
        return;
    importer.dependencies = importer.dependencies || {};
    importer.devDependencies = importer.devDependencies || {};
    importer.optionalDependencies = importer.optionalDependencies || {};
    for (const { alias, pref } of wantedDeps) {
        if (alias && importer.specifiers[alias] !== pref) {
            if (importer.dependencies[alias] && !importer.dependencies[alias].startsWith('link:')) {
                delete importer.dependencies[alias];
            }
            delete importer.devDependencies[alias];
            delete importer.optionalDependencies[alias];
        }
    }
}
async function linkedPackagesAreUpToDate(manifest, lockfileImporter, prefix, localPackages) {
    const localPackagesByDirectory = localPackages ? getLocalPackagesByDirectory(localPackages) : {};
    for (const depField of types_1.DEPENDENCIES_FIELDS) {
        const importerDeps = lockfileImporter[depField];
        const pkgDeps = manifest[depField];
        if (!importerDeps || !pkgDeps)
            continue;
        const depNames = Object.keys(importerDeps);
        for (const depName of depNames) {
            if (!pkgDeps[depName])
                continue;
            const isLinked = importerDeps[depName].startsWith('link:');
            if (isLinked && (pkgDeps[depName].startsWith('link:') || pkgDeps[depName].startsWith('file:')))
                continue;
            const dir = isLinked
                ? path.join(prefix, importerDeps[depName].substr(5))
                : (localPackages && localPackages[depName] && localPackages[depName] && localPackages[depName][importerDeps[depName]] && localPackages[depName][importerDeps[depName]].directory);
            if (!dir)
                continue;
            const linkedPkg = localPackagesByDirectory[dir] || await utils_1.safeReadPackageFromDir(dir);
            const availableVersion = pkgDeps[depName].startsWith('workspace:') ? pkgDeps[depName].substr(10) : pkgDeps[depName];
            const localPackageSatisfiesRange = linkedPkg && semver.satisfies(linkedPkg.version, availableVersion);
            if (isLinked !== localPackageSatisfiesRange)
                return false;
        }
    }
    return true;
}
function getLocalPackagesByDirectory(localPackages) {
    const localPackagesByDirectory = {};
    Object.keys(localPackages || {}).forEach((pkgName) => {
        Object.keys(localPackages[pkgName] || {}).forEach((pkgVersion) => {
            localPackagesByDirectory[localPackages[pkgName][pkgVersion].directory] = localPackages[pkgName][pkgVersion].package;
        });
    });
    return localPackagesByDirectory;
}
function hasLocalTarballDepsInRoot(lockfile, importerId) {
    const importer = lockfile.importers && lockfile.importers[importerId];
    if (!importer)
        return false;
    return R.any(refIsLocalTarball, R.values(importer.dependencies || {}))
        || R.any(refIsLocalTarball, R.values(importer.devDependencies || {}))
        || R.any(refIsLocalTarball, R.values(importer.optionalDependencies || {}));
}
function refIsLocalTarball(ref) {
    return ref.startsWith('file:') && (ref.endsWith('.tgz') || ref.endsWith('.tar.gz') || ref.endsWith('.tar'));
}
async function addDependenciesToPackage(manifest, dependencySelectors, opts) {
    const importers = await mutateModules([
        {
            allowNew: opts.allowNew,
            dependencySelectors,
            manifest,
            mutation: 'installSome',
            peer: opts.peer,
            pinnedVersion: opts.pinnedVersion,
            prefix: opts.prefix || process.cwd(),
            targetDependenciesField: opts.targetDependenciesField,
        },
    ], Object.assign(Object.assign({}, opts), { lockfileDirectory: opts.lockfileDirectory || opts.prefix }));
    return importers[0].manifest;
}
exports.addDependenciesToPackage = addDependenciesToPackage;
async function installInContext(importers, ctx, opts) {
    if (opts.lockfileOnly && ctx.existsCurrentLockfile) {
        logger_1.default.warn({
            message: '`node_modules` is present. Lockfile only installation will make it out-of-date',
            prefix: ctx.lockfileDirectory,
        });
    }
    ctx.wantedLockfile.importers = ctx.wantedLockfile.importers || {};
    for (const { id } of importers) {
        if (!ctx.wantedLockfile.importers[id]) {
            ctx.wantedLockfile.importers[id] = { specifiers: {} };
        }
    }
    if (opts.pruneLockfileImporters) {
        const importerIds = new Set(importers.map(({ id }) => id));
        for (const wantedImporter of Object.keys(ctx.wantedLockfile.importers)) {
            if (!importerIds.has(wantedImporter)) {
                delete ctx.wantedLockfile.importers[wantedImporter];
            }
        }
    }
    await Promise.all(importers
        .map(async (importer) => {
        if (importer.mutation !== 'uninstallSome')
            return;
        importer.manifest = await removeDeps_1.default(importer.manifest, importer.dependencyNames, {
            prefix: importer.prefix,
            saveType: importer.targetDependenciesField,
        });
    }));
    core_loggers_1.stageLogger.debug({
        prefix: ctx.lockfileDirectory,
        stage: 'resolution_started',
    });
    const defaultUpdateDepth = (() => {
        if (opts.force)
            return Infinity;
        if (opts.update) {
            return opts.depth;
        }
        return -1;
    })();
    const _toResolveImporter = toResolveImporter.bind(null, {
        defaultUpdateDepth,
        localPackages: opts.localPackages,
        lockfileOnly: opts.lockfileOnly,
        preferredVersions: opts.preferredVersions,
        storePath: ctx.storePath,
        virtualStoreDir: ctx.virtualStoreDir,
    });
    const { dependenciesTree, outdatedDependencies, resolvedImporters, resolvedPackagesByPackageId, wantedToBeSkippedPackageIds, } = await resolve_dependencies_1.default(await Promise.all(importers.map((importer) => _toResolveImporter(importer, Boolean(ctx.hoistPattern && importer.id === '.')))), {
        currentLockfile: ctx.currentLockfile,
        dryRun: opts.lockfileOnly,
        engineStrict: opts.engineStrict,
        force: opts.force,
        hooks: opts.hooks,
        localPackages: opts.localPackages,
        lockfileDirectory: opts.lockfileDirectory,
        nodeVersion: opts.nodeVersion,
        pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
        registries: opts.registries,
        resolutionStrategy: opts.resolutionStrategy,
        sideEffectsCache: opts.sideEffectsCacheRead,
        storeController: opts.storeController,
        tag: opts.tag,
        updateLockfile: ctx.wantedLockfile.lockfileVersion !== constants_1.LOCKFILE_VERSION || !opts.currentLockfileIsUpToDate,
        virtualStoreDir: ctx.virtualStoreDir,
        wantedLockfile: ctx.wantedLockfile,
    });
    core_loggers_1.stageLogger.debug({
        prefix: ctx.lockfileDirectory,
        stage: 'resolution_done',
    });
    const importersToLink = await Promise.all(importers.map(async (importer) => {
        const resolvedImporter = resolvedImporters[importer.id];
        let newPkg = importer.manifest;
        if (importer.updatePackageJson && importer.mutation === 'installSome') {
            if (!importer.manifest) {
                throw new Error('Cannot save because no package.json found');
            }
            const specsToUpsert = resolvedImporter.directDependencies
                .filter(({ specRaw }) => importer.newPkgRawSpecs.includes(specRaw))
                .map(({ alias, name, normalizedPref, specRaw, version, resolution }) => {
                let pref;
                if (normalizedPref) {
                    pref = normalizedPref;
                }
                else {
                    pref = getPref_1.default(alias, name, version, {
                        pinnedVersion: importer.pinnedVersion,
                        rawSpec: specRaw,
                    });
                    if (resolution.type === 'directory' && opts.saveWorkspaceProtocol) {
                        pref = `workspace:${pref}`;
                    }
                }
                return {
                    name: alias,
                    peer: importer.peer,
                    pref,
                    saveType: importer.targetDependenciesField,
                };
            });
            for (const pkgToInstall of importer.wantedDeps) {
                if (pkgToInstall.alias && !specsToUpsert.some(({ name }) => name === pkgToInstall.alias)) {
                    specsToUpsert.push({
                        name: pkgToInstall.alias,
                        peer: importer.peer,
                        saveType: importer.targetDependenciesField,
                    });
                }
            }
            newPkg = await save_1.default(importer.prefix, importer.manifest, specsToUpsert, { dryRun: true });
        }
        else {
            core_loggers_1.packageJsonLogger.debug({
                prefix: importer.prefix,
                updated: importer.manifest,
            });
        }
        if (newPkg) {
            const lockfileImporter = ctx.wantedLockfile.importers[importer.id];
            ctx.wantedLockfile.importers[importer.id] = addDirectDependenciesToLockfile(newPkg, lockfileImporter, resolvedImporter.linkedDependencies, resolvedImporter.directDependencies, ctx.registries);
        }
        const topParents = importer.manifest
            ? await getTopParents(R.difference(Object.keys(utils_1.getAllDependenciesFromPackage(importer.manifest)), importer.newPkgRawSpecs && resolvedImporter.directDependencies
                .filter(({ specRaw }) => importer.newPkgRawSpecs.includes(specRaw))
                .map(({ alias }) => alias) || []), importer.modulesDir)
            : [];
        return {
            bin: importer.bin,
            directNodeIdsByAlias: resolvedImporter.directNodeIdsByAlias,
            id: importer.id,
            linkedDependencies: resolvedImporter.linkedDependencies,
            manifest: newPkg || importer.manifest,
            modulesDir: importer.modulesDir,
            prefix: importer.prefix,
            pruneDirectDependencies: importer.pruneDirectDependencies,
            removePackages: importer.removePackages,
            topParents,
        };
    }));
    const result = await link_1.default(importersToLink, dependenciesTree, {
        afterAllResolvedHook: opts.hooks && opts.hooks.afterAllResolved,
        currentLockfile: ctx.currentLockfile,
        dryRun: opts.lockfileOnly,
        force: opts.force,
        hoistedAliases: ctx.hoistedAliases,
        hoistedModulesDir: ctx.hoistedModulesDir,
        hoistPattern: ctx.hoistPattern,
        include: opts.include,
        independentLeaves: opts.independentLeaves,
        lockfileDirectory: opts.lockfileDirectory,
        makePartialCurrentLockfile: opts.makePartialCurrentLockfile,
        outdatedDependencies,
        pruneStore: opts.pruneStore,
        registries: ctx.registries,
        sideEffectsCacheRead: opts.sideEffectsCacheRead,
        skipped: ctx.skipped,
        storeController: opts.storeController,
        strictPeerDependencies: opts.strictPeerDependencies,
        updateLockfileMinorVersion: opts.updateLockfileMinorVersion,
        virtualStoreDir: ctx.virtualStoreDir,
        wantedLockfile: ctx.wantedLockfile,
        wantedToBeSkippedPackageIds,
    });
    ctx.pendingBuilds = ctx.pendingBuilds
        .filter((relDepPath) => !result.removedDepPaths.has(dp.resolve(ctx.registries, relDepPath)));
    if (opts.ignoreScripts) {
        // we can use concat here because we always only append new packages, which are guaranteed to not be there by definition
        ctx.pendingBuilds = ctx.pendingBuilds
            .concat(result.newDepPaths
            .filter((depPath) => result.depGraph[depPath].requiresBuild)
            .map((depPath) => dp.relative(ctx.registries, result.depGraph[depPath].name, depPath)));
    }
    if (!opts.lockfileOnly) {
        // postinstall hooks
        if (!opts.ignoreScripts && result.newDepPaths && result.newDepPaths.length) {
            const depPaths = Object.keys(result.depGraph);
            const rootNodes = depPaths.filter((depPath) => result.depGraph[depPath].depth === 0);
            await build_modules_1.default(result.depGraph, rootNodes, {
                childConcurrency: opts.childConcurrency,
                depsToBuild: new Set(result.newDepPaths),
                extraBinPaths: ctx.extraBinPaths,
                optional: opts.include.optionalDependencies,
                prefix: ctx.lockfileDirectory,
                rawNpmConfig: opts.rawNpmConfig,
                rootNodeModulesDir: ctx.virtualStoreDir,
                sideEffectsCacheWrite: opts.sideEffectsCacheWrite,
                storeController: opts.storeController,
                unsafePerm: opts.unsafePerm,
                userAgent: opts.userAgent,
            });
        }
        if (result.newDepPaths && result.newDepPaths.length) {
            const newPkgs = R.props(result.newDepPaths, result.depGraph);
            await linkAllBins(newPkgs, result.depGraph, {
                optional: opts.include.optionalDependencies,
                warn: (message) => logger_1.default.warn({ message, prefix: opts.lockfileDirectory }),
            });
        }
        if (!opts.lockfileOnly) {
            await Promise.all(importersToLink.map(linkBinsOfImporter));
        }
    }
    // waiting till the skipped packages are downloaded to the store
    await Promise.all(R.props(Array.from(ctx.skipped), resolvedPackagesByPackageId)
        // skipped packages might have not been reanalized on a repeat install
        // so lets just ignore those by excluding nulls
        .filter(Boolean)
        .map(({ fetchingFiles }) => fetchingFiles()));
    // waiting till package requests are finished
    await Promise.all(R.values(resolvedPackagesByPackageId).map(({ finishing }) => finishing()));
    const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile };
    if (opts.lockfileOnly) {
        await lockfile_file_1.writeWantedLockfile(ctx.lockfileDirectory, result.wantedLockfile, lockfileOpts);
    }
    else {
        await Promise.all([
            opts.useLockfile
                ? lockfile_file_1.writeLockfiles(ctx.lockfileDirectory, result.wantedLockfile, result.currentLockfile, lockfileOpts)
                : lockfile_file_1.writeCurrentLockfile(ctx.lockfileDirectory, result.currentLockfile, lockfileOpts),
            (() => {
                if (result.currentLockfile.packages === undefined && result.removedDepPaths.size === 0) {
                    return Promise.resolve();
                }
                return modules_yaml_1.write(ctx.rootModulesDir, Object.assign(Object.assign({}, ctx.modulesFile), { hoistedAliases: result.newHoistedAliases, hoistPattern: ctx.hoistPattern, included: ctx.include, independentLeaves: ctx.independentLeaves, layoutVersion: constants_1.LAYOUT_VERSION, packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`, pendingBuilds: ctx.pendingBuilds, registries: ctx.registries, shamefullyHoist: ctx.shamefullyHoist, skipped: Array.from(ctx.skipped), store: ctx.storePath }));
            })(),
        ]);
    }
    core_loggers_1.summaryLogger.debug({ prefix: opts.lockfileDirectory });
    await opts.storeController.close();
    return importersToLink.map(({ manifest, prefix }) => ({ prefix, manifest }));
}
async function toResolveImporter(opts, importer, hoist) {
    const allDeps = utils_1.getWantedDependencies(importer.manifest);
    const { linkedAliases, nonLinkedDependencies } = await partitionLinkedPackages(allDeps, {
        localPackages: opts.localPackages,
        lockfileOnly: opts.lockfileOnly,
        modulesDir: importer.modulesDir,
        prefix: importer.prefix,
        storePath: opts.storePath,
        virtualStoreDir: opts.virtualStoreDir,
    });
    const depsToUpdate = importer.wantedDeps.map((wantedDep) => (Object.assign(Object.assign({}, wantedDep), { isNew: true })));
    const existingDeps = nonLinkedDependencies
        .filter(({ alias }) => !importer.wantedDeps.some((wantedDep) => wantedDep.alias === alias));
    let wantedDependencies;
    if (!importer.manifest || hoist) {
        wantedDependencies = [
            ...depsToUpdate,
            ...existingDeps,
        ]
            .map((dep) => (Object.assign(Object.assign({}, dep), { updateDepth: hoist ? Infinity : opts.defaultUpdateDepth })));
    }
    else {
        wantedDependencies = [
            ...depsToUpdate.map((dep) => (Object.assign(Object.assign({}, dep), { updateDepth: opts.defaultUpdateDepth }))),
            ...existingDeps.map((dep) => (Object.assign(Object.assign({}, dep), { updateDepth: -1 }))),
        ];
    }
    return Object.assign(Object.assign({}, importer), { preferredVersions: opts.preferredVersions || importer.manifest && getPreferredVersions_1.default(importer.manifest) || {}, wantedDependencies: wantedDependencies
            .filter(({ alias, updateDepth }) => updateDepth >= 0 || !linkedAliases.has(alias)) });
}
const limitLinking = p_limit_1.default(16);
function linkBinsOfImporter({ modulesDir, bin, prefix }) {
    const warn = (message) => logger_1.default.warn({ message, prefix });
    return link_bins_1.default(modulesDir, bin, { allowExoticManifests: true, warn });
}
async function linkAllBins(depNodes, depGraph, opts) {
    return Promise.all(depNodes.map((depNode => limitLinking(async () => build_modules_1.linkBinsOfDependencies(depNode, depGraph, opts)))));
}
function addDirectDependenciesToLockfile(newManifest, lockfileImporter, linkedPackages, directDependencies, registries) {
    const newLockfileImporter = {
        dependencies: {},
        devDependencies: {},
        optionalDependencies: {},
        specifiers: {},
    };
    linkedPackages.forEach((linkedPkg) => {
        newLockfileImporter.specifiers[linkedPkg.alias] = getSpecFromPackageJson_1.default(newManifest, linkedPkg.alias);
    });
    const directDependenciesByAlias = directDependencies.reduce((acc, directDependency) => {
        acc[directDependency.alias] = directDependency;
        return acc;
    }, {});
    const optionalDependencies = R.keys(newManifest.optionalDependencies);
    const dependencies = R.difference(R.keys(newManifest.dependencies), optionalDependencies);
    const devDependencies = R.difference(R.difference(R.keys(newManifest.devDependencies), optionalDependencies), dependencies);
    const allDeps = [
        ...optionalDependencies,
        ...devDependencies,
        ...dependencies,
    ];
    for (const alias of allDeps) {
        if (directDependenciesByAlias[alias]) {
            const dep = directDependenciesByAlias[alias];
            const ref = lockfile_1.absolutePathToRef(dep.id, {
                alias: dep.alias,
                realName: dep.name,
                registries,
                resolution: dep.resolution,
            });
            if (dep.dev) {
                newLockfileImporter.devDependencies[dep.alias] = ref;
            }
            else if (dep.optional) {
                newLockfileImporter.optionalDependencies[dep.alias] = ref;
            }
            else {
                newLockfileImporter.dependencies[dep.alias] = ref;
            }
            newLockfileImporter.specifiers[dep.alias] = getSpecFromPackageJson_1.default(newManifest, dep.alias);
        }
        else if (lockfileImporter.specifiers[alias]) {
            newLockfileImporter.specifiers[alias] = lockfileImporter.specifiers[alias];
            if (lockfileImporter.dependencies && lockfileImporter.dependencies[alias]) {
                newLockfileImporter.dependencies[alias] = lockfileImporter.dependencies[alias];
            }
            else if (lockfileImporter.optionalDependencies && lockfileImporter.optionalDependencies[alias]) {
                newLockfileImporter.optionalDependencies[alias] = lockfileImporter.optionalDependencies[alias];
            }
            else if (lockfileImporter.devDependencies && lockfileImporter.devDependencies[alias]) {
                newLockfileImporter.devDependencies[alias] = lockfileImporter.devDependencies[alias];
            }
        }
    }
    alignDependencyTypes(newManifest, newLockfileImporter);
    return newLockfileImporter;
}
function alignDependencyTypes(manifest, lockfileImporter) {
    const depTypesOfAliases = getAliasToDependencyTypeMap(manifest);
    // Aligning the dependency types in pnpm-lock.yaml
    for (const depType of types_1.DEPENDENCIES_FIELDS) {
        if (!lockfileImporter[depType])
            continue;
        for (const alias of Object.keys(lockfileImporter[depType] || {})) {
            if (depType === depTypesOfAliases[alias] || !depTypesOfAliases[alias])
                continue;
            lockfileImporter[depTypesOfAliases[alias]][alias] = lockfileImporter[depType][alias];
            delete lockfileImporter[depType][alias];
        }
    }
}
function getAliasToDependencyTypeMap(manifest) {
    const depTypesOfAliases = {};
    for (const depType of types_1.DEPENDENCIES_FIELDS) {
        if (!manifest[depType])
            continue;
        for (const alias of Object.keys(manifest[depType] || {})) {
            if (!depTypesOfAliases[alias]) {
                depTypesOfAliases[alias] = depType;
            }
        }
    }
    return depTypesOfAliases;
}
async function getTopParents(pkgNames, modules) {
    const pkgs = await Promise.all(pkgNames.map((pkgName) => path.join(modules, pkgName)).map(utils_1.safeReadPackageFromDir));
    return pkgs
        .filter(Boolean)
        .map(({ name, version }) => ({ name, version }));
}
