"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 lifecycle_1 = require("@pnpm/lifecycle");
const logger_1 = require("@pnpm/logger");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const npa = require("@zkochan/npm-package-arg");
const dp = require("dependency-path");
const graphSequencer = require("graph-sequencer");
const pLimit = require("p-limit");
const path = require("path");
const pnpm_shrinkwrap_1 = require("pnpm-shrinkwrap");
const R = require("ramda");
const semver = require("semver");
const constants_1 = require("../constants");
const getContext_1 = require("../getContext");
const extendRebuildOptions_1 = require("./extendRebuildOptions");
function findPackages(packages, searched, opts) {
    return R.keys(packages)
        .filter((relativeDepPath) => {
        const pkgShr = packages[relativeDepPath];
        const pkgInfo = pnpm_shrinkwrap_1.nameVerFromPkgSnapshot(relativeDepPath, pkgShr);
        if (!pkgInfo.name) {
            logger_1.default.warn({
                message: `Skipping ${relativeDepPath} because cannot get the package name from shrinkwrap.yaml.
            Try to run run \`pnpm update --depth 100\` to create a new shrinkwrap.yaml 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);
    });
}
function rebuildPkgs(pkgSpecs, 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 extendRebuildOptions_1.default(maybeOpts);
        const ctx = yield getContext_1.getContextForSingleImporter(opts);
        if (!ctx.currentShrinkwrap || !ctx.currentShrinkwrap.packages)
            return;
        const packages = ctx.currentShrinkwrap.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,
            };
        });
        const pkgs = findPackages(packages, searched, { prefix: ctx.prefix });
        yield _rebuild(new Set(pkgs), ctx.virtualStoreDir, ctx.currentShrinkwrap, ctx.importerId, opts);
    });
}
exports.rebuildPkgs = rebuildPkgs;
function rebuild(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 extendRebuildOptions_1.default(maybeOpts);
        const ctx = yield getContext_1.getContextForSingleImporter(opts);
        let idsToRebuild = [];
        if (opts.pending) {
            idsToRebuild = ctx.pendingBuilds;
        }
        else if (ctx.currentShrinkwrap && ctx.currentShrinkwrap.packages) {
            idsToRebuild = R.keys(ctx.currentShrinkwrap.packages);
        }
        else {
            return;
        }
        if (idsToRebuild.length === 0)
            return;
        const pkgsThatWereRebuilt = yield _rebuild(new Set(idsToRebuild), ctx.virtualStoreDir, ctx.currentShrinkwrap, ctx.importerId, opts);
        ctx.pendingBuilds = ctx.pendingBuilds.filter((relDepPath) => !pkgsThatWereRebuilt.has(relDepPath));
        if (ctx.pkg && ctx.pkg.scripts && (!opts.pending || ctx.pendingBuilds.indexOf(ctx.importerId) !== -1)) {
            yield runLifecycleHooksInDir(opts.prefix, ctx.pkg, {
                rawNpmConfig: opts.rawNpmConfig,
                rootNodeModulesDir: ctx.modulesDir,
                unsafePerm: opts.unsafePerm,
            });
            ctx.pendingBuilds.splice(ctx.pendingBuilds.indexOf(ctx.importerId), 1);
        }
        yield modules_yaml_1.write(ctx.virtualStoreDir, Object.assign({}, ctx.modulesFile, { importers: Object.assign({}, ctx.modulesFile && ctx.modulesFile.importers, { [ctx.importerId]: {
                    hoistedAliases: ctx.hoistedAliases,
                    shamefullyFlatten: opts.shamefullyFlatten,
                } }), included: ctx.include, independentLeaves: opts.independentLeaves, layoutVersion: constants_1.LAYOUT_VERSION, packageManager: `${opts.packageManager.name}@${opts.packageManager.version}`, pendingBuilds: ctx.pendingBuilds, skipped: Array.from(ctx.skipped), store: ctx.storePath }));
    });
}
exports.rebuild = rebuild;
function runLifecycleHooksInDir(prefix, pkg, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const scriptsOpts = {
            depPath: prefix,
            pkgRoot: prefix,
            rawNpmConfig: opts.rawNpmConfig,
            rootNodeModulesDir: opts.rootNodeModulesDir,
            unsafePerm: opts.unsafePerm || false,
        };
        if (pkg.scripts.preinstall) {
            yield lifecycle_1.default('preinstall', pkg, scriptsOpts);
        }
        if (pkg.scripts.install) {
            yield lifecycle_1.default('install', pkg, scriptsOpts);
        }
        if (pkg.scripts.postinstall) {
            yield lifecycle_1.default('postinstall', pkg, scriptsOpts);
        }
        if (pkg.scripts.prepublish) {
            yield lifecycle_1.default('prepublish', pkg, scriptsOpts);
        }
        if (pkg.scripts.prepare) {
            yield lifecycle_1.default('prepare', pkg, scriptsOpts);
        }
    });
}
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 shrinkwrap.yaml` });
            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;
}
function _rebuild(pkgsToRebuild, modules, shr, importerId, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const pkgsThatWereRebuilt = new Set();
        const limitChild = pLimit(opts.childConcurrency);
        const graph = new Map();
        const pkgSnapshots = shr.packages || {};
        const shrImporter = shr.importers[importerId];
        const entryNodes = R.toPairs(Object.assign({}, (opts.development && shrImporter.devDependencies || {}), (opts.production && shrImporter.dependencies || {}), (opts.optional && shrImporter.optionalDependencies || {})))
            .map((pair) => dp.refToRelative(pair[1], pair[0]))
            .filter((nodeId) => nodeId !== null);
        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;
        for (const chunk of chunks) {
            yield Promise.all(chunk
                .filter((relDepPath) => pkgsToRebuild.has(relDepPath))
                .map((relDepPath) => {
                const pkgSnapshot = pkgSnapshots[relDepPath];
                return limitChild(() => __awaiter(this, void 0, void 0, function* () {
                    const depAbsolutePath = dp.resolve(shr.registry, relDepPath);
                    const pkgInfo = pnpm_shrinkwrap_1.nameVerFromPkgSnapshot(relDepPath, pkgSnapshot);
                    try {
                        yield lifecycle_1.runPostinstallHooks({
                            depPath: depAbsolutePath,
                            pkgRoot: path.join(modules, `.${depAbsolutePath}`, 'node_modules', pkgInfo.name),
                            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 || depAbsolutePath,
                                    name: pkgInfo.name,
                                    version: pkgInfo.version,
                                },
                                prefix: opts.prefix,
                                reason: 'build_failure',
                            });
                            return;
                        }
                        throw err;
                    }
                }));
            }));
        }
        return pkgsThatWereRebuilt;
    });
}
//# sourceMappingURL=index.js.map