"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const core_loggers_1 = require("@pnpm/core-loggers");
const link_bins_1 = require("@pnpm/link-bins");
const lockfile_file_1 = require("@pnpm/lockfile-file");
const logger_1 = require("@pnpm/logger");
const modules_cleaner_1 = require("@pnpm/modules-cleaner");
const prune_lockfile_1 = require("@pnpm/prune-lockfile");
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 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");
async function link(linkFromPkgs, destModules, maybeOpts) {
    const reporter = maybeOpts && maybeOpts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    maybeOpts.saveProd = maybeOpts.saveProd === true;
    const opts = await options_1.extendOptions(maybeOpts);
    const ctx = await getContext_1.getContextForSingleImporter(opts);
    const importerId = lockfile_file_1.getLockfileImporterId(ctx.lockfileDirectory, opts.prefix);
    const oldLockfile = R.clone(ctx.currentLockfile);
    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 = await 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,
        };
        addLinkToLockfile(ctx.currentLockfile.importers[importerId], addLinkOpts);
        addLinkToLockfile(ctx.wantedLockfile.importers[importerId], addLinkOpts);
        linkedPkgs.push({
            alias: linkFromAlias || linkedPkg.name,
            path: linkFromPath,
            pkg: linkedPkg,
        });
    }
    const updatedCurrentLockfile = prune_lockfile_1.pruneSharedLockfile(ctx.currentLockfile, { defaultRegistry: opts.registries.default });
    const warn = (message) => logger_1.default.warn({ message, prefix: opts.prefix });
    const updatedWantedLockfile = prune_lockfile_1.pruneSharedLockfile(ctx.wantedLockfile, {
        defaultRegistry: opts.registries.default,
        warn,
    });
    await modules_cleaner_1.prune({
        importers: [
            {
                bin: opts.bin,
                hoistedAliases: ctx.hoistedAliases,
                id: importerId,
                modulesDir: ctx.modulesDir,
                prefix: opts.prefix,
                shamefullyFlatten: opts.shamefullyFlatten,
            },
        ],
        lockfileDirectory: opts.lockfileDirectory,
        newLockfile: updatedCurrentLockfile,
        oldLockfile,
        registries: ctx.registries,
        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);
        await 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');
    await 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 = await save_1.default(opts.prefix, specsToUpsert);
        for (const specToUpsert of specsToUpsert) {
            updatedWantedLockfile.importers[importerId].specifiers[specToUpsert.name] = getSpecFromPackageJson_1.default(newPkg, specToUpsert.name);
        }
    }
    const lockfileOpts = { forceSharedFormat: opts.forceSharedLockfile };
    if (opts.useLockfile) {
        await lockfile_file_1.writeLockfiles(ctx.lockfileDirectory, updatedWantedLockfile, updatedCurrentLockfile, lockfileOpts);
    }
    else {
        await lockfile_file_1.writeCurrentLockfile(ctx.lockfileDirectory, updatedCurrentLockfile, lockfileOpts);
    }
    core_loggers_1.summaryLogger.debug({ prefix: opts.prefix });
    if (reporter) {
        logger_1.streamParser.removeListener('data', reporter);
    }
}
exports.default = link;
function addLinkToLockfile(lockfileImporter, 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;
            lockfileImporter[depType] = lockfileImporter[depType] || {};
            lockfileImporter[depType][opts.linkedPkgName] = id;
        }
        else if (lockfileImporter[depType]) {
            delete lockfileImporter[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) {
        lockfileImporter.specifiers[opts.linkedPkgName] = availableSpec;
    }
    else {
        delete lockfileImporter.specifiers[opts.linkedPkgName];
    }
}
async function linkFromGlobal(pkgNames, linkTo, maybeOpts) {
    const reporter = maybeOpts && maybeOpts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    const opts = await options_1.extendOptions(maybeOpts);
    const globalPkgPath = pathAbsolute(maybeOpts.globalPrefix);
    const linkFromPkgs = pkgNames.map((pkgName) => path.join(globalPkgPath, 'node_modules', pkgName));
    await link(linkFromPkgs, path.join(linkTo, 'node_modules'), opts);
    if (reporter) {
        logger_1.streamParser.removeListener('data', reporter);
    }
}
exports.linkFromGlobal = linkFromGlobal;
async function linkToGlobal(linkFrom, maybeOpts) {
    const reporter = maybeOpts && maybeOpts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    maybeOpts.lockfileDirectory = maybeOpts.lockfileDirectory || maybeOpts.globalPrefix;
    const opts = await options_1.extendOptions(maybeOpts);
    const globalPkgPath = pathAbsolute(maybeOpts.globalPrefix);
    await 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