"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 logger_1 = require("@pnpm/logger");
const camelcaseKeys = require("camelcase-keys");
const graphSequencer = require("graph-sequencer");
const pLimit = require("p-limit");
const path = require("path");
const pkgs_graph_1 = require("pkgs-graph");
const readIniFile = require("read-ini-file");
const supi_1 = require("supi");
const createStoreController_1 = require("../../createStoreController");
const findWorkspacePackages_1 = require("../../findWorkspacePackages");
const getCommandFullName_1 = require("../../getCommandFullName");
const parsePackageSelectors_1 = require("../../parsePackageSelectors");
const requireHooks_1 = require("../../requireHooks");
const help_1 = require("../help");
const exec_1 = require("./exec");
const filter_1 = require("./filter");
const list_1 = require("./list");
const outdated_1 = require("./outdated");
const recursiveSummary_1 = require("./recursiveSummary");
const run_1 = require("./run");
const supportedRecursiveCommands = new Set([
    'install',
    'uninstall',
    'update',
    'link',
    'unlink',
    'list',
    'outdated',
    'rebuild',
    'run',
    'test',
    'exec',
]);
exports.default = (input, opts) => __awaiter(this, void 0, void 0, function* () {
    if (opts.workspaceConcurrency < 1) {
        const err = new Error('Workspace concurrency should be at least 1');
        err['code'] = 'ERR_PNPM_INVALID_WORKSPACE_CONCURRENCY'; // tslint:disable-line:no-string-literal
        throw err;
    }
    const cmd = input.shift();
    if (!cmd) {
        help_1.default(['recursive']);
        return;
    }
    const cmdFullName = getCommandFullName_1.default(cmd);
    if (!supportedRecursiveCommands.has(cmdFullName)) {
        help_1.default(['recursive']);
        const err = new Error(`"recursive ${cmdFullName}" is not a pnpm command. See "pnpm help recursive".`);
        err['code'] = 'ERR_PNPM_INVALID_RECURSIVE_COMMAND'; // tslint:disable-line:no-string-literal
        throw err;
    }
    const workspacePrefix = opts.workspacePrefix || process.cwd();
    const allWorkspacePkgs = yield findWorkspacePackages_1.default(workspacePrefix);
    if (!allWorkspacePkgs.length) {
        logger_1.default.info({ message: `No packages found in "${workspacePrefix}"`, prefix: workspacePrefix });
        return;
    }
    if (opts.filter) {
        // TODO: maybe @pnpm/config should return this in a parsed form already?
        // We don't use opts.prefix in this case because opts.prefix searches for a package.json in parent directories and
        // selects the directory where it finds one
        opts['packageSelectors'] = opts.filter.map((f) => parsePackageSelectors_1.default(f, process.cwd())); // tslint:disable-line
    }
    const atLeastOnePackageMatched = yield recursive(allWorkspacePkgs, input, opts, cmdFullName, cmd);
    if (!atLeastOnePackageMatched) {
        logger_1.default.info({ message: `No packages matched the filters in "${workspacePrefix}"`, prefix: workspacePrefix });
        return;
    }
});
function recursive(allPkgs, input, opts, cmdFullName, cmd) {
    return __awaiter(this, void 0, void 0, function* () {
        if (allPkgs.length === 0) {
            // It might make sense to throw an exception in this case
            return false;
        }
        const pkgGraphResult = pkgs_graph_1.default(allPkgs);
        let pkgs;
        if (opts.scope) {
            pkgGraphResult.graph = filter_1.filterGraphByScope(pkgGraphResult.graph, opts.scope);
            pkgs = allPkgs.filter((pkg) => pkgGraphResult.graph[pkg.path]);
        }
        else if (opts.packageSelectors && opts.packageSelectors.length) {
            pkgGraphResult.graph = filter_1.filterGraph(pkgGraphResult.graph, opts.packageSelectors);
            pkgs = allPkgs.filter((pkg) => pkgGraphResult.graph[pkg.path]);
        }
        else {
            pkgs = allPkgs;
        }
        if (pkgs.length === 0) {
            return false;
        }
        const throwOnFail = recursiveSummary_1.throwOnCommandFail.bind(null, `pnpm recursive ${cmd}`);
        switch (cmdFullName) {
            case 'list':
                yield list_1.default(pkgs, input, cmd, opts); // tslint:disable-line:no-any
                return true;
            case 'outdated':
                yield outdated_1.default(pkgs, input, cmd, opts); // tslint:disable-line:no-any
                return true;
        }
        const chunks = opts.sort
            ? sortPackages(pkgGraphResult.graph)
            : [Object.keys(pkgGraphResult.graph).sort()];
        switch (cmdFullName) {
            case 'test':
                throwOnFail(yield run_1.default(chunks, pkgGraphResult.graph, ['test', ...input], cmd, opts)); // tslint:disable-line:no-any
                return true;
            case 'run':
                throwOnFail(yield run_1.default(chunks, pkgGraphResult.graph, input, cmd, opts)); // tslint:disable-line:no-any
                return true;
            case 'update':
                opts = Object.assign({}, opts, { update: true, allowNew: false }); // tslint:disable-line:no-any
                break;
            case 'exec':
                throwOnFail(yield exec_1.default(chunks, input, cmd, opts)); // tslint:disable-line:no-any
                return true;
        }
        const store = yield createStoreController_1.default(opts);
        // It is enough to save the store.json file once,
        // once all installations are done.
        // That's why saveState that is passed to the install engine
        // does nothing.
        const saveState = store.ctrl.saveState;
        const storeController = Object.assign({}, store.ctrl, { saveState: () => __awaiter(this, void 0, void 0, function* () { return undefined; }) });
        if (cmdFullName === 'link' && opts.linkWorkspacePackages) {
            const err = new Error('"pnpm recursive link" is deprecated with link-workspace-packages = true. Please use "pnpm recursive install" instead');
            err['code'] = 'ERR_PNPM_RECURSIVE_LINK_DEPRECATED'; // tslint:disable-line:no-string-literal
            throw err;
        }
        const localPackages = cmdFullName === 'link' || opts.linkWorkspacePackages
            ? findWorkspacePackages_1.arrayOfLocalPackagesToMap(allPkgs)
            : {};
        const installOpts = Object.assign(opts, {
            localPackages,
            ownLifecycleHooksStdio: 'pipe',
            store: store.path,
            storeController,
        });
        const limitInstallation = pLimit(opts.workspaceConcurrency);
        let action; // tslint:disable-line:no-any
        switch (cmdFullName) {
            case 'unlink':
                action = (input.length === 0 ? supi_1.unlink : supi_1.unlinkPkgs.bind(null, input));
                break;
            case 'rebuild':
                action = (input.length === 0 ? supi_1.rebuild : supi_1.rebuildPkgs.bind(null, input));
                break;
            case 'uninstall':
                action = supi_1.uninstall.bind(null, input);
                break;
            default:
                action = (input.length === 0 ? supi_1.install : supi_1.installPkgs.bind(null, input));
                break;
        }
        const result = {
            fails: [],
            passes: 0,
        };
        for (const chunk of chunks) {
            yield Promise.all(chunk.map((prefix) => limitInstallation(() => __awaiter(this, void 0, void 0, function* () {
                const hooks = opts.ignorePnpmfile ? {} : requireHooks_1.default(prefix, opts);
                try {
                    const localConfigs = yield readLocalConfigs(prefix);
                    if (opts.ignoredPackages && opts.ignoredPackages.has(prefix)) {
                        return;
                    }
                    yield action(Object.assign({}, installOpts, localConfigs, { bin: path.join(prefix, 'node_modules', '.bin'), hooks,
                        prefix, rawNpmConfig: Object.assign({}, installOpts.rawNpmConfig, localConfigs.rawNpmConfig), storeController }));
                    result.passes++;
                }
                catch (err) {
                    logger_1.default.info(err);
                    if (!opts.bail) {
                        result.fails.push({
                            error: err,
                            message: err.message,
                            prefix,
                        });
                        return;
                    }
                    err['prefix'] = prefix; // tslint:disable-line:no-string-literal
                    throw err;
                }
            }))));
        }
        yield saveState();
        throwOnFail(result);
        return true;
    });
}
exports.recursive = recursive;
function sortPackages(pkgGraph) {
    const keys = Object.keys(pkgGraph);
    const graph = new Map(keys.map((pkgPath) => [pkgPath, pkgGraph[pkgPath].dependencies]));
    const graphSequencerResult = graphSequencer({
        graph,
        groups: [keys],
    });
    return graphSequencerResult.chunks;
}
function readLocalConfigs(prefix) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            const ini = yield readIniFile(path.join(prefix, '.npmrc'));
            return camelcaseKeys(ini);
        }
        catch (err) {
            if (err.code !== 'ENOENT')
                throw err;
            return {};
        }
    });
}
//# sourceMappingURL=index.js.map