"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("@pnpm/constants");
const core_loggers_1 = require("@pnpm/core-loggers");
const filter_lockfile_1 = require("@pnpm/filter-lockfile");
const hoist_1 = __importDefault(require("@pnpm/hoist"));
const logger_1 = __importDefault(require("@pnpm/logger"));
const modules_cleaner_1 = require("@pnpm/modules-cleaner");
const symlink_dependency_1 = __importStar(require("@pnpm/symlink-dependency"));
const path = require("path");
const fs = require("mz/fs");
const pLimit = require("p-limit");
const R = require("ramda");
const brokenModulesLogger = logger_1.default('_broken_node_modules');
async function linkPackages(projects, depGraph, opts) {
    var _a, _b, _c, _d, _e;
    let depNodes = R.values(depGraph).filter(({ depPath, id }) => {
        var _a;
        if (((_a = opts.wantedLockfile.packages) === null || _a === void 0 ? void 0 : _a[depPath]) && !opts.wantedLockfile.packages[depPath].optional) {
            opts.skipped.delete(depPath);
            return true;
        }
        if (opts.wantedToBeSkippedPackageIds.has(id)) {
            opts.skipped.add(depPath);
            return false;
        }
        opts.skipped.delete(depPath);
        return true;
    });
    if (!opts.include.dependencies) {
        depNodes = depNodes.filter(({ dev, optional }) => dev || optional);
    }
    if (!opts.include.devDependencies) {
        depNodes = depNodes.filter(({ optional, prod }) => prod || optional);
    }
    if (!opts.include.optionalDependencies) {
        depNodes = depNodes.filter(({ optional }) => !optional);
    }
    depGraph = R.fromPairs(depNodes.map((depNode) => [depNode.depPath, depNode]));
    const removedDepPaths = await modules_cleaner_1.prune(projects, {
        currentLockfile: opts.currentLockfile,
        hoistedDependencies: opts.hoistedDependencies,
        hoistedModulesDir: (_a = (opts.hoistPattern && opts.hoistedModulesDir)) !== null && _a !== void 0 ? _a : undefined,
        include: opts.include,
        lockfileDir: opts.lockfileDir,
        pruneStore: opts.pruneStore,
        publicHoistedModulesDir: (_b = (opts.publicHoistPattern && opts.rootModulesDir)) !== null && _b !== void 0 ? _b : undefined,
        registries: opts.registries,
        skipped: opts.skipped,
        storeController: opts.storeController,
        virtualStoreDir: opts.virtualStoreDir,
        wantedLockfile: opts.wantedLockfile,
    });
    core_loggers_1.stageLogger.debug({
        prefix: opts.lockfileDir,
        stage: 'importing_started',
    });
    const projectIds = projects.map(({ id }) => id);
    const filterOpts = {
        include: opts.include,
        registries: opts.registries,
        skipped: opts.skipped,
    };
    const newCurrentLockfile = filter_lockfile_1.filterLockfileByImporters(opts.wantedLockfile, projectIds, {
        ...filterOpts,
        failOnMissingDependencies: true,
        skipped: new Set(),
    });
    const newDepPaths = await linkNewPackages(filter_lockfile_1.filterLockfileByImporters(opts.currentLockfile, projectIds, {
        ...filterOpts,
        failOnMissingDependencies: false,
    }), newCurrentLockfile, depGraph, {
        force: opts.force,
        lockfileDir: opts.lockfileDir,
        optional: opts.include.optionalDependencies,
        sideEffectsCacheRead: opts.sideEffectsCacheRead,
        skipped: opts.skipped,
        storeController: opts.storeController,
        virtualStoreDir: opts.virtualStoreDir,
    });
    core_loggers_1.stageLogger.debug({
        prefix: opts.lockfileDir,
        stage: 'importing_done',
    });
    await Promise.all(projects.map(async ({ id, manifest, modulesDir, rootDir }) => {
        const deps = opts.dependenciesByProjectId[id];
        await Promise.all([
            ...Object.entries(deps)
                .map(([rootAlias, depPath]) => ({ rootAlias, depGraphNode: depGraph[depPath] }))
                .filter(({ depGraphNode }) => depGraphNode)
                .map(async ({ rootAlias, depGraphNode }) => {
                var _a, _b;
                const isDev = Boolean((_a = manifest.devDependencies) === null || _a === void 0 ? void 0 : _a[depGraphNode.name]);
                const isOptional = Boolean((_b = manifest.optionalDependencies) === null || _b === void 0 ? void 0 : _b[depGraphNode.name]);
                if (isDev && !opts.include.devDependencies ||
                    isOptional && !opts.include.optionalDependencies ||
                    !isDev && !isOptional && !opts.include.dependencies)
                    return;
                if ((await symlink_dependency_1.default(depGraphNode.dir, modulesDir, rootAlias)).reused)
                    return;
                core_loggers_1.rootLogger.debug({
                    added: {
                        dependencyType: isDev && 'dev' || isOptional && 'optional' || 'prod',
                        id: depGraphNode.id,
                        latest: opts.outdatedDependencies[depGraphNode.id],
                        name: rootAlias,
                        realName: depGraphNode.name,
                        version: depGraphNode.version,
                    },
                    prefix: rootDir,
                });
            }),
            ...opts.linkedDependenciesByProjectId[id].map((linkedDependency) => {
                const depLocation = resolvePath(rootDir, linkedDependency.resolution.directory);
                return symlink_dependency_1.symlinkDirectRootDependency(depLocation, modulesDir, linkedDependency.alias, {
                    fromDependenciesField: linkedDependency.dev && 'devDependencies' || linkedDependency.optional && 'optionalDependencies' || 'dependencies',
                    linkedPackage: linkedDependency,
                    prefix: rootDir,
                });
            }),
        ]);
    }));
    let currentLockfile;
    const allImportersIncluded = R.equals(projectIds.sort(), Object.keys(opts.wantedLockfile.importers).sort());
    if (opts.makePartialCurrentLockfile ||
        !allImportersIncluded) {
        const packages = (_c = opts.currentLockfile.packages) !== null && _c !== void 0 ? _c : {};
        if (opts.wantedLockfile.packages) {
            for (const depPath in opts.wantedLockfile.packages) { // eslint-disable-line:forin
                if (depGraph[depPath]) {
                    packages[depPath] = opts.wantedLockfile.packages[depPath];
                }
            }
        }
        const projects = projectIds.reduce((acc, projectId) => {
            acc[projectId] = opts.wantedLockfile.importers[projectId];
            return acc;
        }, opts.currentLockfile.importers);
        currentLockfile = filter_lockfile_1.filterLockfileByImporters({
            ...opts.wantedLockfile,
            importers: projects,
            packages,
        }, Object.keys(projects), {
            ...filterOpts,
            failOnMissingDependencies: false,
            skipped: new Set(),
        });
    }
    else if (opts.include.dependencies &&
        opts.include.devDependencies &&
        opts.include.optionalDependencies &&
        opts.skipped.size === 0) {
        currentLockfile = opts.wantedLockfile;
    }
    else {
        currentLockfile = newCurrentLockfile;
    }
    let newHoistedDependencies;
    if ((opts.hoistPattern != null || opts.publicHoistPattern != null) && (newDepPaths.length > 0 || removedDepPaths.size > 0)) {
        newHoistedDependencies = await hoist_1.default({
            lockfile: currentLockfile,
            lockfileDir: opts.lockfileDir,
            privateHoistedModulesDir: opts.hoistedModulesDir,
            privateHoistPattern: (_d = opts.hoistPattern) !== null && _d !== void 0 ? _d : [],
            publicHoistedModulesDir: opts.rootModulesDir,
            publicHoistPattern: (_e = opts.publicHoistPattern) !== null && _e !== void 0 ? _e : [],
            virtualStoreDir: opts.virtualStoreDir,
        });
    }
    else {
        newHoistedDependencies = {};
    }
    return {
        currentLockfile,
        newDepPaths,
        newHoistedDependencies,
        removedDepPaths,
    };
}
exports.default = linkPackages;
const isAbsolutePath = /^[/]|^[A-Za-z]:/;
// This function is copied from @pnpm/local-resolver
function resolvePath(where, spec) {
    if (isAbsolutePath.test(spec))
        return spec;
    return path.resolve(where, spec);
}
async function linkNewPackages(currentLockfile, wantedLockfile, depGraph, opts) {
    const wantedRelDepPaths = R.difference(R.keys(wantedLockfile.packages), Array.from(opts.skipped));
    let newDepPathsSet;
    if (opts.force) {
        newDepPathsSet = new Set(wantedRelDepPaths
            // when installing a new package, not all the nodes are analyzed
            // just skip the ones that are in the lockfile but were not analyzed
            .filter((depPath) => depGraph[depPath]));
    }
    else {
        newDepPathsSet = await selectNewFromWantedDeps(wantedRelDepPaths, currentLockfile, depGraph);
    }
    core_loggers_1.statsLogger.debug({
        added: newDepPathsSet.size,
        prefix: opts.lockfileDir,
    });
    const existingWithUpdatedDeps = [];
    if (!opts.force && currentLockfile.packages && wantedLockfile.packages) {
        // add subdependencies that have been updated
        // TODO: no need to relink everything. Can be relinked only what was changed
        for (const depPath of wantedRelDepPaths) {
            if (currentLockfile.packages[depPath] &&
                (!R.equals(currentLockfile.packages[depPath].dependencies, wantedLockfile.packages[depPath].dependencies) ||
                    !R.equals(currentLockfile.packages[depPath].optionalDependencies, wantedLockfile.packages[depPath].optionalDependencies))) {
                // TODO: come up with a test that triggers the usecase of depGraph[depPath] undefined
                // see related issue: https://github.com/pnpm/pnpm/issues/870
                if (depGraph[depPath] && !newDepPathsSet.has(depPath)) {
                    existingWithUpdatedDeps.push(depGraph[depPath]);
                }
            }
        }
    }
    if (!newDepPathsSet.size && !existingWithUpdatedDeps.length)
        return [];
    const newDepPaths = Array.from(newDepPathsSet);
    const newPkgs = R.props(newDepPaths, depGraph);
    await Promise.all(newPkgs.map((depNode) => fs.mkdir(depNode.modules, { recursive: true })));
    await Promise.all([
        linkAllModules(newPkgs, depGraph, {
            lockfileDir: opts.lockfileDir,
            optional: opts.optional,
        }),
        linkAllModules(existingWithUpdatedDeps, depGraph, {
            lockfileDir: opts.lockfileDir,
            optional: opts.optional,
        }),
        linkAllPkgs(opts.storeController, newPkgs, {
            force: opts.force,
            lockfileDir: opts.lockfileDir,
            targetEngine: opts.sideEffectsCacheRead && constants_1.ENGINE_NAME || undefined,
        }),
    ]);
    return newDepPaths;
}
async function selectNewFromWantedDeps(wantedRelDepPaths, currentLockfile, depGraph) {
    var _a;
    const newDeps = new Set();
    const prevDeps = (_a = currentLockfile.packages) !== null && _a !== void 0 ? _a : {};
    await Promise.all(wantedRelDepPaths.map(async (depPath) => {
        const depNode = depGraph[depPath];
        if (!depNode)
            return;
        const prevDep = prevDeps[depPath];
        if (prevDep &&
            depNode.resolution['integrity'] === prevDep.resolution['integrity']) {
            if (await fs.exists(depNode.dir)) {
                return;
            }
            brokenModulesLogger.debug({
                missing: depNode.dir,
            });
        }
        newDeps.add(depPath);
    }));
    return newDeps;
}
const limitLinking = pLimit(16);
function linkAllPkgs(storeController, depNodes, opts) {
    return Promise.all(depNodes.map(async (depNode) => {
        const filesResponse = await depNode.fetchingFiles();
        const { importMethod, isBuilt } = await storeController.importPackage(depNode.dir, {
            filesResponse,
            force: opts.force,
            targetEngine: opts.targetEngine,
        });
        if (importMethod) {
            core_loggers_1.progressLogger.debug({
                method: importMethod,
                requester: opts.lockfileDir,
                status: 'imported',
                to: depNode.dir,
            });
        }
        depNode.isBuilt = isBuilt;
    }));
}
function linkAllModules(depNodes, depGraph, opts) {
    return Promise.all(depNodes
        .map(async ({ children, optionalDependencies, name, modules }) => {
        const childrenToLink = opts.optional
            ? children
            : Object.keys(children)
                .reduce((nonOptionalChildren, childAlias) => {
                if (!optionalDependencies.has(childAlias)) {
                    nonOptionalChildren[childAlias] = children[childAlias];
                }
                return nonOptionalChildren;
            }, {});
        await Promise.all(Object.keys(childrenToLink)
            .map(async (childAlias) => {
            if (childrenToLink[childAlias].startsWith('link:')) {
                await limitLinking(() => symlink_dependency_1.default(path.resolve(opts.lockfileDir, childrenToLink[childAlias].substr(5)), modules, childAlias));
                return;
            }
            const pkg = depGraph[childrenToLink[childAlias]];
            if (!pkg || !pkg.installable && pkg.optional)
                return;
            if (childAlias === name) {
                logger_1.default.warn({
                    message: `Cannot link dependency with name ${childAlias} to ${modules}. Dependency's name should differ from the parent's name.`,
                    prefix: opts.lockfileDir,
                });
                return;
            }
            await limitLinking(() => symlink_dependency_1.default(pkg.dir, modules, childAlias));
        }));
    }));
}
//# sourceMappingURL=link.js.map