"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("@pnpm/constants");
const core_loggers_1 = require("@pnpm/core-loggers");
const lifecycle_1 = require("@pnpm/lifecycle");
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const logger_1 = require("@pnpm/logger");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const pkgid_to_filename_1 = require("@pnpm/pkgid-to-filename");
const npa = require("@zkochan/npm-package-arg");
const dp = require("dependency-path");
const graphSequencer = require("graph-sequencer");
const path = require("path");
const R = require("ramda");
const run_groups_1 = require("run-groups");
const semver = require("semver");
const getContext_1 = require("../getContext");
const extendRebuildOptions_1 = require("./extendRebuildOptions");
function findPackages(packages, searched, opts) {
    return R.keys(packages)
        .filter((relativeDepPath) => {
        const pkgLockfile = packages[relativeDepPath];
        const pkgInfo = lockfile_utils_1.nameVerFromPkgSnapshot(relativeDepPath, pkgLockfile);
        if (!pkgInfo.name) {
            logger_1.default.warn({
                message: `Skipping ${relativeDepPath} because cannot get the package name from ${constants_1.WANTED_LOCKFILE}.
            Try to run run \`pnpm update --depth 100\` to create a new ${constants_1.WANTED_LOCKFILE} with all the necessary info.`,
                prefix: opts.prefix,
            });
            return false;
        }
        return matches(searched, pkgInfo);
    });
}
// TODO: move this logic to separate package as this is also used in dependencies-hierarchy
function matches(searched, pkg) {
    return searched.some((searchedPkg) => {
        if (typeof searchedPkg === 'string') {
            return pkg.name === searchedPkg;
        }
        return searchedPkg.name === pkg.name && !!pkg.version &&
            semver.satisfies(pkg.version, searchedPkg.range);
    });
}
async function rebuildPkgs(importers, pkgSpecs, maybeOpts) {
    const reporter = maybeOpts && maybeOpts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    const opts = await extendRebuildOptions_1.default(maybeOpts);
    const ctx = await getContext_1.default(importers, opts);
    if (!ctx.currentLockfile || !ctx.currentLockfile.packages)
        return;
    const packages = ctx.currentLockfile.packages;
    const searched = pkgSpecs.map((arg) => {
        const parsed = npa(arg);
        if (parsed.raw === parsed.name) {
            return parsed.name;
        }
        if (parsed.type !== 'version' && parsed.type !== 'range') {
            throw new Error(`Invalid argument - ${arg}. Rebuild can only select by version or range`);
        }
        return {
            name: parsed.name,
            range: parsed.fetchSpec,
        };
    });
    let pkgs = [];
    for (const importer of importers) {
        pkgs = [
            ...pkgs,
            ...findPackages(packages, searched, { prefix: importer.prefix }),
        ];
    }
    await _rebuild(new Set(pkgs), ctx.virtualStoreDir, ctx.currentLockfile, ctx.importers.map((importer) => importer.id), opts);
}
exports.rebuildPkgs = rebuildPkgs;
async function rebuild(importers, maybeOpts) {
    const reporter = maybeOpts && maybeOpts.reporter;
    if (reporter) {
        logger_1.streamParser.on('data', reporter);
    }
    const opts = await extendRebuildOptions_1.default(maybeOpts);
    const ctx = await getContext_1.default(importers, opts);
    let idsToRebuild = [];
    if (opts.pending) {
        idsToRebuild = ctx.pendingBuilds;
    }
    else if (ctx.currentLockfile && ctx.currentLockfile.packages) {
        idsToRebuild = R.keys(ctx.currentLockfile.packages);
    }
    else {
        return;
    }
    if (idsToRebuild.length === 0)
        return;
    const pkgsThatWereRebuilt = await _rebuild(new Set(idsToRebuild), ctx.virtualStoreDir, ctx.currentLockfile, ctx.importers.map((importer) => importer.id), opts);
    ctx.pendingBuilds = ctx.pendingBuilds.filter((relDepPath) => !pkgsThatWereRebuilt.has(relDepPath));
    const scriptsOpts = {
        rawNpmConfig: opts.rawNpmConfig,
        unsafePerm: opts.unsafePerm || false,
    };
    await lifecycle_1.runLifecycleHooksConcurrently(['preinstall', 'install', 'postinstall', 'prepublish', 'prepare'], ctx.importers, opts.childConcurrency || 5, scriptsOpts);
    for (const importer of ctx.importers) {
        if (importer.pkg && importer.pkg.scripts && (!opts.pending || ctx.pendingBuilds.indexOf(importer.id) !== -1)) {
            ctx.pendingBuilds.splice(ctx.pendingBuilds.indexOf(importer.id), 1);
        }
    }
    await modules_yaml_1.write(ctx.virtualStoreDir, Object.assign({}, ctx.modulesFile, { importers: Object.assign({}, ctx.modulesFile && ctx.modulesFile.importers, ctx.importers.reduce((acc, importer) => {
            acc[importer.id] = {
                hoistedAliases: importer.hoistedAliases,
                shamefullyFlatten: importer.shamefullyFlatten,
            };
            return acc;
        }, {})), included: ctx.include, independentLeaves: opts.independentLeaves, layoutVersion: constants_1.LAYOUT_VERSION, packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`, pendingBuilds: ctx.pendingBuilds, registries: ctx.registries, skipped: Array.from(ctx.skipped), store: ctx.storePath }));
}
exports.rebuild = rebuild;
function getSubgraphToBuild(pkgSnapshots, entryNodes, nodesToBuildAndTransitive, walked, opts) {
    let currentShouldBeBuilt = false;
    for (const depPath of entryNodes) {
        if (nodesToBuildAndTransitive.has(depPath)) {
            currentShouldBeBuilt = true;
        }
        if (walked.has(depPath))
            continue;
        walked.add(depPath);
        const pkgSnapshot = pkgSnapshots[depPath];
        if (!pkgSnapshot) {
            if (depPath.startsWith('link:'))
                continue;
            // It might make sense to fail if the depPath is not in the skipped list from .modules.yaml
            // However, the skipped list currently contains package IDs, not dep paths.
            logger_1.default.debug({ message: `No entry for "${depPath}" in ${constants_1.WANTED_LOCKFILE}` });
            continue;
        }
        const nextEntryNodes = R.toPairs(Object.assign({}, pkgSnapshot.dependencies, (opts.optional && pkgSnapshot.optionalDependencies || {})))
            .map((pair) => dp.refToRelative(pair[1], pair[0]))
            .filter((nodeId) => nodeId !== null);
        const childShouldBeBuilt = getSubgraphToBuild(pkgSnapshots, nextEntryNodes, nodesToBuildAndTransitive, walked, opts)
            || opts.pkgsToRebuild.has(depPath);
        if (childShouldBeBuilt) {
            nodesToBuildAndTransitive.add(depPath);
            currentShouldBeBuilt = true;
        }
    }
    return currentShouldBeBuilt;
}
async function _rebuild(pkgsToRebuild, modules, lockfile, importerIds, opts) {
    const pkgsThatWereRebuilt = new Set();
    const graph = new Map();
    const pkgSnapshots = lockfile.packages || {};
    const entryNodes = [];
    importerIds.forEach((importerId) => {
        const lockfileImporter = lockfile.importers[importerId];
        R.toPairs(Object.assign({}, (opts.development && lockfileImporter.devDependencies || {}), (opts.production && lockfileImporter.dependencies || {}), (opts.optional && lockfileImporter.optionalDependencies || {})))
            .map((pair) => dp.refToRelative(pair[1], pair[0]))
            .filter((nodeId) => nodeId !== null)
            .forEach((relDepPath) => {
            entryNodes.push(relDepPath);
        });
    });
    const nodesToBuildAndTransitive = new Set();
    getSubgraphToBuild(pkgSnapshots, entryNodes, nodesToBuildAndTransitive, new Set(), { optional: opts.optional === true, pkgsToRebuild });
    const nodesToBuildAndTransitiveArray = Array.from(nodesToBuildAndTransitive);
    for (const relDepPath of nodesToBuildAndTransitiveArray) {
        const pkgSnapshot = pkgSnapshots[relDepPath];
        graph.set(relDepPath, R.toPairs(Object.assign({}, pkgSnapshot.dependencies, pkgSnapshot.optionalDependencies))
            .map((pair) => dp.refToRelative(pair[1], pair[0]))
            .filter((childRelDepPath) => nodesToBuildAndTransitive.has(childRelDepPath)));
    }
    const graphSequencerResult = graphSequencer({
        graph,
        groups: [nodesToBuildAndTransitiveArray],
    });
    const chunks = graphSequencerResult.chunks;
    const groups = chunks.map((chunk) => chunk.filter((relDepPath) => pkgsToRebuild.has(relDepPath)).map((relDepPath) => async () => {
        const pkgSnapshot = pkgSnapshots[relDepPath];
        const depPath = dp.resolve(opts.registries, relDepPath);
        const pkgInfo = lockfile_utils_1.nameVerFromPkgSnapshot(relDepPath, pkgSnapshot);
        const independent = opts.independentLeaves && lockfile_utils_1.packageIsIndependent(pkgSnapshot);
        const pkgRoot = !independent
            ? path.join(modules, `.${pkgid_to_filename_1.default(depPath, opts.lockfileDirectory)}`, 'node_modules', pkgInfo.name)
            : await (async () => {
                const { directory } = await opts.storeController.getPackageLocation(pkgSnapshot.id || depPath, pkgInfo.name, {
                    lockfileDirectory: opts.lockfileDirectory,
                    targetEngine: opts.sideEffectsCacheRead && !opts.force && constants_1.ENGINE_NAME || undefined,
                });
                return directory;
            })();
        try {
            await lifecycle_1.runPostinstallHooks({
                depPath,
                optional: pkgSnapshot.optional === true,
                pkgRoot,
                prepare: pkgSnapshot.prepare,
                rawNpmConfig: opts.rawNpmConfig,
                rootNodeModulesDir: modules,
                unsafePerm: opts.unsafePerm || false,
            });
            pkgsThatWereRebuilt.add(relDepPath);
        }
        catch (err) {
            if (pkgSnapshot.optional) {
                // TODO: add parents field to the log
                core_loggers_1.skippedOptionalDependencyLogger.debug({
                    details: err.toString(),
                    package: {
                        id: pkgSnapshot.id || depPath,
                        name: pkgInfo.name,
                        version: pkgInfo.version,
                    },
                    prefix: opts.prefix,
                    reason: 'build_failure',
                });
                return;
            }
            throw err;
        }
    }));
    await run_groups_1.default(opts.childConcurrency || 5, groups);
    return pkgsThatWereRebuilt;
}
//# sourceMappingURL=index.js.map