"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 });
exports.rebuild = exports.rebuildPkgs = void 0;
const constants_1 = require("@pnpm/constants");
const core_loggers_1 = require("@pnpm/core-loggers");
const get_context_1 = __importDefault(require("@pnpm/get-context"));
const lifecycle_1 = require("@pnpm/lifecycle");
const link_bins_1 = __importDefault(require("@pnpm/link-bins"));
const lockfile_utils_1 = require("@pnpm/lockfile-utils");
const lockfile_walker_1 = __importDefault(require("@pnpm/lockfile-walker"));
const logger_1 = __importStar(require("@pnpm/logger"));
const modules_yaml_1 = require("@pnpm/modules-yaml");
const pkgid_to_filename_1 = __importDefault(require("@pnpm/pkgid-to-filename"));
const dp = __importStar(require("dependency-path"));
const run_groups_1 = __importDefault(require("run-groups"));
const extendRebuildOptions_1 = __importDefault(require("./extendRebuildOptions"));
const path = require("path");
const npa = require("@zkochan/npm-package-arg");
const graphSequencer = require("graph-sequencer");
const pLimit = require("p-limit");
const R = require("ramda");
const semver = require("semver");
function findPackages(packages, searched, opts) {
    return Object.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, manifest) {
    return searched.some((searchedPkg) => {
        if (typeof searchedPkg === 'string') {
            return manifest.name === searchedPkg;
        }
        return searchedPkg.name === manifest.name && !!manifest.version &&
            semver.satisfies(manifest.version, searchedPkg.range);
    });
}
async function rebuildPkgs(projects, pkgSpecs, maybeOpts) {
    const reporter = maybeOpts === null || maybeOpts === void 0 ? void 0 : maybeOpts.reporter;
    if (reporter && typeof reporter === 'function') {
        logger_1.streamParser.on('data', reporter);
    }
    const opts = await extendRebuildOptions_1.default(maybeOpts);
    const ctx = await get_context_1.default(projects, opts);
    if (!ctx.currentLockfile || !ctx.currentLockfile.packages)
        return;
    const packages = ctx.currentLockfile.packages;
    const searched = pkgSpecs.map((arg) => {
        const { fetchSpec, name, raw, type } = npa(arg);
        if (raw === name) {
            return name;
        }
        if (type !== 'version' && type !== 'range') {
            throw new Error(`Invalid argument - ${arg}. Rebuild can only select by version or range`);
        }
        return {
            name,
            range: fetchSpec,
        };
    });
    let pkgs = [];
    for (const { rootDir } of projects) {
        pkgs = [
            ...pkgs,
            ...findPackages(packages, searched, { prefix: rootDir }),
        ];
    }
    await _rebuild({
        pkgsToRebuild: new Set(pkgs),
        ...ctx,
    }, opts);
}
exports.rebuildPkgs = rebuildPkgs;
async function rebuild(projects, maybeOpts) {
    var _a;
    const reporter = maybeOpts === null || maybeOpts === void 0 ? void 0 : maybeOpts.reporter;
    if (reporter && typeof reporter === 'function') {
        logger_1.streamParser.on('data', reporter);
    }
    const opts = await extendRebuildOptions_1.default(maybeOpts);
    const ctx = await get_context_1.default(projects, opts);
    let idsToRebuild = [];
    if (opts.pending) {
        idsToRebuild = ctx.pendingBuilds;
    }
    else if ((_a = ctx.currentLockfile) === null || _a === void 0 ? void 0 : _a.packages) {
        idsToRebuild = Object.keys(ctx.currentLockfile.packages);
    }
    const pkgsThatWereRebuilt = await _rebuild({
        pkgsToRebuild: new Set(idsToRebuild),
        ...ctx,
    }, opts);
    ctx.pendingBuilds = ctx.pendingBuilds.filter((depPath) => !pkgsThatWereRebuilt.has(depPath));
    const scriptsOpts = {
        extraBinPaths: ctx.extraBinPaths,
        rawConfig: opts.rawConfig,
        shellEmulator: opts.shellEmulator,
        unsafePerm: opts.unsafePerm || false,
    };
    await lifecycle_1.runLifecycleHooksConcurrently(['preinstall', 'install', 'postinstall', 'prepublish', 'prepare'], ctx.projects, opts.childConcurrency || 5, scriptsOpts);
    for (const { id, manifest } of ctx.projects) {
        if ((manifest === null || manifest === void 0 ? void 0 : manifest.scripts) && (!opts.pending || ctx.pendingBuilds.includes(id))) {
            ctx.pendingBuilds.splice(ctx.pendingBuilds.indexOf(id), 1);
        }
    }
    await modules_yaml_1.write(ctx.rootModulesDir, {
        ...ctx.modulesFile,
        hoistedDependencies: ctx.hoistedDependencies,
        hoistPattern: ctx.hoistPattern,
        included: ctx.include,
        layoutVersion: constants_1.LAYOUT_VERSION,
        packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`,
        pendingBuilds: ctx.pendingBuilds,
        publicHoistPattern: ctx.publicHoistPattern,
        registries: ctx.registries,
        skipped: Array.from(ctx.skipped),
        storeDir: ctx.storeDir,
        virtualStoreDir: ctx.virtualStoreDir,
    });
}
exports.rebuild = rebuild;
function getSubgraphToBuild(step, nodesToBuildAndTransitive, opts) {
    let currentShouldBeBuilt = false;
    for (const { depPath, next } of step.dependencies) {
        if (nodesToBuildAndTransitive.has(depPath)) {
            currentShouldBeBuilt = true;
        }
        const childShouldBeBuilt = getSubgraphToBuild(next(), nodesToBuildAndTransitive, opts) ||
            opts.pkgsToRebuild.has(depPath);
        if (childShouldBeBuilt) {
            nodesToBuildAndTransitive.add(depPath);
            currentShouldBeBuilt = true;
        }
    }
    for (const depPath of step.missing) {
        // 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}` });
    }
    return currentShouldBeBuilt;
}
const limitLinking = pLimit(16);
async function _rebuild(ctx, opts) {
    var _a;
    const pkgsThatWereRebuilt = new Set();
    const graph = new Map();
    const pkgSnapshots = (_a = ctx.currentLockfile.packages) !== null && _a !== void 0 ? _a : {};
    const nodesToBuildAndTransitive = new Set();
    getSubgraphToBuild(lockfile_walker_1.default(ctx.currentLockfile, ctx.projects.map(({ id }) => id), {
        include: {
            dependencies: opts.production,
            devDependencies: opts.development,
            optionalDependencies: opts.optional,
        },
    }).step, nodesToBuildAndTransitive, { pkgsToRebuild: ctx.pkgsToRebuild });
    const nodesToBuildAndTransitiveArray = Array.from(nodesToBuildAndTransitive);
    for (const depPath of nodesToBuildAndTransitiveArray) {
        const pkgSnapshot = pkgSnapshots[depPath];
        graph.set(depPath, R.toPairs({ ...pkgSnapshot.dependencies, ...pkgSnapshot.optionalDependencies })
            .map(([pkgName, reference]) => dp.refToRelative(reference, pkgName))
            .filter((childRelDepPath) => childRelDepPath && nodesToBuildAndTransitive.has(childRelDepPath)));
    }
    const graphSequencerResult = graphSequencer({
        graph,
        groups: [nodesToBuildAndTransitiveArray],
    });
    const chunks = graphSequencerResult.chunks;
    const warn = (message) => logger_1.default.info({ message, prefix: opts.dir });
    const groups = chunks.map((chunk) => chunk.filter((depPath) => ctx.pkgsToRebuild.has(depPath)).map((depPath) => async () => {
        var _a;
        const pkgSnapshot = pkgSnapshots[depPath];
        const pkgInfo = lockfile_utils_1.nameVerFromPkgSnapshot(depPath, pkgSnapshot);
        const pkgRoot = path.join(ctx.virtualStoreDir, pkgid_to_filename_1.default(depPath, opts.lockfileDir), 'node_modules', pkgInfo.name);
        try {
            const modules = path.join(ctx.virtualStoreDir, pkgid_to_filename_1.default(depPath, opts.lockfileDir), 'node_modules');
            const binPath = path.join(pkgRoot, 'node_modules', '.bin');
            await link_bins_1.default(modules, binPath, { warn });
            await lifecycle_1.runPostinstallHooks({
                depPath,
                extraBinPaths: ctx.extraBinPaths,
                optional: pkgSnapshot.optional === true,
                pkgRoot,
                prepare: pkgSnapshot.prepare,
                rawConfig: opts.rawConfig,
                rootModulesDir: ctx.rootModulesDir,
                shellEmulator: opts.shellEmulator,
                unsafePerm: opts.unsafePerm || false,
            });
            pkgsThatWereRebuilt.add(depPath);
        }
        catch (err) {
            if (pkgSnapshot.optional) {
                // TODO: add parents field to the log
                core_loggers_1.skippedOptionalDependencyLogger.debug({
                    details: err.toString(),
                    package: {
                        id: (_a = pkgSnapshot.id) !== null && _a !== void 0 ? _a : depPath,
                        name: pkgInfo.name,
                        version: pkgInfo.version,
                    },
                    prefix: opts.dir,
                    reason: 'build_failure',
                });
                return;
            }
            throw err;
        }
    }));
    await run_groups_1.default(opts.childConcurrency || 5, groups);
    // It may be optimized because some bins were already linked before running lifecycle scripts
    await Promise.all(Object
        .keys(pkgSnapshots)
        .filter((depPath) => !lockfile_utils_1.packageIsIndependent(pkgSnapshots[depPath]))
        .map((depPath) => limitLinking(() => {
        const pkgSnapshot = pkgSnapshots[depPath];
        const pkgInfo = lockfile_utils_1.nameVerFromPkgSnapshot(depPath, pkgSnapshot);
        const modules = path.join(ctx.virtualStoreDir, pkgid_to_filename_1.default(depPath, opts.lockfileDir), 'node_modules');
        const binPath = path.join(modules, pkgInfo.name, 'node_modules', '.bin');
        return link_bins_1.default(modules, binPath, { warn });
    })));
    await Promise.all(ctx.projects.map(({ rootDir }) => limitLinking(() => {
        const modules = path.join(rootDir, 'node_modules');
        const binPath = path.join(modules, '.bin');
        return link_bins_1.default(modules, binPath, {
            allowExoticManifests: true,
            warn,
        });
    })));
    return pkgsThatWereRebuilt;
}
//# sourceMappingURL=index.js.map