"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 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 utils_1 = require("@pnpm/utils");
const dp = require("dependency-path");
const graphSequencer = require("graph-sequencer");
const pLimit = require("p-limit");
const path = require("path");
const pnpm_shrinkwrap_1 = require("pnpm-shrinkwrap");
const R = require("ramda");
const constants_1 = require("../constants");
const depsFromPackage_1 = require("../depsFromPackage");
const depsToSpecs_1 = require("../depsToSpecs");
const shrinkwrap_1 = require("../fs/shrinkwrap");
const getSpecFromPackageJson_1 = require("../getSpecFromPackageJson");
const link_1 = require("../link");
const nodeIdUtils_1 = require("../nodeIdUtils");
const parseWantedDependencies_1 = require("../parseWantedDependencies");
const resolveDependencies_1 = require("../resolveDependencies");
const safeIsInnerLink_1 = require("../safeIsInnerLink");
const save_1 = require("../save");
const extendInstallOptions_1 = require("./extendInstallOptions");
const getContext_1 = require("./getContext");
const link_2 = require("./link");
const lock_1 = require("./lock");
const shrinkwrapsEqual_1 = require("./shrinkwrapsEqual");
const ENGINE_NAME = `${process.platform}-${process.arch}-node-${process.version.split('.')[0]}`;
function install(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.production && opts.optional) {
            throw new Error('Optional dependencies cannot be installed without production dependencies');
        }
        if (opts.lock) {
            yield lock_1.default(opts.prefix, _install, { stale: opts.lockStaleDuration, locks: opts.locks });
        }
        else {
            yield _install();
        }
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
        function _install() {
            return __awaiter(this, void 0, void 0, function* () {
                const installType = 'general';
                const ctx = yield getContext_1.default(opts, installType);
                if (!ctx.pkg)
                    throw new Error('No package.json found');
                if (!opts.update && (opts.frozenShrinkwrap ||
                    opts.preferFrozenShrinkwrap && ctx.existsWantedShrinkwrap && ctx.wantedShrinkwrap.shrinkwrapMinorVersion === constants_1.SHRINKWRAP_MINOR_VERSION &&
                        !hasLocalTarballDepsInRoot(ctx.wantedShrinkwrap) &&
                        pnpm_shrinkwrap_1.satisfiesPackageJson(ctx.wantedShrinkwrap, ctx.pkg))) {
                    if (opts.shamefullyFlatten) {
                        if (opts.frozenShrinkwrap) {
                            logger_1.default.warn('Headless installation does not support flat node_modules layout yet');
                        }
                    }
                    else if (!ctx.existsWantedShrinkwrap) {
                        if (R.keys(ctx.pkg.dependencies).length || R.keys(ctx.pkg.devDependencies).length || R.keys(ctx.pkg.optionalDependencies).length) {
                            throw new Error('Headless installation requires a shrinkwrap.yaml file');
                        }
                    }
                    else {
                        logger_1.default.info('Performing headless installation');
                        yield headless_1.default(Object.assign({}, opts, { currentShrinkwrap: ctx.currentShrinkwrap, packageJson: ctx.pkg, wantedShrinkwrap: ctx.wantedShrinkwrap }));
                        return;
                    }
                }
                const preferredVersions = depsFromPackage_1.getPreferredVersionsFromPackage(ctx.pkg);
                const specs = specsToInstallFromPackage(ctx.pkg, {
                    prefix: opts.prefix,
                });
                if (ctx.wantedShrinkwrap.specifiers) {
                    ctx.wantedShrinkwrap.dependencies = ctx.wantedShrinkwrap.dependencies || {};
                    ctx.wantedShrinkwrap.devDependencies = ctx.wantedShrinkwrap.devDependencies || {};
                    ctx.wantedShrinkwrap.optionalDependencies = ctx.wantedShrinkwrap.optionalDependencies || {};
                    for (const spec of specs) {
                        if (spec.alias && ctx.wantedShrinkwrap.specifiers[spec.alias] !== spec.pref) {
                            if (ctx.wantedShrinkwrap.dependencies[spec.alias] && !ctx.wantedShrinkwrap.dependencies[spec.alias].startsWith('link:')) {
                                delete ctx.wantedShrinkwrap.dependencies[spec.alias];
                            }
                            delete ctx.wantedShrinkwrap.devDependencies[spec.alias];
                            delete ctx.wantedShrinkwrap.optionalDependencies[spec.alias];
                        }
                    }
                }
                const scripts = !opts.ignoreScripts && ctx.pkg && ctx.pkg.scripts || {};
                if (scripts['prepublish']) { // tslint:disable-line:no-string-literal
                    logger_1.default.warn('`prepublish` scripts are deprecated. Use `prepare` for build steps and `prepublishOnly` for upload-only.');
                }
                const scriptsOpts = {
                    depPath: opts.prefix,
                    pkgRoot: opts.prefix,
                    rawNpmConfig: opts.rawNpmConfig,
                    rootNodeModulesDir: yield utils_1.realNodeModulesDir(opts.prefix),
                    stdio: opts.ownLifecycleHooksStdio,
                    unsafePerm: opts.unsafePerm || false,
                };
                if (scripts.preinstall) {
                    yield lifecycle_1.default('preinstall', ctx.pkg, scriptsOpts);
                }
                yield installInContext(installType, specs, [], ctx, preferredVersions, opts);
                if (scripts.install) {
                    yield lifecycle_1.default('install', ctx.pkg, scriptsOpts);
                }
                if (scripts.postinstall) {
                    yield lifecycle_1.default('postinstall', ctx.pkg, scriptsOpts);
                }
                if (scripts.prepublish) {
                    yield lifecycle_1.default('prepublish', ctx.pkg, scriptsOpts);
                }
                if (scripts.prepare) {
                    yield lifecycle_1.default('prepare', ctx.pkg, scriptsOpts);
                }
            });
        }
    });
}
exports.install = install;
function hasLocalTarballDepsInRoot(shr) {
    return R.any(refIsLocalTarball, R.values(shr.dependencies || {}))
        || R.any(refIsLocalTarball, R.values(shr.devDependencies || {}))
        || R.any(refIsLocalTarball, R.values(shr.optionalDependencies || {}));
}
function refIsLocalTarball(ref) {
    return ref.startsWith('file:') && (ref.endsWith('.tgz') || ref.endsWith('.tar.gz') || ref.endsWith('.tar'));
}
function specsToInstallFromPackage(pkg, opts) {
    const depsToInstall = depsFromPackage_1.default(pkg);
    return depsToSpecs_1.default(depsToInstall, {
        devDependencies: pkg.devDependencies || {},
        optionalDependencies: pkg.optionalDependencies || {},
    });
}
/**
 * Perform installation.
 *
 * @example
 *     install({'lodash': '1.0.0', 'foo': '^2.1.0' }, { silent: true })
 */
