"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_loggers_1 = require("@pnpm/core-loggers");
const headless_1 = require("@pnpm/headless");
const lifecycle_1 = require("@pnpm/lifecycle");
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 dp = require("dependency-path");
const graphSequencer = require("graph-sequencer");
const isInnerLink = require("is-inner-link");
const isSubdir = require("is-subdir");
const pEvery = require("p-every");
const pFilter = require("p-filter");
const pLimit = require("p-limit");
const path = require("path");
const pnpm_shrinkwrap_1 = require("pnpm-shrinkwrap");
const R = require("ramda");
const rimraf = require("rimraf-then");
const semver = require("semver");
const constants_1 = require("../constants");
const errorTypes_1 = require("../errorTypes");
const getContext_1 = require("../getContext");
const getSpecFromPackageJson_1 = require("../getSpecFromPackageJson");
const lock_1 = require("../lock");
const parseWantedDependencies_1 = require("../parseWantedDependencies");
const safeIsInnerLink_1 = require("../safeIsInnerLink");
const save_1 = require("../save");
const shrinkwrapsEqual_1 = require("../shrinkwrapsEqual");
const removeDeps_1 = require("../uninstall/removeDeps");
const getPref_1 = require("../utils/getPref");
const extendInstallOptions_1 = require("./extendInstallOptions");
const link_1 = require("./link");
const shrinkwrap_1 = require("./shrinkwrap");
const ENGINE_NAME = `${process.platform}-${process.arch}-node-${process.version.split('.')[0]}`;
function install(opts) {
    return mutateModules([{ prefix: opts.prefix || process.cwd(), mutation: 'install' }], opts);
}
exports.install = install;
function mutateModules(importers, maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const reporter = maybeOpts && maybeOpts.reporter;
        if (reporter) {
            logger_1.streamParser.on('data', reporter);
        }
        const opts = yield extendInstallOptions_1.default(maybeOpts);
        if (!opts.include.dependencies && opts.include.optionalDependencies) {
            throw new errorTypes_1.PnpmError('ERR_PNPM_OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies');
        }
        const ctx = yield getContext_1.default(importers, opts);
        for (const importer of ctx.importers) {
            if (!importer.pkg) {
                throw new Error(`No package.json found in "${importer.prefix}"`);
            }
        }
        if (opts.lock) {
            yield lock_1.default(ctx.shrinkwrapDirectory, _install, {
                locks: opts.locks,
                prefix: ctx.shrinkwrapDirectory,
                stale: opts.lockStaleDuration,
                storeController: opts.storeController,
            });
        }
        else {
            yield _install();
        }
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
        function _install() {
            return __awaiter(this, void 0, void 0, function* () {
                const installsOnly = importers.every((importer) => importer.mutation === 'install');
                if (!opts.shrinkwrapOnly &&
                    !opts.update &&
                    installsOnly &&
                    (opts.frozenShrinkwrap ||
                        opts.preferFrozenShrinkwrap &&
                            (!opts.pruneShrinkwrapImporters || Object.keys(ctx.wantedShrinkwrap.importers).length === ctx.importers.length) &&
                            ctx.existsWantedShrinkwrap &&
                            (ctx.wantedShrinkwrap.shrinkwrapVersion === constants_1.SHRINKWRAP_VERSION ||
                                ctx.wantedShrinkwrap.shrinkwrapVersion === constants_1.SHRINKWRAP_NEXT_VERSION) &&
                            (yield pEvery(ctx.importers, (importer) => __awaiter(this, void 0, void 0, function* () {
                                return !hasLocalTarballDepsInRoot(ctx.wantedShrinkwrap, importer.id) &&
                                    pnpm_shrinkwrap_1.satisfiesPackageJson(ctx.wantedShrinkwrap, importer.pkg, importer.id) &&
                                    linkedPackagesAreUpToDate(importer.pkg, ctx.wantedShrinkwrap.importers[importer.id], importer.prefix, opts.localPackages);
                            }))))) {
                    if (!ctx.existsWantedShrinkwrap) {
                        if (ctx.importers.some((importer) => pkgHasDependencies(importer.pkg))) {
                            throw new Error('Headless installation requires a shrinkwrap.yaml file');
                        }
                    }
                    else {
                        logger_1.default.info({ message: 'Performing headless installation', prefix: opts.shrinkwrapDirectory });
                        yield headless_1.default({
                            currentShrinkwrap: ctx.currentShrinkwrap,
                            force: opts.force,
                            ignoreScripts: opts.ignoreScripts,
                            importers: ctx.importers,
                            include: opts.include,
                            independentLeaves: opts.independentLeaves,
                            packageManager: opts.packageManager,
                            pendingBuilds: ctx.pendingBuilds,
                            pruneStore: opts.pruneStore,
                            rawNpmConfig: opts.rawNpmConfig,
                            registries: opts.registries,
                            shrinkwrapDirectory: ctx.shrinkwrapDirectory,
                            sideEffectsCache: opts.sideEffectsCache,
                            sideEffectsCacheReadonly: opts.sideEffectsCacheReadonly,
                            store: opts.store,
                            storeController: opts.storeController,
                            unsafePerm: opts.unsafePerm,
                            userAgent: opts.userAgent,
                            verifyStoreIntegrity: opts.verifyStoreIntegrity,
                            wantedShrinkwrap: ctx.wantedShrinkwrap,
                        });
                        return;
                    }
                }
                const importersToInstall = [];
                // TODO: make it concurrent
                for (const importer of ctx.importers) {
                    switch (importer.mutation) {
                        case 'uninstallSome':
                            importersToInstall.push(Object.assign({ pruneDirectDependencies: false }, importer, { linkedPackages: [], newPkgRawSpecs: [], nonLinkedPackages: [], removePackages: importer.dependencyNames, updatePackageJson: true, usesExternalShrinkwrap: ctx.shrinkwrapDirectory !== importer.prefix, wantedDeps: [] }));
                            break;
                        case 'install': {
                            yield installCase(importer);
                            break;
                        }
                        case 'installSome': {
                            const currentPrefs = opts.ignoreCurrentPrefs ? {} : utils_1.getAllDependenciesFromPackage(importer.pkg);
                            const optionalDependencies = importer.targetDependenciesField ? {} : importer.pkg.optionalDependencies || {};
                            const devDependencies = importer.targetDependenciesField ? {} : importer.pkg.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({ pruneDirectDependencies: false }, importer, { linkedPackages: [], newPkgRawSpecs: wantedDeps.map((wantedDependency) => wantedDependency.raw), nonLinkedPackages: wantedDeps, updatePackageJson: true, usesExternalShrinkwrap: ctx.shrinkwrapDirectory !== importer.prefix, wantedDeps }));
                            break;
                        }
                        case 'unlink': {
                            const packageDirs = yield read_modules_dir_1.default(importer.modulesDir);
                            const externalPackages = yield pFilter(packageDirs, (packageDir) => isExternalLink(ctx.storePath, importer.modulesDir, packageDir));
                            const allDeps = utils_1.getAllDependenciesFromPackage(importer.pkg);
                            const packagesToInstall = [];
                            for (const pkgName of externalPackages) {
                                yield rimraf(path.join(importer.modulesDir, pkgName));
                                if (allDeps[pkgName]) {
                                    packagesToInstall.push(pkgName);
                                }
                            }
                            if (!packagesToInstall.length)
                                return;
                            // TODO: install only those that were unlinked
                            // but don't update their version specs in package.json
                            yield installCase(Object.assign({}, importer, { mutation: 'install' }));
                            break;
                        }
                        case 'unlinkSome': {
                            const packagesToInstall = [];
                            const allDeps = utils_1.getAllDependenciesFromPackage(importer.pkg);
                            for (const depName of importer.dependencyNames) {
                                try {
                                    if (!(yield 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
                                }
                                yield rimraf(path.join(importer.modulesDir, depName));
                                if (allDeps[depName]) {
                                    packagesToInstall.push(depName);
                                }
                            }
                            if (!packagesToInstall.length)
                                return;
                            // TODO: install only those that were unlinked
                            // but don't update their version specs in package.json
                            yield installCase(Object.assign({}, importer, { mutation: 'install' }));
                            break;
                        }
                    }
                }
                function installCase(importer) {
                    return __awaiter(this, void 0, void 0, function* () {
                        const wantedDeps = utils_1.getWantedDependencies(importer.pkg);
                        if (ctx.wantedShrinkwrap && ctx.wantedShrinkwrap.importers) {
                            forgetResolutionsOfPrevWantedDeps(ctx.wantedShrinkwrap.importers[importer.id], wantedDeps);
                        }
                        const scripts = !opts.ignoreScripts && importer.pkg && importer.pkg.scripts || {};
                        if (opts.ignoreScripts && importer.pkg && importer.pkg.scripts &&
                            (importer.pkg.scripts.preinstall || importer.pkg.scripts.prepublish ||
                                importer.pkg.scripts.install ||
                                importer.pkg.scripts.postinstall ||
                                importer.pkg.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,
                            });
                        }
                        const scriptsOpts = {
                            depPath: importer.prefix,
                            pkgRoot: importer.prefix,
                            rawNpmConfig: opts.rawNpmConfig,
                            rootNodeModulesDir: importer.modulesDir,
                            stdio: opts.ownLifecycleHooksStdio,
                            unsafePerm: opts.unsafePerm || false,
                        };
                        if (scripts.preinstall) {
                            yield lifecycle_1.default('preinstall', importer.pkg, scriptsOpts);
                        }
                        importersToInstall.push(Object.assign({ pruneDirectDependencies: false }, importer, yield partitionLinkedPackages(wantedDeps, {
                            localPackages: opts.localPackages,
                            modulesDir: importer.modulesDir,
                            prefix: importer.prefix,
                            shrinkwrapOnly: opts.shrinkwrapOnly,
                            storePath: ctx.storePath,
                            virtualStoreDir: ctx.virtualStoreDir,
                        }), { newPkgRawSpecs: [], updatePackageJson: false, usesExternalShrinkwrap: ctx.shrinkwrapDirectory !== importer.prefix, wantedDeps }));
                    });
                }
                // Unfortunately, the private shrinkwrap file may differ from the public one.
                // A user might run named installations on a project that has a shrinkwrap.yaml file before running a noop install
                const makePartialCurrentShrinkwrap = !installsOnly && (ctx.existsWantedShrinkwrap && !ctx.existsCurrentShrinkwrap ||
                    // 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
                    !shrinkwrapsEqual_1.default(ctx.currentShrinkwrap, ctx.wantedShrinkwrap));
                yield installInContext(importersToInstall, ctx, Object.assign({}, opts, { makePartialCurrentShrinkwrap, update: opts.update || !installsOnly, updateShrinkwrapMinorVersion: true }));
                for (const importer of ctx.importers) {
                    if (importer.mutation !== 'install')
                        continue;
                    const scripts = !opts.ignoreScripts && importer.pkg && importer.pkg.scripts || {};
                    const scriptsOpts = {
                        depPath: importer.prefix,
                        pkgRoot: importer.prefix,
                        rawNpmConfig: opts.rawNpmConfig,
                        rootNodeModulesDir: importer.modulesDir,
                        stdio: opts.ownLifecycleHooksStdio,
                        unsafePerm: opts.unsafePerm || false,
                    };
                    if (scripts.install) {
                        yield lifecycle_1.default('install', importer.pkg, scriptsOpts);
                    }
                    if (scripts.postinstall) {
                        yield lifecycle_1.default('postinstall', importer.pkg, scriptsOpts);
                    }
                    if (scripts.prepublish) {
                        yield lifecycle_1.default('prepublish', importer.pkg, scriptsOpts);
                    }
                    if (scripts.prepare) {
                        yield lifecycle_1.default('prepare', importer.pkg, scriptsOpts);
                    }
                }
            });
        }
    });
}
exports.mutateModules = mutateModules;
function isExternalLink(store, modules, pkgName) {
    return __awaiter(this, void 0, void 0, function* () {
        const link = yield 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(pkg) {
    return Boolean(R.keys(pkg.dependencies).length ||
        R.keys(pkg.devDependencies).length ||
        R.keys(pkg.optionalDependencies).length);
}
function partitionLinkedPackages(wantedDeps, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const nonLinkedPackages = [];
        const linkedPackages = [];
        for (const wantedDependency of wantedDeps) {
            if (!wantedDependency.alias || opts.localPackages && opts.localPackages[wantedDependency.alias]) {
                nonLinkedPackages.push(wantedDependency);
                continue;
            }
            const isInnerLink = yield safeIsInnerLink_1.default(opts.virtualStoreDir, wantedDependency.alias, {
                hideAlienModules: opts.shrinkwrapOnly === false,
                prefix: opts.prefix,
                storePath: opts.storePath,
            });
            if (isInnerLink === true) {
                nonLinkedPackages.push(wantedDependency);
                continue;
            }
            // This info-log might be better to be moved to the reporter
            logger_1.default.info({
                message: `${wantedDependency.alias} is linked to ${opts.modulesDir} from ${isInnerLink}`,
                prefix: opts.prefix,
            });
            linkedPackages.push(wantedDependency);
        }
        return {
            linkedPackages,
            nonLinkedPackages,
        };
    });
}
// 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 wantedDep of wantedDeps) {
        if (wantedDep.alias && importer.specifiers[wantedDep.alias] !== wantedDep.pref) {
            if (importer.dependencies[wantedDep.alias] && !importer.dependencies[wantedDep.alias].startsWith('link:')) {
                delete importer.dependencies[wantedDep.alias];
            }
            delete importer.devDependencies[wantedDep.alias];
            delete importer.optionalDependencies[wantedDep.alias];
        }
    }
}
function linkedPackagesAreUpToDate(pkg, shrImporter, prefix, localPackages) {
    return __awaiter(this, void 0, void 0, function* () {
        const localPackagesByDirectory = localPackages ? getLocalPackagesByDirectory(localPackages) : {};
        for (const depField of types_1.DEPENDENCIES_FIELDS) {
            const importerDeps = shrImporter[depField];
            const pkgDeps = pkg[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] || (yield utils_1.safeReadPackageFromDir(dir));
                const localPackageSatisfiesRange = linkedPkg && semver.satisfies(linkedPkg.version, pkgDeps[depName]);
                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(shr, importerId) {
    const importer = shr.importers && shr.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'));
}
function addDependenciesToPackage(dependencySelectors, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        return mutateModules([
            {
                allowNew: opts.allowNew,
                dependencySelectors,
                mutation: 'installSome',
                pinnedVersion: opts.pinnedVersion,
                prefix: opts.prefix || process.cwd(),
                targetDependenciesField: opts.targetDependenciesField,
            },
        ], Object.assign({}, opts, { shrinkwrapDirectory: opts.shrinkwrapDirectory || opts.prefix }));
    });
}
exports.addDependenciesToPackage = addDependenciesToPackage;
function installInContext(importers, ctx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        if (opts.shrinkwrapOnly && ctx.existsCurrentShrinkwrap) {
            logger_1.default.warn({
                message: '`node_modules` is present. Shrinkwrap only installation will make it out-of-date',
                prefix: ctx.shrinkwrapDirectory,
            });
        }
        // Avoid requesting package meta info from registry only when the shrinkwrap version is at least the expected
        const hasManifestInShrinkwrap = ctx.wantedShrinkwrap.shrinkwrapVersion >= constants_1.SHRINKWRAP_VERSION;
        ctx.wantedShrinkwrap.importers = ctx.wantedShrinkwrap.importers || {};
        for (const importer of importers) {
            if (!ctx.wantedShrinkwrap.importers[importer.id]) {
                ctx.wantedShrinkwrap.importers[importer.id] = { specifiers: {} };
            }
        }
        if (opts.pruneShrinkwrapImporters) {
            const importerIds = new Set(importers.map((importer) => importer.id));
            for (const wantedImporter of Object.keys(ctx.wantedShrinkwrap.importers)) {
                if (!importerIds.has(wantedImporter)) {
                    delete ctx.wantedShrinkwrap.importers[wantedImporter];
                }
            }
        }
        yield Promise.all(importers
            .map((importer) => __awaiter(this, void 0, void 0, function* () {
            if (importer.mutation !== 'uninstallSome')
                return;
            const pkgJsonPath = path.join(importer.prefix, 'package.json');
            importer.pkg = yield removeDeps_1.default(pkgJsonPath, importer.dependencyNames, {
                prefix: importer.prefix,
                saveType: importer.targetDependenciesField,
            });
        })));
        core_loggers_1.stageLogger.debug('resolution_started');
        const { dependenciesTree, outdatedDependencies, resolvedImporters, resolvedPackagesByPackageId, } = yield resolve_dependencies_1.default({
            currentShrinkwrap: ctx.currentShrinkwrap,
            dryRun: opts.shrinkwrapOnly,
            engineStrict: opts.engineStrict,
            force: opts.force,
            hasManifestInShrinkwrap,
            hooks: opts.hooks,
            importers,
            localPackages: opts.localPackages,
            nodeVersion: opts.nodeVersion,
            pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
            preferredVersions: opts.preferredVersions,
            registries: opts.registries,
            sideEffectsCache: opts.sideEffectsCache,
            skipped: ctx.skipped,
            storeController: opts.storeController,
            tag: opts.tag,
            updateDepth: (() => {
                // This can be remove from shrinkwrap v4
                if (!hasManifestInShrinkwrap) {
                    // The shrinkwrap file has to be updated to contain
                    // the necessary info from package manifests
                    return Infinity;
                }
                if (opts.update) {
                    return opts.depth;
                }
                if (modulesIsUpToDate({
                    currentShrinkwrap: ctx.currentShrinkwrap,
                    defaultRegistry: ctx.registries.default,
                    skippedPkgIds: Array.from(ctx.skipped),
                    wantedShrinkwrap: ctx.wantedShrinkwrap,
                })) {
                    return opts.repeatInstallDepth;
                }
                return Infinity;
            })(),
            verifyStoreIntegrity: opts.verifyStoreIntegrity,
            virtualStoreDir: ctx.virtualStoreDir,
            wantedShrinkwrap: ctx.wantedShrinkwrap,
        });
        core_loggers_1.stageLogger.debug('resolution_done');
        const importersToLink = yield Promise.all(importers.map((importer) => __awaiter(this, void 0, void 0, function* () {
            const resolvedImporter = resolvedImporters[importer.id];
            let newPkg = importer.pkg;
            if (importer.updatePackageJson && importer.mutation === 'installSome') {
                if (!importer.pkg) {
                    throw new Error('Cannot save because no package.json found');
                }
                const specsToUsert = resolvedImporter.directDependencies // tslint:disable-line
                    .filter((dep) => importer.newPkgRawSpecs.indexOf(dep.specRaw) !== -1)
                    .map((dep) => {
                    return {
                        name: dep.alias,
                        pref: dep.normalizedPref || getPref_1.default(dep.alias, dep.name, dep.version, {
                            pinnedVersion: importer.pinnedVersion,
                        }),
                        saveType: importer.targetDependenciesField,
                    };
                });
                for (const pkgToInstall of importer.wantedDeps) {
                    if (pkgToInstall.alias && !specsToUsert.some((spec) => spec.name === pkgToInstall.alias)) { // tslint:disable-line
                        specsToUsert.push({
                            name: pkgToInstall.alias,
                            saveType: importer.targetDependenciesField,
                        });
                    }
                }
                newPkg = yield save_1.default(importer.prefix, specsToUsert);
            }
            else {
                core_loggers_1.packageJsonLogger.debug({
                    prefix: importer.prefix,
                    updated: importer.pkg,
                });
            }
            if (newPkg) {
                const shrImporter = ctx.wantedShrinkwrap.importers[importer.id];
                ctx.wantedShrinkwrap.importers[importer.id] = addDirectDependenciesToShrinkwrap(newPkg, shrImporter, importer.linkedPackages, resolvedImporter.directDependencies, ctx.registries.default);
            }
            const topParents = importer.pkg
                ? yield getTopParents(R.difference(R.keys(utils_1.getAllDependenciesFromPackage(importer.pkg)), importer.newPkgRawSpecs && resolvedImporter.directDependencies
                    .filter((directDep) => importer.newPkgRawSpecs.indexOf(directDep.specRaw) !== -1)
                    .map((directDep) => directDep.alias) || []), importer.modulesDir)
                : [];
            return {
                bin: importer.bin,
                directNodeIdsByAlias: resolvedImporter.directNodeIdsByAlias,
                hoistedAliases: importer.hoistedAliases,
                id: importer.id,
                linkedDependencies: resolvedImporter.linkedDependencies,
                modulesDir: importer.modulesDir,
                pkg: newPkg || importer.pkg,
                prefix: importer.prefix,
                pruneDirectDependencies: importer.pruneDirectDependencies,
                removePackages: importer.removePackages,
                shamefullyFlatten: importer.shamefullyFlatten,
                topParents,
                usesExternalShrinkwrap: importer.usesExternalShrinkwrap,
            };
        })));
        const result = yield link_1.default(importersToLink, dependenciesTree, {
            afterAllResolvedHook: opts.hooks && opts.hooks.afterAllResolved,
            currentShrinkwrap: ctx.currentShrinkwrap,
            dryRun: opts.shrinkwrapOnly,
            force: opts.force,
            include: opts.include,
            independentLeaves: opts.independentLeaves,
            makePartialCurrentShrinkwrap: opts.makePartialCurrentShrinkwrap,
            outdatedDependencies,
            pruneStore: opts.pruneStore,
            registries: ctx.registries,
            shrinkwrapDirectory: opts.shrinkwrapDirectory,
            sideEffectsCache: opts.sideEffectsCache,
            skipped: ctx.skipped,
            storeController: opts.storeController,
            strictPeerDependencies: opts.strictPeerDependencies,
            updateShrinkwrapMinorVersion: opts.updateShrinkwrapMinorVersion,
            virtualStoreDir: ctx.virtualStoreDir,
            wantedShrinkwrap: ctx.wantedShrinkwrap,
        });
        ctx.pendingBuilds = ctx.pendingBuilds
            .filter((relDepPath) => !result.removedDepPaths.has(dp.resolve(ctx.registries.default, 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.default, depPath)));
        }
        const shrinkwrapOpts = { forceSharedFormat: opts.forceSharedShrinkwrap };
        if (opts.shrinkwrapOnly) {
            yield pnpm_shrinkwrap_1.writeWantedOnly(ctx.shrinkwrapDirectory, result.wantedShrinkwrap, shrinkwrapOpts);
        }
        else {
            yield Promise.all([
                opts.shrinkwrap
                    ? pnpm_shrinkwrap_1.write(ctx.shrinkwrapDirectory, result.wantedShrinkwrap, result.currentShrinkwrap, shrinkwrapOpts)
                    : pnpm_shrinkwrap_1.writeCurrentOnly(ctx.shrinkwrapDirectory, result.currentShrinkwrap, shrinkwrapOpts),
                (() => {
                    if (result.currentShrinkwrap.packages === undefined && result.removedDepPaths.size === 0) {
                        return Promise.resolve();
                    }
                    return modules_yaml_1.write(ctx.virtualStoreDir, Object.assign({}, ctx.modulesFile, { importers: Object.assign({}, ctx.modulesFile && ctx.modulesFile.importers, importersToLink.reduce((acc, importer) => {
                            acc[importer.id] = {
                                hoistedAliases: importer.hoistedAliases,
                                shamefullyFlatten: importer.shamefullyFlatten,
                            };
                            return acc;
                        }, {})), included: ctx.include, independentLeaves: opts.independentLeaves, layoutVersion: constants_1.LAYOUT_VERSION, packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`, pendingBuilds: ctx.pendingBuilds, registries: ctx.registries, skipped: Array.from(ctx.skipped), store: ctx.storePath }));
                })(),
            ]);
            // postinstall hooks
            if (!(opts.ignoreScripts || !result.newDepPaths || !result.newDepPaths.length)) {
                const limitChild = pLimit(opts.childConcurrency);
                const depPaths = Object.keys(result.depGraph);
                const rootNodes = depPaths.filter((depPath) => result.depGraph[depPath].depth === 0);
                const nodesToBuild = new Set();
                getSubgraphToBuild(result.depGraph, rootNodes, nodesToBuild, new Set());
                const onlyFromBuildGraph = R.filter((depPath) => nodesToBuild.has(depPath));
                const nodesToBuildArray = Array.from(nodesToBuild);
                const graph = new Map(nodesToBuildArray
                    .map((depPath) => [depPath, onlyFromBuildGraph(R.values(result.depGraph[depPath].children))]));
                const graphSequencerResult = graphSequencer({
                    graph,
                    groups: [nodesToBuildArray],
                });
                const chunks = graphSequencerResult.chunks;
                for (const chunk of chunks) {
                    yield Promise.all(chunk
                        .filter((depPath) => result.depGraph[depPath].requiresBuild && !result.depGraph[depPath].isBuilt && result.newDepPaths.indexOf(depPath) !== -1)
                        .map((depPath) => result.depGraph[depPath])
                        .map((pkg) => limitChild(() => __awaiter(this, void 0, void 0, function* () {
                        try {
                            const hasSideEffects = yield lifecycle_1.runPostinstallHooks({
                                depPath: pkg.absolutePath,
                                pkgRoot: pkg.peripheralLocation,
                                prepare: pkg.prepare,
                                rawNpmConfig: opts.rawNpmConfig,
                                rootNodeModulesDir: ctx.virtualStoreDir,
                                unsafePerm: opts.unsafePerm || false,
                            });
                            if (hasSideEffects && opts.sideEffectsCache && !opts.sideEffectsCacheReadonly) {
                                try {
                                    yield opts.storeController.upload(pkg.peripheralLocation, {
                                        engine: ENGINE_NAME,
                                        pkgId: pkg.id,
                                    });
                                }
                                catch (err) {
                                    if (err && err.statusCode === 403) {
                                        logger_1.default.warn({
                                            message: `The store server disabled upload requests, could not upload ${pkg.id}`,
                                            prefix: ctx.shrinkwrapDirectory,
                                        });
                                    }
                                    else {
                                        logger_1.default.warn({
                                            error: err,
                                            message: `An error occurred while uploading ${pkg.id}`,
                                            prefix: ctx.shrinkwrapDirectory,
                                        });
                                    }
                                }
                            }
                        }
                        catch (err) {
                            if (resolvedPackagesByPackageId[pkg.id].optional) {
                                // TODO: add parents field to the log
                                core_loggers_1.skippedOptionalDependencyLogger.debug({
                                    details: err.toString(),
                                    package: {
                                        id: pkg.id,
                                        name: pkg.name,
                                        version: pkg.version,
                                    },
                                    prefix: opts.shrinkwrapDirectory,
                                    reason: 'build_failure',
                                });
                                return;
                            }
                            throw err;
                        }
                    }))));
                }
            }
        }
        // waiting till the skipped packages are downloaded to the store
        yield 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((pkg) => pkg.fetchingFiles));
        // waiting till package requests are finished
        yield Promise.all(R.values(resolvedPackagesByPackageId).map((installed) => installed.finishing));
        core_loggers_1.summaryLogger.debug({ prefix: opts.shrinkwrapDirectory });
        yield opts.storeController.close();
    });
}
function modulesIsUpToDate(opts) {
    const currentWithSkipped = [
        ...R.keys(opts.currentShrinkwrap.packages),
        ...opts.skippedPkgIds.map((skippedPkgId) => dp.relative(opts.defaultRegistry, skippedPkgId))
    ];
    currentWithSkipped.sort();
    return R.equals(R.keys(opts.wantedShrinkwrap.packages), currentWithSkipped);
}
function getSubgraphToBuild(graph, entryNodes, nodesToBuild, walked) {
    let currentShouldBeBuilt = false;
    for (const depPath of entryNodes) {
        if (nodesToBuild.has(depPath)) {
            currentShouldBeBuilt = true;
        }
        if (walked.has(depPath))
            continue;
        walked.add(depPath);
        const childShouldBeBuilt = getSubgraphToBuild(graph, R.values(graph[depPath].children), nodesToBuild, walked)
            || graph[depPath].requiresBuild;
        if (childShouldBeBuilt) {
            nodesToBuild.add(depPath);
            currentShouldBeBuilt = true;
        }
    }
    return currentShouldBeBuilt;
}
function addDirectDependenciesToShrinkwrap(newPkg, shrinkwrapImporter, linkedPackages, directDependencies, standardRegistry) {
    const newShrImporter = {
        dependencies: {},
        devDependencies: {},
        optionalDependencies: {},
        specifiers: {},
    };
    linkedPackages.forEach((linkedPkg) => {
        newShrImporter.specifiers[linkedPkg.alias] = getSpecFromPackageJson_1.default(newPkg, linkedPkg.alias);
    });
    const directDependenciesByAlias = directDependencies.reduce((acc, directDependency) => {
        acc[directDependency.alias] = directDependency;
        return acc;
    }, {});
    const optionalDependencies = R.keys(newPkg.optionalDependencies);
    const dependencies = R.difference(R.keys(newPkg.dependencies), optionalDependencies);
    const devDependencies = R.difference(R.difference(R.keys(newPkg.devDependencies), optionalDependencies), dependencies);
    const allDeps = R.reduce(R.union, [], [optionalDependencies, devDependencies, dependencies]);
    for (const alias of allDeps) {
        if (directDependenciesByAlias[alias]) {
            const dep = directDependenciesByAlias[alias];
            const ref = shrinkwrap_1.absolutePathToRef(dep.id, {
                alias: dep.alias,
                realName: dep.name,
                resolution: dep.resolution,
                standardRegistry,
            });
            if (dep.dev) {
                newShrImporter.devDependencies[dep.alias] = ref;
            }
            else if (dep.optional) {
                newShrImporter.optionalDependencies[dep.alias] = ref;
            }
            else {
                newShrImporter.dependencies[dep.alias] = ref;
            }
            newShrImporter.specifiers[dep.alias] = getSpecFromPackageJson_1.default(newPkg, dep.alias);
        }
        else if (shrinkwrapImporter.specifiers[alias]) {
            newShrImporter.specifiers[alias] = shrinkwrapImporter.specifiers[alias];
            if (shrinkwrapImporter.dependencies && shrinkwrapImporter.dependencies[alias]) {
                newShrImporter.dependencies[alias] = shrinkwrapImporter.dependencies[alias];
            }
            else if (shrinkwrapImporter.optionalDependencies && shrinkwrapImporter.optionalDependencies[alias]) {
                newShrImporter.optionalDependencies[alias] = shrinkwrapImporter.optionalDependencies[alias];
            }
            else if (shrinkwrapImporter.devDependencies && shrinkwrapImporter.devDependencies[alias]) {
                newShrImporter.devDependencies[alias] = shrinkwrapImporter.devDependencies[alias];
            }
        }
    }
    alignDependencyTypes(newPkg, newShrImporter);
    return newShrImporter;
}
function alignDependencyTypes(pkg, shrImporter) {
    const depTypesOfAliases = getAliasToDependencyTypeMap(pkg);
    // Aligning the dependency types in shrinkwrap.yaml
    for (const depType of types_1.DEPENDENCIES_FIELDS) {
        if (!shrImporter[depType])
            continue;
        for (const alias of Object.keys(shrImporter[depType] || {})) {
            if (depType === depTypesOfAliases[alias] || !depTypesOfAliases[alias])
                continue;
            shrImporter[depTypesOfAliases[alias]][alias] = shrImporter[depType][alias];
            delete shrImporter[depType][alias];
        }
    }
}
function getAliasToDependencyTypeMap(pkg) {
    const depTypesOfAliases = {};
    for (const depType of types_1.DEPENDENCIES_FIELDS) {
        if (!pkg[depType])
            continue;
        for (const alias of Object.keys(pkg[depType] || {})) {
            if (!depTypesOfAliases[alias]) {
                depTypesOfAliases[alias] = depType;
            }
        }
    }
    return depTypesOfAliases;
}
function getTopParents(pkgNames, modules) {
    return __awaiter(this, void 0, void 0, function* () {
        const pkgs = yield Promise.all(pkgNames.map((pkgName) => path.join(modules, pkgName)).map(utils_1.safeReadPackageFromDir));
        return pkgs.filter(Boolean).map((pkg) => ({
            name: pkg.name,
            version: pkg.version,
        }));
    });
}
//# sourceMappingURL=index.js.map