"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 link_bins_1 = require("@pnpm/link-bins");
const logger_1 = require("@pnpm/logger");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const utils_1 = require("@pnpm/utils");
const loadJsonFile = require("load-json-file");
const normalize = require("normalize-path");
const pLimit = require("p-limit");
const path = require("path");
const pathAbsolute = require("path-absolute");
const pnpm_shrinkwrap_1 = require("pnpm-shrinkwrap");
const R = require("ramda");
const symlinkDir = require("symlink-dir");
const getSpecFromPackageJson_1 = require("../getSpecFromPackageJson");
const readShrinkwrapFiles_1 = require("../readShrinkwrapFiles");
const save_1 = require("../save");
const extendInstallOptions_1 = require("./extendInstallOptions");
const install_1 = require("./install");
const getPref_1 = require("./utils/getPref");
const linkLogger = logger_1.default('link');
const installLimit = pLimit(4);
function link(linkFromPkgs, destModules, maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const reporter = maybeOpts && maybeOpts.reporter;
        if (reporter) {
            logger_1.streamParser.on('data', reporter);
        }
        maybeOpts.saveProd = maybeOpts.saveProd === true;
        const opts = yield extendInstallOptions_1.default(maybeOpts);
        if (!maybeOpts || !maybeOpts.skipInstall) {
            yield Promise.all(linkFromPkgs.map((prefix) => installLimit(() => install_1.install(Object.assign({}, opts, { bin: path.join(prefix, 'node_modules', '.bin'), global: false, prefix })))));
        }
        const shrFiles = yield readShrinkwrapFiles_1.default({
            force: opts.force,
            prefix: opts.prefix,
            registry: opts.registry,
            shrinkwrap: opts.shrinkwrap,
        });
        const oldShrinkwrap = R.clone(shrFiles.currentShrinkwrap);
        const pkg = (yield utils_1.safeReadPackage(path.join(opts.prefix, 'package.json'))) || undefined;
        if (pkg) {
            utils_1.packageJsonLogger.debug({
                initial: pkg,
                prefix: opts.prefix,
            });
        }
        const linkedPkgs = [];
        const specsToUpsert = [];
        const saveType = utils_1.getSaveType(opts);
        for (const linkFrom of linkFromPkgs) {
            const linkedPkg = yield loadJsonFile(path.join(linkFrom, 'package.json'));
            specsToUpsert.push({
                name: linkedPkg.name,
                pref: getPref_1.default(linkedPkg.name, linkedPkg.name, linkedPkg.version, {
                    saveExact: opts.saveExact,
                    savePrefix: opts.savePrefix,
                }),
                saveType: saveType,
            });
            const packagePath = normalize(path.relative(opts.prefix, linkFrom));
            const addLinkOpts = {
                linkedPkgName: linkedPkg.name,
                packagePath,
                pkg,
            };
            addLinkToShrinkwrap(shrFiles.currentShrinkwrap, addLinkOpts);
            addLinkToShrinkwrap(shrFiles.wantedShrinkwrap, addLinkOpts);
            linkedPkgs.push({ path: linkFrom, pkg: linkedPkg });
        }
        const updatedCurrentShrinkwrap = pnpm_shrinkwrap_1.prune(shrFiles.currentShrinkwrap);
        const updatedWantedShrinkwrap = pnpm_shrinkwrap_1.prune(shrFiles.wantedShrinkwrap);
        const modulesInfo = yield modules_yaml_1.read(destModules);
        yield utils_1.removeOrphanPackages({
            bin: opts.bin,
            hoistedAliases: modulesInfo && modulesInfo.hoistedAliases || {},
            newShrinkwrap: updatedCurrentShrinkwrap,
            oldShrinkwrap,
            prefix: opts.prefix,
            shamefullyFlatten: opts.shamefullyFlatten,
            storeController: opts.storeController,
        });
        // Linking should happen after removing orphans
        // Otherwise would've been removed
        for (const linkedPkg of linkedPkgs) {
            yield linkToModules(linkedPkg.pkg, linkedPkg.path, destModules, { saveType, prefix: opts.prefix });
        }
        const linkToBin = maybeOpts && maybeOpts.linkToBin || path.join(destModules, '.bin');
        yield link_bins_1.linkBinsOfPackages(linkedPkgs.map((p) => ({ manifest: p.pkg, location: p.path })), linkToBin);
        if (opts.saveDev || opts.saveProd || opts.saveOptional) {
            const newPkg = yield save_1.default(opts.prefix, specsToUpsert);
            for (const specToUpsert of specsToUpsert) {
                updatedWantedShrinkwrap.specifiers[specToUpsert.name] = getSpecFromPackageJson_1.default(newPkg, specToUpsert.name);
            }
        }
        if (opts.shrinkwrap) {
            yield pnpm_shrinkwrap_1.write(opts.prefix, updatedWantedShrinkwrap, updatedCurrentShrinkwrap);
        }
        else {
            yield pnpm_shrinkwrap_1.writeCurrentOnly(opts.prefix, updatedCurrentShrinkwrap);
        }
        utils_1.summaryLogger.info({ prefix: opts.prefix });
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
    });
}
exports.default = link;
function addLinkToShrinkwrap(shr, opts) {
    const id = `link:${opts.packagePath}`;
    let addedTo;
    for (const depType of utils_1.dependenciesTypes) {
        if (opts.pkg && opts.pkg[depType]) {
            addedTo = depType;
            shr[depType] = shr[depType] || {};
            shr[depType][opts.linkedPkgName] = id;
        }
        else if (shr[depType]) {
            delete shr[depType][opts.linkedPkgName];
        }
    }
    if (!addedTo) {
        shr.dependencies = shr.dependencies || {};
        shr.dependencies[opts.linkedPkgName] = id;
    }
    // package.json might not be available when linking to global
    if (!opts.pkg)
        return;
    const availableSpec = getSpecFromPackageJson_1.default(opts.pkg, opts.linkedPkgName);
    if (availableSpec) {
        shr.specifiers[opts.linkedPkgName] = availableSpec;
    }
    else {
        delete shr.specifiers[opts.linkedPkgName];
    }
}
const DEP_TYPE_BY_DEPS_FIELD_NAME = {
    dependencies: 'prod',
    devDependencies: 'dev',
    optionalDependencies: 'optional',
};
function linkToModules(pkg, linkFrom, modules, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const dest = path.join(modules, pkg.name);
        utils_1.rootLogger.debug({
            added: {
                dependencyType: opts.saveType && DEP_TYPE_BY_DEPS_FIELD_NAME[opts.saveType],
                linkedFrom: linkFrom,
                name: pkg.name,
                realName: pkg.name,
                version: pkg.version,
            },
            prefix: opts.prefix,
        });
        yield symlinkDir(linkFrom, dest);
    });
}
function linkFromGlobal(pkgNames, linkTo, 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);
        const globalPkgPath = pathAbsolute(maybeOpts.globalPrefix);
        const linkFromPkgs = pkgNames.map((pkgName) => path.join(globalPkgPath, 'node_modules', pkgName));
        yield link(linkFromPkgs, path.join(linkTo, 'node_modules'), opts);
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
    });
}
exports.linkFromGlobal = linkFromGlobal;
function linkToGlobal(linkFrom, 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);
        const globalPkgPath = pathAbsolute(maybeOpts.globalPrefix);
        yield link([linkFrom], path.join(globalPkgPath, 'node_modules'), Object.assign({}, opts, { linkToBin: maybeOpts.globalBin, prefix: maybeOpts.globalPrefix }));
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
    });
}
exports.linkToGlobal = linkToGlobal;
//# sourceMappingURL=link.js.map