function installPkgs(fuzzyDeps, maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const reporter = maybeOpts && maybeOpts.reporter;
        if (reporter) {
            logger_1.streamParser.on('data', reporter);
        }
        if (maybeOpts.update === undefined)
            maybeOpts.update = true;
        const opts = yield extendInstallOptions_1.default(maybeOpts);
        if (R.isEmpty(fuzzyDeps) && !opts.reinstallForFlatten) {
            throw new Error('At least one package has to be installed');
        }
        if (opts.lock) {
            yield lock_1.default(opts.prefix, _installPkgs, { stale: opts.lockStaleDuration, locks: opts.locks });
        }
        else {
            yield _installPkgs();
        }
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
        function _installPkgs() {
            return __awaiter(this, void 0, void 0, function* () {
                const installType = 'named';
                const ctx = yield getContext_1.default(opts, installType);
                const currentPrefs = opts.global ? {} : depsFromPackage_1.default(ctx.pkg);
                opts.allowNew = opts.allowNew || opts.global; // TODO: global install should also know about globally installed packages
                const saveType = utils_1.getSaveType(opts);
                const optionalDependencies = saveType ? {} : ctx.pkg.optionalDependencies || {};
                const devDependencies = saveType ? {} : ctx.pkg.devDependencies || {};
                const packagesToInstall = Array.isArray(fuzzyDeps)
                    ? parseWantedDependencies_1.default(fuzzyDeps, {
                        allowNew: opts.allowNew,
                        currentPrefs,
                        defaultTag: opts.tag,
                        dev: opts.saveDev,
                        devDependencies,
                        optional: opts.saveOptional,
                        optionalDependencies,
                    })
                    : depsToSpecs_1.similarDepsToSpecs(fuzzyDeps, {
                        allowNew: opts.allowNew,
                        currentPrefs,
                        dev: opts.saveDev,
                        devDependencies,
                        optional: opts.saveOptional,
                        optionalDependencies,
                    });
                const preferredVersions = depsFromPackage_1.getPreferredVersionsFromPackage(ctx.pkg);
                return installInContext(installType, packagesToInstall, packagesToInstall.map((wantedDependency) => wantedDependency.raw), ctx, preferredVersions, opts);
            });
        }
    });
}
exports.installPkgs = installPkgs;
function installInContext(installType, packagesToInstall, newPkgRawSpecs, ctx, preferredVersions, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        // 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 = installType === 'named' && (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));
        if (opts.shrinkwrapOnly && ctx.existsCurrentShrinkwrap) {
            logger_1.default.warn('`node_modules` is present. Shrinkwrap only installation will make it out-of-date');
        }
        const nodeModulesPath = yield utils_1.realNodeModulesDir(ctx.root);
        // Avoid requesting package meta info from registry only when the shrinkwrap version is at least the expected
        const hasManifestInShrinkwrap = typeof ctx.wantedShrinkwrap.shrinkwrapMinorVersion === 'number' &&
            ctx.wantedShrinkwrap.shrinkwrapMinorVersion >= constants_1.SHRINKWRAP_MINOR_VERSION;
        const installCtx = {
            childrenByParentId: {},
            currentShrinkwrap: ctx.currentShrinkwrap,
            defaultTag: opts.tag,
            depth: (() => {
                // 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 (R.equals(ctx.wantedShrinkwrap.packages, ctx.currentShrinkwrap.packages)) {
                    return opts.repeatInstallDepth;
                }
                return Infinity;
            })(),
            dryRun: opts.shrinkwrapOnly,
            engineStrict: opts.engineStrict,
            force: opts.force,
            localPackages: [],
            nodeModules: nodeModulesPath,
            nodeVersion: opts.nodeVersion,
            nodesToBuild: [],
            outdatedPkgs: {},
            pkgByPkgId: {},
            pkgGraph: {},
            pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
            preferredVersions,
            prefix: opts.prefix,
            rawNpmConfig: opts.rawNpmConfig,
            registry: ctx.wantedShrinkwrap.registry,
            skipped: ctx.skipped,
            storeController: opts.storeController,
            verifyStoreInegrity: opts.verifyStoreIntegrity,
            wantedShrinkwrap: ctx.wantedShrinkwrap,
        };
        const installOpts = {
            currentDepth: 0,
            hasManifestInShrinkwrap,
            keypath: [],
            parentNodeId: nodeIdUtils_1.ROOT_NODE_ID,
            readPackageHook: opts.hooks.readPackage,
            reinstallForFlatten: opts.reinstallForFlatten,
            resolvedDependencies: Object.assign({}, ctx.wantedShrinkwrap.dependencies, ctx.wantedShrinkwrap.devDependencies, ctx.wantedShrinkwrap.optionalDependencies),
            root: ctx.root,
            shamefullyFlatten: opts.shamefullyFlatten,
            sideEffectsCache: opts.sideEffectsCache,
            update: opts.update,
        };
        const nonLinkedPkgs = [];
        const linkedPkgs = [];
        for (const wantedDependency of packagesToInstall) {
            if (!wantedDependency.alias) {
                nonLinkedPkgs.push(wantedDependency);
                continue;
            }
            const isInnerLink = yield safeIsInnerLink_1.default(nodeModulesPath, wantedDependency.alias, {
                storePath: ctx.storePath,
            });
            if (isInnerLink === true) {
                nonLinkedPkgs.push(wantedDependency);
                continue;
            }
            utils_1.rootLogger.debug({
                linked: {
                    dependencyType: wantedDependency.dev && 'dev' || wantedDependency.optional && 'optional' || 'prod',
                    from: isInnerLink,
                    name: wantedDependency.alias,
                    to: nodeModulesPath,
                },
            });
            // This info-log might be better to be moved to the reporter
            logger_1.default.info(`${wantedDependency.alias} is linked to ${nodeModulesPath} from ${isInnerLink}`);
            linkedPkgs.push(wantedDependency);
        }
        utils_1.stageLogger.debug('resolution_started');
        const rootPkgs = yield resolveDependencies_1.default(installCtx, nonLinkedPkgs, installOpts);
        utils_1.stageLogger.debug('resolution_done');
        installCtx.nodesToBuild.forEach((nodeToBuild) => {
            installCtx.pkgGraph[nodeToBuild.nodeId] = {
                children: () => buildTree(installCtx, nodeToBuild.nodeId, nodeToBuild.pkg.id, installCtx.childrenByParentId[nodeToBuild.pkg.id], nodeToBuild.depth + 1, nodeToBuild.installable),
                depth: nodeToBuild.depth,
                installable: nodeToBuild.installable,
                pkg: nodeToBuild.pkg,
            };
        });
        const rootNodeIdsByAlias = rootPkgs
            .reduce((acc, rootPkg) => {
            const pkg = installCtx.pkgGraph[rootPkg.nodeId].pkg;
            const specRaw = pkg.specRaw;
            const spec = R.find((sp) => sp.raw === specRaw, packagesToInstall);
            acc[rootPkg.alias] = rootPkg.nodeId;
            return acc;
        }, {});
        const pkgsToSave = rootPkgs
            .map((rootPkg) => (Object.assign({}, installCtx.pkgGraph[rootPkg.nodeId].pkg, { alias: rootPkg.alias, normalizedPref: rootPkg.normalizedPref })))
            .concat(installCtx.localPackages);
        let newPkg = ctx.pkg;
        if (installType === 'named' && !opts.reinstallForFlatten) {
            if (!ctx.pkg) {
                throw new Error('Cannot save because no package.json found');
            }
            const pkgJsonPath = path.join(ctx.root, 'package.json');
            const saveType = utils_1.getSaveType(opts);
            const specsToUsert = pkgsToSave // tslint:disable-line
                .map((dep) => {
                return {
                    name: dep.alias,
                    pref: dep.normalizedPref || getPref(dep.alias, dep.name, dep.version, {
                        saveExact: opts.saveExact,
                        savePrefix: opts.savePrefix,
                    }),
                    saveType,
                };
            });
            for (const pkgToInstall of packagesToInstall) {
                if (pkgToInstall.alias && !specsToUsert.some((spec) => spec.name === pkgToInstall.alias)) { // tslint:disable-line
                    specsToUsert.push({
                        name: pkgToInstall.alias,
                        saveType,
                    });
                }
            }
            newPkg = yield save_1.default(pkgJsonPath, specsToUsert);
        }
        else {
            utils_1.packageJsonLogger.debug({ updated: ctx.pkg });
        }
        if (newPkg) {
            ctx.wantedShrinkwrap.dependencies = ctx.wantedShrinkwrap.dependencies || {};
            ctx.wantedShrinkwrap.specifiers = ctx.wantedShrinkwrap.specifiers || {};
            ctx.wantedShrinkwrap.optionalDependencies = ctx.wantedShrinkwrap.optionalDependencies || {};
            ctx.wantedShrinkwrap.devDependencies = ctx.wantedShrinkwrap.devDependencies || {};
            const devDeps = newPkg.devDependencies || {};
            const optionalDeps = newPkg.optionalDependencies || {};
            linkedPkgs.forEach((linkedPkg) => {
                ctx.wantedShrinkwrap.specifiers[linkedPkg.alias] = getSpecFromPackageJson_1.default(newPkg, linkedPkg.alias);
            });
            for (const dep of pkgsToSave) {
                const ref = shrinkwrap_1.absolutePathToRef(dep.id, {
                    alias: dep.alias,
                    realName: dep.name,
                    resolution: dep.resolution,
                    standardRegistry: ctx.wantedShrinkwrap.registry,
                });
                const isDev = !!devDeps[dep.alias];
                const isOptional = !!optionalDeps[dep.alias];
                if (isDev) {
                    ctx.wantedShrinkwrap.devDependencies[dep.alias] = ref;
                }
                else if (isOptional) {
                    ctx.wantedShrinkwrap.optionalDependencies[dep.alias] = ref;
                }
                else {
                    ctx.wantedShrinkwrap.dependencies[dep.alias] = ref;
                }
                if (!isDev) {
                    delete ctx.wantedShrinkwrap.devDependencies[dep.alias];
                }
                if (!isOptional) {
                    delete ctx.wantedShrinkwrap.optionalDependencies[dep.alias];
                }
                if (isDev || isOptional) {
                    delete ctx.wantedShrinkwrap.dependencies[dep.alias];
                }
                ctx.wantedShrinkwrap.specifiers[dep.alias] = getSpecFromPackageJson_1.default(newPkg, dep.alias);
            }
            alignDependencyTypes(newPkg, ctx.wantedShrinkwrap);
        }
        const topParents = ctx.pkg
            ? yield getTopParents(R.difference(R.keys(depsFromPackage_1.default(ctx.pkg)), newPkgRawSpecs && pkgsToSave.filter((pkgToSave) => newPkgRawSpecs.indexOf(pkgToSave.specRaw) !== -1).map((pkg) => pkg.alias) || []), nodeModulesPath)
            : [];
        const result = yield link_1.default(rootNodeIdsByAlias, installCtx.pkgGraph, {
            afterAllResolvedHook: opts.hooks && opts.hooks.afterAllResolved,
            baseNodeModules: nodeModulesPath,
            bin: opts.bin,
            currentShrinkwrap: ctx.currentShrinkwrap,
            development: opts.development,
            dryRun: opts.shrinkwrapOnly,
            force: opts.force,
            global: opts.global,
            hoistedAliases: ctx.hoistedAliases,
            independentLeaves: opts.independentLeaves,
            makePartialCurrentShrinkwrap,
            optional: opts.optional,
            outdatedPkgs: installCtx.outdatedPkgs,
            pkg: newPkg || ctx.pkg,
            production: opts.production,
            reinstallForFlatten: Boolean(opts.reinstallForFlatten),
            root: ctx.root,
            shamefullyFlatten: opts.shamefullyFlatten,
            sideEffectsCache: opts.sideEffectsCache,
            skipped: ctx.skipped,
            storeController: opts.storeController,
            topParents,
            updateShrinkwrapMinorVersion: installType === 'general' || R.isEmpty(ctx.currentShrinkwrap.packages),
            wantedShrinkwrap: ctx.wantedShrinkwrap,
        });
        ctx.hoistedAliases = result.hoistedAliases;
        ctx.pendingBuilds = ctx.pendingBuilds
            .filter((relDepPath) => !result.removedDepPaths.has(dp.resolve(ctx.wantedShrinkwrap.registry, 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.wantedShrinkwrap.registry, depPath)));
        }
        if (opts.shrinkwrapOnly) {
            yield pnpm_shrinkwrap_1.writeWantedOnly(ctx.root, result.wantedShrinkwrap);
        }
        else {
            yield Promise.all([
                opts.shrinkwrap
                    ? pnpm_shrinkwrap_1.write(ctx.root, result.wantedShrinkwrap, result.currentShrinkwrap)
                    : pnpm_shrinkwrap_1.writeCurrentOnly(ctx.root, result.currentShrinkwrap),
                result.currentShrinkwrap.packages === undefined && result.removedDepPaths.size === 0
                    ? Promise.resolve()
                    : modules_yaml_1.write(path.join(ctx.root, 'node_modules'), {
                        hoistedAliases: ctx.hoistedAliases,
                        independentLeaves: opts.independentLeaves,
                        layoutVersion: constants_1.LAYOUT_VERSION,
                        packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`,
                        pendingBuilds: ctx.pendingBuilds,
                        shamefullyFlatten: opts.shamefullyFlatten,
                        skipped: Array.from(installCtx.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: installCtx.rawNpmConfig,
                                rootNodeModulesDir: ctx.root,
                                unsafePerm: opts.unsafePerm || false,
                            });
                            if (hasSideEffects && opts.sideEffectsCache && !opts.sideEffectsCacheReadonly) {
                                try {
                                    yield installCtx.storeController.upload(pkg.peripheralLocation, {
                                        engine: ENGINE_NAME,
                                        pkgId: pkg.id,
                                    });
                                }
                                catch (err) {
                                    if (err && err.statusCode === 403) {
                                        logger_1.default.warn(`The store server disabled upload requests, could not upload ${pkg.id}`);
                                    }
                                    else {
                                        logger_1.default.warn({
                                            err,
                                            message: `An error occurred while uploading ${pkg.id}`,
                                        });
                                    }
                                }
                            }
                        }
                        catch (err) {
                            if (installCtx.pkgByPkgId[pkg.id].optional) {
                                // TODO: add parents field to the log
                                utils_1.skippedOptionalDependencyLogger.debug({
                                    details: err.toString(),
                                    package: {
                                        id: pkg.id,
                                        name: pkg.name,
                                        version: pkg.version,
                                    },
                                    reason: 'build_failure',
                                });
                                return;
                            }
                            throw err;
                        }
                    }))));
                }
            }
            if (installCtx.localPackages.length) {
                const linkOpts = Object.assign({}, opts, { linkToBin: opts.bin, skipInstall: true });
                const externalPkgs = installCtx.localPackages.map((localPackage) => localPackage.resolution.directory);
                yield link_2.default(externalPkgs, installCtx.nodeModules, linkOpts);
            }
        }
        // waiting till the skipped packages are downloaded to the store
        yield Promise.all(R.props(Array.from(installCtx.skipped), installCtx.pkgByPkgId)
            // 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(installCtx.pkgByPkgId).map((installed) => installed.finishing));
        utils_1.summaryLogger.info(undefined);
        yield opts.storeController.close();
    });
}
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 alignDependencyTypes(pkg, shr) {
    const depTypesOfAliases = getAliasToDependencyTypeMap(pkg);
    // Aligning the dependency types in shrinkwrap.yaml
    for (const depType of utils_1.dependenciesTypes) {
        if (!shr[depType])
            continue;
        for (const alias of Object.keys(shr[depType] || {})) {
            if (depType === depTypesOfAliases[alias] || !depTypesOfAliases[alias])
                continue;
            shr[depTypesOfAliases[alias]][alias] = shr[depType][alias];
            delete shr[depType][alias];
        }
    }
}
function getAliasToDependencyTypeMap(pkg) {
    const depTypesOfAliases = {};
    for (const depType of utils_1.dependenciesTypes) {
        if (!pkg[depType])
            continue;
        for (const alias of Object.keys(pkg[depType] || {})) {
            depTypesOfAliases[alias] = depType;
        }
    }
    return depTypesOfAliases;
}
function buildTree(ctx, parentNodeId, parentId, children, depth, installable) {
    const childrenNodeIds = {};
    for (const child of children) {
        if (nodeIdUtils_1.nodeIdContainsSequence(parentNodeId, parentId, child.pkgId)) {
            continue;
        }
        const childNodeId = nodeIdUtils_1.createNodeId(parentNodeId, child.pkgId);
        childrenNodeIds[child.alias] = childNodeId;
        installable = installable && !ctx.skipped.has(child.pkgId);
        ctx.pkgGraph[childNodeId] = {
            children: () => buildTree(ctx, childNodeId, child.pkgId, ctx.childrenByParentId[child.pkgId], depth + 1, installable),
            depth,
            installable,
            pkg: ctx.pkgByPkgId[child.pkgId],
        };
    }
    return childrenNodeIds;
}
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,
        }));
    });
}
function getPref(alias, name, version, opts) {
    const prefix = alias !== name ? `npm:${name}@` : '';
    if (opts.saveExact)
        return `${prefix}${version}`;
    return `${prefix}${opts.savePrefix}${version}`;
}
//# sourceMappingURL=install.js.map