"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 link_bins_1 = require("@pnpm/link-bins");
const logger_1 = require("@pnpm/logger");
const modules_cleaner_1 = require("@pnpm/modules-cleaner");
const symlink_dependency_1 = require("@pnpm/symlink-dependency");
const types_1 = require("@pnpm/types");
const utils_1 = require("@pnpm/utils");
const load_json_file_1 = 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 getContext_1 = require("../getContext");
const getSpecFromPackageJson_1 = require("../getSpecFromPackageJson");
const save_1 = require("../save");
const getPref_1 = require("../utils/getPref");
const options_1 = require("./options");
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 options_1.extendOptions(maybeOpts);
        const ctx = yield getContext_1.getContextForSingleImporter(opts);
        const importerId = pnpm_shrinkwrap_1.getImporterId(ctx.shrinkwrapDirectory, opts.prefix);
        const oldShrinkwrap = R.clone(ctx.currentShrinkwrap);
        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 load_json_file_1.default(path.join(linkFromPath, 'package.json'));
            specsToUpsert.push({
                name: linkedPkg.name,
                pref: getPref_1.default(linkedPkg.name, linkedPkg.name, linkedPkg.version, {
                    pinnedVersion: opts.pinnedVersion,
                }),
                saveType: (saveType || ctx.pkg && save_1.guessDependencyType(linkedPkg.name, ctx.pkg)),
            });
            const packagePath = normalize(path.relative(opts.prefix, linkFromPath));
            const addLinkOpts = {
                linkedPkgName: linkFromAlias || linkedPkg.name,
                packagePath,
                pkg: ctx.pkg,
            };
            addLinkToShrinkwrap(ctx.currentShrinkwrap.importers[importerId], addLinkOpts);
            addLinkToShrinkwrap(ctx.wantedShrinkwrap.importers[importerId], addLinkOpts);
            linkedPkgs.push({
                alias: linkFromAlias || linkedPkg.name,
                path: linkFromPath,
                pkg: linkedPkg,
            });
        }
        const updatedCurrentShrinkwrap = pnpm_shrinkwrap_1.pruneSharedShrinkwrap(ctx.currentShrinkwrap, { defaultRegistry: opts.registries.default });
        const warn = (message) => logger_1.default.warn({ message, prefix: opts.prefix });
        const updatedWantedShrinkwrap = pnpm_shrinkwrap_1.pruneSharedShrinkwrap(ctx.wantedShrinkwrap, {
            defaultRegistry: opts.registries.default,
            warn,
        });
        yield modules_cleaner_1.prune({
            importers: [
                {
                    bin: opts.bin,
                    hoistedAliases: ctx.hoistedAliases,
                    id: importerId,
                    modulesDir: ctx.modulesDir,
                    prefix: opts.prefix,
                    shamefullyFlatten: opts.shamefullyFlatten,
                },
            ],
            newShrinkwrap: updatedCurrentShrinkwrap,
            oldShrinkwrap,
            registries: ctx.registries,
            shrinkwrapDirectory: opts.shrinkwrapDirectory,
            storeController: opts.storeController,
            virtualStoreDir: ctx.virtualStoreDir,
        });
        // 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 symlink_dependency_1.symlinkDirectRootDependency(linkedPkg.path, destModules, linkedPkg.alias, {
                fromDependenciesField: stu && stu.saveType || saveType,
                linkedPackage: linkedPkg.pkg,
                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, {
            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.importers[importerId].specifiers[specToUpsert.name] = getSpecFromPackageJson_1.default(newPkg, specToUpsert.name);
            }
        }
        const shrinkwrapOpts = { forceSharedFormat: opts.forceSharedShrinkwrap };
        if (opts.shrinkwrap) {
            yield pnpm_shrinkwrap_1.write(ctx.shrinkwrapDirectory, updatedWantedShrinkwrap, updatedCurrentShrinkwrap, shrinkwrapOpts);
        }
        else {
            yield pnpm_shrinkwrap_1.writeCurrentOnly(ctx.shrinkwrapDirectory, updatedCurrentShrinkwrap, shrinkwrapOpts);
        }
        core_loggers_1.summaryLogger.debug({ prefix: opts.prefix });
        if (reporter) {
            logger_1.streamParser.removeListener('data', reporter);
        }
    });
}
exports.default = link;
function addLinkToShrinkwrap(shrImporter, opts) {
    const id = `link:${opts.packagePath}`;
    let addedTo;
    for (const depType of types_1.DEPENDENCIES_FIELDS) {
        if (!addedTo && opts.pkg && opts.pkg[depType] && opts.pkg[depType][opts.linkedPkgName]) {
            addedTo = depType;
            shrImporter[depType] = shrImporter[depType] || {};
            shrImporter[depType][opts.linkedPkgName] = id;
        }
        else if (shrImporter[depType]) {
            delete shrImporter[depType][opts.linkedPkgName];
        }
    }
    // 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) {
        shrImporter.specifiers[opts.linkedPkgName] = availableSpec;
    }
    else {
        delete shrImporter.specifiers[opts.linkedPkgName];
    }
}
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 options_1.extendOptions(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);
        }
        maybeOpts.shrinkwrapDirectory = maybeOpts.shrinkwrapDirectory || maybeOpts.globalPrefix;
        const opts = yield options_1.extendOptions(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=index.js.map