"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 lifecycle_1 = require("@pnpm/lifecycle");
const link_bins_1 = require("@pnpm/link-bins");
const logger_1 = require("@pnpm/logger");
const modules_cleaner_1 = require("@pnpm/modules-cleaner");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const pkgid_to_filename_1 = require("@pnpm/pkgid-to-filename");
const read_package_json_1 = require("@pnpm/read-package-json");
const shamefully_flatten_1 = require("@pnpm/shamefully-flatten");
const symlink_dependency_1 = require("@pnpm/symlink-dependency");
const utils_1 = require("@pnpm/utils");
const dp = require("dependency-path");
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 runDependenciesScripts_1 = require("./runDependenciesScripts");
exports.default = (opts) => __awaiter(this, void 0, void 0, function* () {
    const reporter = opts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    const shrinkwrapDirectory = opts.shrinkwrapDirectory;
    const wantedShrinkwrap = opts.wantedShrinkwrap || (yield pnpm_shrinkwrap_1.readWanted(shrinkwrapDirectory, { ignoreIncompatible: false }));
    if (!wantedShrinkwrap) {
        throw new Error('Headless installation requires a shrinkwrap.yaml file');
    }
    const currentShrinkwrap = opts.currentShrinkwrap || (yield pnpm_shrinkwrap_1.readCurrent(shrinkwrapDirectory, { ignoreIncompatible: false }));
    const virtualStoreDir = yield utils_1.realNodeModulesDir(shrinkwrapDirectory);
    for (const importer of opts.importers) {
        if (!pnpm_shrinkwrap_1.satisfiesPackageJson(wantedShrinkwrap, importer.pkg, importer.id)) {
            const err = new Error('Cannot install with "frozen-shrinkwrap" because shrinkwrap.yaml is not up-to-date with ' +
                path.relative(opts.shrinkwrapDirectory, path.join(importer.prefix, 'package.json')));
            err['code'] = 'ERR_PNPM_OUTDATED_SHRINKWRAP'; // tslint:disable-line
            throw err;
        }
    }
    if (!opts.ignoreScripts) {
        for (const importer of opts.importers) {
            const scripts = !opts.ignoreScripts && importer.pkg.scripts || {};
            const scriptsOpts = {
                depPath: importer.prefix,
                pkgRoot: importer.prefix,
                rawNpmConfig: opts.rawNpmConfig,
                rootNodeModulesDir: importer.modulesDir,
                stdio: opts.ownLifecycleHooksStdio || 'inherit',
                unsafePerm: opts.unsafePerm || false,
            };
            if (scripts.preinstall) {
                yield lifecycle_1.default('preinstall', importer.pkg, scriptsOpts);
            }
        }
    }
    const filterOpts = {
        defaultRegistry: opts.registries.default,
        include: opts.include,
        skipped: new Set(),
    };
    if (currentShrinkwrap) {
        yield modules_cleaner_1.prune({
            dryRun: false,
            importers: opts.importers,
            newShrinkwrap: pnpm_shrinkwrap_1.filter(wantedShrinkwrap, filterOpts),
            oldShrinkwrap: currentShrinkwrap,
            pruneStore: opts.pruneStore,
            registries: opts.registries,
            shrinkwrapDirectory,
            storeController: opts.storeController,
            virtualStoreDir,
        });
    }
    else {
        core_loggers_1.statsLogger.debug({
            prefix: shrinkwrapDirectory,
            removed: 0,
        });
    }
    core_loggers_1.stageLogger.debug('importing_started');
    const filteredShrinkwrap = pnpm_shrinkwrap_1.filterByImporters(wantedShrinkwrap, opts.importers.map((importer) => importer.id), Object.assign({}, filterOpts, { failOnMissingDependencies: true }));
    const res = yield shrinkwrapToDepGraph(filteredShrinkwrap, opts.force ? null : currentShrinkwrap, Object.assign({}, opts, { defaultRegistry: opts.registries.default, importerIds: opts.importers.map((importer) => importer.id), prefix: shrinkwrapDirectory, virtualStoreDir }));
    const depGraph = res.graph;
    core_loggers_1.statsLogger.debug({
        added: Object.keys(depGraph).length,
        prefix: shrinkwrapDirectory,
    });
    yield Promise.all([
        linkAllModules(depGraph, { optional: opts.include.optionalDependencies }),
        linkAllPkgs(opts.storeController, R.values(depGraph), opts),
    ]);
    core_loggers_1.stageLogger.debug('importing_done');
    function warn(message) {
        logger_1.default.warn({
            message,
            prefix: shrinkwrapDirectory,
        });
    }
    yield linkAllBins(depGraph, { optional: opts.include.optionalDependencies, warn });
    yield Promise.all(opts.importers.map((importer) => __awaiter(this, void 0, void 0, function* () {
        if (importer.shamefullyFlatten) {
            importer.hoistedAliases = yield shamefully_flatten_1.shamefullyFlattenByShrinkwrap(filteredShrinkwrap, importer.id, {
                defaultRegistry: opts.registries.default,
                modulesDir: importer.modulesDir,
                prefix: importer.prefix,
                virtualStoreDir,
            });
        }
        else {
            importer.hoistedAliases = {};
        }
    })));
    yield Promise.all(opts.importers.map((importer) => __awaiter(this, void 0, void 0, function* () {
        yield linkRootPackages(filteredShrinkwrap, {
            defaultRegistry: opts.registries.default,
            importerId: importer.id,
            importerModulesDir: importer.modulesDir,
            prefix: importer.prefix,
            rootDependencies: res.directDependenciesByImporterId[importer.id],
        });
        const bin = path.join(importer.modulesDir, '.bin');
        yield link_bins_1.default(importer.modulesDir, bin, { warn });
        // Even though headless installation will never update the package.json
        // this needs to be logged because otherwise install summary won't be printed
        core_loggers_1.packageJsonLogger.debug({
            prefix: importer.prefix,
            updated: importer.pkg,
        });
    })));
    yield pnpm_shrinkwrap_1.writeCurrentOnly(shrinkwrapDirectory, filteredShrinkwrap);
    if (opts.ignoreScripts) {
        for (const importer of opts.importers) {
            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)) {
                opts.pendingBuilds.push(importer.id);
            }
        }
        // we can use concat here because we always only append new packages, which are guaranteed to not be there by definition
        opts.pendingBuilds = opts.pendingBuilds
            .concat(R.values(depGraph)
            .filter((node) => node.requiresBuild)
            .map((node) => node.relDepPath));
    }
    yield modules_yaml_1.write(virtualStoreDir, {
        importers: opts.importers.reduce((acc, importer) => {
            acc[importer.id] = {
                hoistedAliases: importer.hoistedAliases,
                shamefullyFlatten: importer.shamefullyFlatten,
            };
            return acc;
        }, {}),
        included: opts.include,
        independentLeaves: !!opts.independentLeaves,
        layoutVersion: constants_1.LAYOUT_VERSION,
        packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`,
        pendingBuilds: opts.pendingBuilds,
        registries: opts.registries,
        skipped: [],
        store: opts.store,
    });
    if (!opts.ignoreScripts) {
        for (const importer of opts.importers) {
            yield runDependenciesScripts_1.default(depGraph, R.values(res.directDependenciesByImporterId[importer.id]).filter((loc) => depGraph[loc]), {
                childConcurrency: opts.childConcurrency,
                prefix: importer.prefix,
                rawNpmConfig: opts.rawNpmConfig,
                rootNodeModulesDir: importer.modulesDir,
                sideEffectsCache: opts.sideEffectsCache,
                sideEffectsCacheReadonly: opts.sideEffectsCacheReadonly,
                storeController: opts.storeController,
                unsafePerm: opts.unsafePerm,
                userAgent: opts.userAgent,
            });
        }
    }
    // waiting till package requests are finished
    yield Promise.all(R.values(depGraph).map((depNode) => depNode.finishing));
    core_loggers_1.summaryLogger.debug({ prefix: opts.shrinkwrapDirectory });
    yield opts.storeController.close();
    if (!opts.ignoreScripts) {
        for (const importer of opts.importers) {
            if (!importer.pkg.scripts)
                continue;
            const scriptsOpts = {
                depPath: importer.prefix,
                pkgRoot: importer.prefix,
                rawNpmConfig: opts.rawNpmConfig,
                rootNodeModulesDir: importer.modulesDir,
                stdio: opts.ownLifecycleHooksStdio || 'inherit',
                unsafePerm: opts.unsafePerm || false,
            };
            if (importer.pkg.scripts.install) {
                yield lifecycle_1.default('install', importer.pkg, scriptsOpts);
            }
            if (importer.pkg.scripts.postinstall) {
                yield lifecycle_1.default('postinstall', importer.pkg, scriptsOpts);
            }
            if (importer.pkg.scripts.prepublish) {
                yield lifecycle_1.default('prepublish', importer.pkg, scriptsOpts);
            }
            if (importer.pkg.scripts.prepare) {
                yield lifecycle_1.default('prepare', importer.pkg, scriptsOpts);
            }
        }
    }
    if (reporter) {
        logger_1.streamParser.removeListener('data', reporter);
    }
});
function linkRootPackages(shr, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const shrImporter = shr.importers[opts.importerId];
        const allDeps = Object.assign({}, shrImporter.devDependencies, shrImporter.dependencies, shrImporter.optionalDependencies);
        return Promise.all(R.keys(allDeps)
            .map((alias) => __awaiter(this, void 0, void 0, function* () {
            if (allDeps[alias].startsWith('link:')) {
                const isDev = shrImporter.devDependencies && shrImporter.devDependencies[alias];
                const isOptional = shrImporter.optionalDependencies && shrImporter.optionalDependencies[alias];
                const packageDir = path.join(opts.prefix, allDeps[alias].substr(5));
                const linkedPackage = yield read_package_json_1.fromDir(packageDir);
                yield symlink_dependency_1.symlinkDirectRootDependency(packageDir, opts.importerModulesDir, alias, {
                    fromDependenciesField: isDev && 'devDependencies' ||
                        isOptional && 'optionalDependencies' ||
                        'dependencies',
                    linkedPackage,
                    prefix: opts.prefix,
                });
                return;
            }
            const depPath = dp.refToAbsolute(allDeps[alias], alias, opts.defaultRegistry);
            const peripheralLocation = opts.rootDependencies[alias];
            // Skipping linked packages
            if (!peripheralLocation) {
                return;
            }
            if ((yield symlink_dependency_1.default(peripheralLocation, opts.importerModulesDir, alias)).reused) {
                return;
            }
            const isDev = shrImporter.devDependencies && shrImporter.devDependencies[alias];
            const isOptional = shrImporter.optionalDependencies && shrImporter.optionalDependencies[alias];
            const relDepPath = dp.refToRelative(allDeps[alias], alias);
            if (relDepPath === null)
                return;
            const pkgSnapshot = shr.packages && shr.packages[relDepPath];
            if (!pkgSnapshot)
                return; // this won't ever happen. Just making typescript happy
            const pkgId = pkgSnapshot.id || depPath;
            const pkgInfo = pnpm_shrinkwrap_1.nameVerFromPkgSnapshot(relDepPath, pkgSnapshot);
            core_loggers_1.rootLogger.debug({
                added: {
                    dependencyType: isDev && 'dev' || isOptional && 'optional' || 'prod',
                    id: pkgId,
                    // latest: opts.outdatedPkgs[pkg.id],
                    name: alias,
                    realName: pkgInfo.name,
                    version: pkgInfo.version,
                },
                prefix: opts.prefix,
            });
        })));
    });
}
function shrinkwrapToDepGraph(shr, currentShrinkwrap, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const currentPackages = currentShrinkwrap && currentShrinkwrap.packages || {};
        const graph = {};
        let directDependenciesByImporterId = {};
        if (shr.packages) {
            const pkgSnapshotByLocation = {};
            for (const relDepPath of R.keys(shr.packages)) {
                if (currentPackages[relDepPath] && R.equals(currentPackages[relDepPath].dependencies, shr.packages[relDepPath].dependencies) &&
                    R.equals(currentPackages[relDepPath].optionalDependencies, shr.packages[relDepPath].optionalDependencies)) {
                    continue;
                }
                const depPath = dp.resolve(opts.defaultRegistry, relDepPath);
                const pkgSnapshot = shr.packages[relDepPath];
                const independent = opts.independentLeaves && pkgIsIndependent(pkgSnapshot);
                const resolution = pnpm_shrinkwrap_1.pkgSnapshotToResolution(relDepPath, pkgSnapshot, opts.defaultRegistry);
                // TODO: optimize. This info can be already returned by pkgSnapshotToResolution()
                const pkgName = pnpm_shrinkwrap_1.nameVerFromPkgSnapshot(relDepPath, pkgSnapshot).name;
                const pkgId = pkgSnapshot.id || depPath;
                core_loggers_1.progressLogger.debug({
                    pkgId,
                    status: 'resolving_content',
                });
                let fetchResponse = opts.storeController.fetchPackage({
                    force: false,
                    pkgId,
                    prefix: opts.prefix,
                    resolution,
                    verifyStoreIntegrity: opts.verifyStoreIntegrity,
                });
                if (fetchResponse instanceof Promise)
                    fetchResponse = yield fetchResponse;
                fetchResponse.fetchingFiles // tslint:disable-line
                    .then((fetchResult) => {
                    core_loggers_1.progressLogger.debug({
                        pkgId,
                        status: fetchResult.fromStore
                            ? 'found_in_store' : 'fetched',
                    });
                });
                const cache = !opts.force && (yield getCache(opts.storeController, opts.store, pkgId));
                const centralLocation = cache || path.join(fetchResponse.inStoreLocation, 'node_modules', pkgName);
                // NOTE: This code will not convert the depPath with peer deps correctly
                // Unfortunately, there is currently no way to tell if the last dir in the path is originally there or added to separate
                // the diferent peer dependency sets
                const modules = path.join(opts.virtualStoreDir, `.${pkgid_to_filename_1.default(depPath, opts.prefix)}`, 'node_modules');
                const peripheralLocation = !independent
                    ? path.join(modules, pkgName)
                    : centralLocation;
                graph[peripheralLocation] = {
                    centralLocation,
                    children: {},
                    fetchingFiles: fetchResponse.fetchingFiles,
                    finishing: fetchResponse.finishing,
                    hasBin: pkgSnapshot.hasBin === true,
                    hasBundledDependencies: !!pkgSnapshot.bundledDependencies,
                    independent,
                    isBuilt: !!cache,
                    modules,
                    optional: !!pkgSnapshot.optional,
                    optionalDependencies: new Set(R.keys(pkgSnapshot.optionalDependencies)),
                    peripheralLocation,
                    pkgId,
                    prepare: pkgSnapshot.prepare === true,
                    relDepPath: depPath,
                    requiresBuild: pkgSnapshot.requiresBuild === true,
                };
                pkgSnapshotByLocation[peripheralLocation] = pkgSnapshot;
            }
            const ctx = {
                force: opts.force,
                graph,
                independentLeaves: opts.independentLeaves,
                pkgSnapshotsByRelDepPaths: shr.packages,
                prefix: opts.prefix,
                registry: opts.defaultRegistry,
                store: opts.store,
                storeController: opts.storeController,
                virtualStoreDir: opts.virtualStoreDir,
            };
            for (const peripheralLocation of R.keys(graph)) {
                const pkgSnapshot = pkgSnapshotByLocation[peripheralLocation];
                const allDeps = Object.assign({}, pkgSnapshot.dependencies, pkgSnapshot.optionalDependencies);
                graph[peripheralLocation].children = yield getChildrenPaths(ctx, allDeps);
            }
            for (const importerId of opts.importerIds) {
                const shrImporter = shr.importers[importerId];
                const rootDeps = Object.assign({}, shrImporter.devDependencies, shrImporter.dependencies, shrImporter.optionalDependencies);
                directDependenciesByImporterId[importerId] = yield getChildrenPaths(ctx, rootDeps);
            }
        }
        return { graph, directDependenciesByImporterId };
    });
}
function getChildrenPaths(ctx, allDeps) {
    return __awaiter(this, void 0, void 0, function* () {
        const children = {};
        for (const alias of R.keys(allDeps)) {
            const childDepPath = dp.refToAbsolute(allDeps[alias], alias, ctx.registry);
            if (childDepPath === null) {
                children[alias] = path.resolve(ctx.prefix, allDeps[alias].substr(5));
                continue;
            }
            const childRelDepPath = dp.relative(ctx.registry, childDepPath);
            const childPkgSnapshot = ctx.pkgSnapshotsByRelDepPaths[childRelDepPath];
            if (ctx.graph[childDepPath]) {
                children[alias] = ctx.graph[childDepPath].peripheralLocation;
            }
            else if (ctx.independentLeaves && pkgIsIndependent(childPkgSnapshot)) {
                const pkgId = childPkgSnapshot.id || childDepPath;
                const cache = !ctx.force && (yield getCache(ctx.storeController, ctx.store, pkgId));
                const pkgName = pnpm_shrinkwrap_1.nameVerFromPkgSnapshot(childRelDepPath, childPkgSnapshot).name;
                const inStoreLocation = pkgid_to_filename_1.default(pkgId, ctx.prefix);
                children[alias] = cache || path.join(inStoreLocation, 'node_modules', pkgName);
            }
            else if (childPkgSnapshot) {
                const relDepPath = dp.relative(ctx.registry, childDepPath);
                const pkgName = pnpm_shrinkwrap_1.nameVerFromPkgSnapshot(relDepPath, childPkgSnapshot).name;
                children[alias] = path.join(ctx.virtualStoreDir, `.${pkgid_to_filename_1.default(childDepPath, ctx.prefix)}`, 'node_modules', pkgName);
            }
            else if (allDeps[alias].indexOf('file:') === 0) {
                children[alias] = path.resolve(ctx.prefix, allDeps[alias].substr(5));
            }
            else {
                throw new Error(`${childRelDepPath} not found in shrinkwrap.yaml`);
            }
        }
        return children;
    });
}
function getCache(store, storePath, pkgId) {
    return __awaiter(this, void 0, void 0, function* () {
        return (yield store.getCacheByEngine(storePath, pkgId))[constants_1.ENGINE_NAME];
    });
}
function pkgIsIndependent(pkgSnapshot) {
    return pkgSnapshot.dependencies === undefined && pkgSnapshot.optionalDependencies === undefined;
}
const limitLinking = pLimit(16);
function linkAllPkgs(storeController, depNodes, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        return Promise.all(depNodes.map((depNode) => __awaiter(this, void 0, void 0, function* () {
            const filesResponse = yield depNode.fetchingFiles;
            if (depNode.independent)
                return;
            return storeController.importPackage(depNode.centralLocation, depNode.peripheralLocation, {
                filesResponse,
                force: opts.force,
            });
        })));
    });
}
function linkAllBins(depGraph, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        return Promise.all(R.values(depGraph)
            .map((depNode) => limitLinking(() => __awaiter(this, void 0, void 0, function* () {
            const childrenToLink = opts.optional
                ? depNode.children
                : R.keys(depNode.children)
                    .reduce((nonOptionalChildren, childAlias) => {
                    if (!depNode.optionalDependencies.has(childAlias)) {
                        nonOptionalChildren[childAlias] = depNode.children[childAlias];
                    }
                    return nonOptionalChildren;
                }, {});
            const binPath = path.join(depNode.peripheralLocation, 'node_modules', '.bin');
            const pkgSnapshots = R.props(R.values(childrenToLink), depGraph);
            if (pkgSnapshots.indexOf(undefined) !== -1) { // tslint:disable-line
                yield link_bins_1.default(depNode.modules, binPath, { warn: opts.warn });
            }
            else {
                const pkgs = yield Promise.all(pkgSnapshots
                    .filter((dep) => dep.hasBin)
                    .map((dep) => __awaiter(this, void 0, void 0, function* () {
                    return ({
                        location: dep.peripheralLocation,
                        manifest: yield read_package_json_1.fromDir(dep.peripheralLocation),
                    });
                })));
                yield link_bins_1.linkBinsOfPackages(pkgs, binPath, { warn: opts.warn });
            }
            // link also the bundled dependencies` bins
            if (depNode.hasBundledDependencies) {
                const bundledModules = path.join(depNode.peripheralLocation, 'node_modules');
                yield link_bins_1.default(bundledModules, binPath, { warn: opts.warn });
            }
        }))));
    });
}
function linkAllModules(depGraph, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        return Promise.all(R.values(depGraph)
            .filter((depNode) => !depNode.independent)
            .map((depNode) => limitLinking(() => __awaiter(this, void 0, void 0, function* () {
            const childrenToLink = opts.optional
                ? depNode.children
                : R.keys(depNode.children)
                    .reduce((nonOptionalChildren, childAlias) => {
                    if (!depNode.optionalDependencies.has(childAlias)) {
                        nonOptionalChildren[childAlias] = depNode.children[childAlias];
                    }
                    return nonOptionalChildren;
                }, {});
            yield Promise.all(R.keys(childrenToLink)
                .map((alias) => __awaiter(this, void 0, void 0, function* () {
                // if (!pkg.installable && pkg.optional) return
                yield symlink_dependency_1.default(childrenToLink[alias], depNode.modules, alias);
            })));
        }))));
    });
}
//# sourceMappingURL=index.js.map