"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 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 getPref_1 = require("./utils/getPref");
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);
        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) {
            let linkFromPath;
            let linkFromAlias;
            if (typeof linkFrom === 'string') {
                linkFromPath = linkFrom;
            }
            else {
                linkFromPath = linkFrom.path;
                linkFromAlias = linkFrom.alias;
            }
            const linkedPkg = yield loadJsonFile(path.join(linkFromPath, '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 || pkg && save_1.guessDependencyType(linkedPkg.name, pkg)),
            });
            const packagePath = normalize(path.relative(opts.prefix, linkFromPath));
            const addLinkOpts = {
                linkedPkgName: linkFromAlias || linkedPkg.name,
                packagePath,
                pkg,
            };
            addLinkToShrinkwrap(shrFiles.currentShrinkwrap, addLinkOpts);
            addLinkToShrinkwrap(shrFiles.wantedShrinkwrap, addLinkOpts);
            linkedPkgs.push({
                alias: linkFromAlias || linkedPkg.name,
                path: linkFromPath,
                pkg: linkedPkg,
            });
        }
        const warn = (message) => logger_1.default.warn({ message, prefix: opts.prefix });
        const updatedCurrentShrinkwrap = pnpm_shrinkwrap_1.pruneWithoutPackageJson(shrFiles.currentShrinkwrap, warn);
        const updatedWantedShrinkwrap = pnpm_shrinkwrap_1.pruneWithoutPackageJson(shrFiles.wantedShrinkwrap, warn);
        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) {
            // TODO: cover with test that linking reports with correct dependency types
            const stu = specsToUpsert.find((s) => s.name === linkedPkg.pkg.name);
            yield linkToModules({
                alias: linkedPkg.alias,
                destModulesDir: destModules,
                packageDir: linkedPkg.path,
                pkg: linkedPkg.pkg,
                prefix: opts.prefix,
                saveType: stu && stu.saveType || saveType,
            });
        }
        const linkToBin = maybeOpts && maybeOpts.linkToBin || path.join(destModules, '.bin');
        yield link_bins_1.linkBinsOfPackages(linkedPkgs.map((p) => ({ manifest: p.pkg, location: p.path })), linkToBin, {
            warn: (message) => logger_1.default.warn({ message, prefix: opts.prefix }),
        });
        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.debug({ 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 (!addedTo && opts.pkg && opts.pkg[depType] && opts.pkg[depType][opts.linkedPkgName]) {
            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(opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const dest = path.join(opts.destModulesDir, opts.alias);
        const { reused } = yield symlinkDir(opts.packageDir, dest);
        if (reused)
            return; // if the link was already present, don't log
        utils_1.rootLogger.debug({
            added: {
                dependencyType: opts.saveType && DEP_TYPE_BY_DEPS_FIELD_NAME[opts.saveType],
                linkedFrom: opts.packageDir,
                name: opts.alias,
                realName: opts.pkg.name,
                version: opts.pkg.version,
            },
            prefix: opts.prefix,
        });
    });
}
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