"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseInstallAction = void 0;
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const BaseRushAction_1 = require("./BaseRushAction");
const EventHooks_1 = require("../../api/EventHooks");
const PurgeManager_1 = require("../../logic/PurgeManager");
const SetupChecks_1 = require("../../logic/SetupChecks");
const StandardScriptUpdater_1 = require("../../logic/StandardScriptUpdater");
const Stopwatch_1 = require("../../utilities/Stopwatch");
const VersionMismatchFinder_1 = require("../../logic/versionMismatch/VersionMismatchFinder");
const RushConstants_1 = require("../../logic/RushConstants");
const SelectionParameterSet_1 = require("../parsing/SelectionParameterSet");
const Variants_1 = require("../../api/Variants");
const performance_1 = require("../../utilities/performance");
/**
 * This is the common base class for InstallAction and UpdateAction.
 */
class BaseInstallAction extends BaseRushAction_1.BaseRushAction {
    constructor(options) {
        super(options);
        this._purgeParameter = this.defineFlagParameter({
            parameterLongName: '--purge',
            parameterShortName: '-p',
            description: 'Perform "rush purge" before starting the installation'
        });
        this._bypassPolicyParameter = this.defineFlagParameter({
            parameterLongName: RushConstants_1.RushConstants.bypassPolicyFlagLongName,
            description: `Overrides enforcement of the "gitPolicy" rules from ${RushConstants_1.RushConstants.rushJsonFilename} (use honorably!)`
        });
        this._noLinkParameter = this.defineFlagParameter({
            parameterLongName: '--no-link',
            description: 'If "--no-link" is specified, then project symlinks will NOT be created' +
                ' after the installation completes.  You will need to run "rush link" manually.' +
                ' This flag is useful for automated builds that want to report stages individually' +
                ' or perform extra operations in between the two stages. This flag is not supported' +
                ' when using workspaces.'
        });
        this._networkConcurrencyParameter = this.defineIntegerParameter({
            parameterLongName: '--network-concurrency',
            argumentName: 'COUNT',
            description: 'If specified, limits the maximum number of concurrent network requests.' +
                '  This is useful when troubleshooting network failures.'
        });
        this._debugPackageManagerParameter = this.defineFlagParameter({
            parameterLongName: '--debug-package-manager',
            description: 'Activates verbose logging for the package manager. You will probably want to pipe' +
                ' the output of Rush to a file when using this command.'
        });
        this._maxInstallAttempts = this.defineIntegerParameter({
            parameterLongName: '--max-install-attempts',
            argumentName: 'NUMBER',
            description: `Overrides the default maximum number of install attempts.`,
            defaultValue: RushConstants_1.RushConstants.defaultMaxInstallAttempts
        });
        this._ignoreHooksParameter = this.defineFlagParameter({
            parameterLongName: '--ignore-hooks',
            description: `Skips execution of the "eventHooks" scripts defined in ${RushConstants_1.RushConstants.rushJsonFilename}. ` +
                'Make sure you know what you are skipping.'
        });
        this._offlineParameter = this.defineFlagParameter({
            parameterLongName: '--offline',
            description: `Enables installation to be performed without internet access. PNPM will instead report an error` +
                ` if the necessary NPM packages cannot be obtained from the local cache.` +
                ` For details, see the documentation for PNPM's "--offline" parameter.`
        });
        this._variantParameter = this.defineStringParameter(Variants_1.VARIANT_PARAMETER);
    }
    async runAsync() {
        var _a, _b, _c;
        const installManagerOptions = await this.buildInstallOptionsAsync();
        // If we are doing a filtered install and subspaces is enabled, we need to find the affected subspaces and install for all of them.
        let selectedSubspaces;
        const subspaceInstallationDataBySubspace = new Map();
        if (this.rushConfiguration.subspacesFeatureEnabled) {
            // Selecting all subspaces if preventSelectingAllSubspaces is not enabled in subspaces.json
            if (((_a = this.rushConfiguration.subspacesConfiguration) === null || _a === void 0 ? void 0 : _a.preventSelectingAllSubspaces) &&
                !((_b = this._selectionParameters) === null || _b === void 0 ? void 0 : _b.didUserSelectAnything())) {
                this.terminal.writeLine();
                this.terminal.writeLine(terminal_1.Colorize.red(`The subspaces preventSelectingAllSubspaces configuration is enabled, which enforces installation for a specified set of subspace,` +
                    ` passed by the "${SelectionParameterSet_1.SUBSPACE_LONG_ARG_NAME}" parameter or selected from targeted projects using any project selector.`));
                throw new node_core_library_1.AlreadyReportedError();
            }
            const { selectedProjects } = installManagerOptions;
            if (selectedProjects.size === this.rushConfiguration.projects.length) {
                // Optimization for the common case, equivalent to the logic below
                selectedSubspaces = new Set(this.rushConfiguration.subspaces);
            }
            else {
                selectedSubspaces = this.rushConfiguration.getSubspacesForProjects(selectedProjects);
                for (const selectedSubspace of selectedSubspaces) {
                    let subspaceSelectedProjects;
                    let pnpmFilterArgumentValues;
                    if ((_c = selectedSubspace.getPnpmOptions()) === null || _c === void 0 ? void 0 : _c.alwaysFullInstall) {
                        subspaceSelectedProjects = new Set(selectedSubspace.getProjects());
                        pnpmFilterArgumentValues = [];
                    }
                    else {
                        // This may involve filtered installs. Go through each project, add its subspace's pnpm filter arguments
                        subspaceSelectedProjects = new Set();
                        pnpmFilterArgumentValues = [];
                        for (const project of selectedSubspace.getProjects()) {
                            if (selectedProjects.has(project)) {
                                subspaceSelectedProjects.add(project);
                                pnpmFilterArgumentValues.push(project.packageName);
                            }
                        }
                    }
                    subspaceInstallationDataBySubspace.set(selectedSubspace, {
                        selectedProjects: subspaceSelectedProjects,
                        pnpmFilterArgumentValues
                    });
                }
            }
        }
        const variant = await (0, Variants_1.getVariantAsync)(this._variantParameter, this.rushConfiguration, false);
        if (selectedSubspaces) {
            // Check each subspace for version inconsistencies
            for (const subspace of selectedSubspaces) {
                VersionMismatchFinder_1.VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this.terminal, {
                    subspace,
                    variant
                });
            }
        }
        else {
            VersionMismatchFinder_1.VersionMismatchFinder.ensureConsistentVersions(this.rushConfiguration, this.terminal, {
                subspace: undefined,
                variant
            });
        }
        const stopwatch = Stopwatch_1.Stopwatch.start();
        SetupChecks_1.SetupChecks.validate(this.rushConfiguration);
        let warnAboutScriptUpdate = false;
        if (this.actionName === 'update') {
            warnAboutScriptUpdate = await StandardScriptUpdater_1.StandardScriptUpdater.updateAsync(this.rushConfiguration);
        }
        else {
            await StandardScriptUpdater_1.StandardScriptUpdater.validateAsync(this.rushConfiguration);
        }
        this.eventHooksManager.handle(EventHooks_1.Event.preRushInstall, this.parser.isDebug, this._ignoreHooksParameter.value);
        const purgeManager = new PurgeManager_1.PurgeManager(this.rushConfiguration, this.rushGlobalFolder);
        if (this._purgeParameter.value) {
            // eslint-disable-next-line no-console
            console.log('The --purge flag was specified, so performing "rush purge"');
            purgeManager.purgeNormal();
            // eslint-disable-next-line no-console
            console.log('');
        }
        if (this._networkConcurrencyParameter.value) {
            if (this.rushConfiguration.packageManager !== 'pnpm') {
                throw new Error(`The "${this._networkConcurrencyParameter.longName}" parameter is` +
                    ` only supported when using the PNPM package manager.`);
            }
        }
        if (this._maxInstallAttempts.value < 1) {
            throw new Error(`The value of "${this._maxInstallAttempts.longName}" must be positive and nonzero.`);
        }
        const installManagerFactoryModule = await Promise.resolve().then(() => __importStar(require(
        /* webpackChunkName: 'InstallManagerFactory' */
        '../../logic/InstallManagerFactory')));
        let installSuccessful = true;
        try {
            if (selectedSubspaces) {
                // Run the install for each affected subspace
                for (const subspace of selectedSubspaces) {
                    const subspaceInstallationData = subspaceInstallationDataBySubspace.get(subspace);
                    // eslint-disable-next-line no-console
                    console.log(terminal_1.Colorize.green(`Installing for subspace: ${subspace.subspaceName}`));
                    let installManagerOptionsForInstall;
                    if (subspaceInstallationData) {
                        // This will install the selected of projects in the subspace
                        const { selectedProjects, pnpmFilterArgumentValues } = subspaceInstallationData;
                        installManagerOptionsForInstall = {
                            ...installManagerOptions,
                            selectedProjects,
                            // IMPORTANT: SelectionParameterSet.getPnpmFilterArgumentValuesAsync() already calculated
                            // installManagerOptions.pnpmFilterArgumentValues using PNPM CLI operators such as "...my-app".
                            // But with subspaces, "pnpm install" can only see the subset of projects in subspace's temp workspace,
                            // therefore an operator like "--filter ...my-app" will malfunction.  As a workaround, here we are
                            // overwriting installManagerOptions.pnpmFilterArgumentValues with a flat last of project names that
                            // were calculated by Rush.
                            //
                            // TODO: If the flat list produces too many "--filter" arguments, invoking "pnpm install" will exceed
                            // the maximum command length and fail on Windows OS.  Once this is solved, we can eliminate the
                            // redundant logic from SelectionParameterSet.getPnpmFilterArgumentValuesAsync().
                            pnpmFilterArgumentValues,
                            subspace
                        };
                    }
                    else {
                        // This will install all projects in the subspace
                        installManagerOptionsForInstall = {
                            ...installManagerOptions,
                            pnpmFilterArgumentValues: [],
                            subspace
                        };
                    }
                    await this._doInstallAsync(installManagerFactoryModule, purgeManager, installManagerOptionsForInstall);
                }
            }
            else {
                // Simple case when subspacesFeatureEnabled=false
                await this._doInstallAsync(installManagerFactoryModule, purgeManager, {
                    ...installManagerOptions,
                    subspace: this.rushConfiguration.defaultSubspace
                });
            }
        }
        catch (error) {
            installSuccessful = false;
            throw error;
        }
        finally {
            await (0, performance_1.measureAsyncFn)('rush:installManager:startDeleteAllAsync', () => purgeManager.startDeleteAllAsync());
            stopwatch.stop();
            this._collectTelemetry(stopwatch, installManagerOptions, installSuccessful);
            this.parser.flushTelemetry();
            this.eventHooksManager.handle(EventHooks_1.Event.postRushInstall, this.parser.isDebug, this._ignoreHooksParameter.value);
        }
        if (warnAboutScriptUpdate) {
            // eslint-disable-next-line no-console
            console.log('\n' +
                terminal_1.Colorize.yellow('Rush refreshed some files in the "common/scripts" folder.' +
                    '  Please commit this change to Git.'));
        }
        // eslint-disable-next-line no-console
        console.log('\n' + terminal_1.Colorize.green(`Rush ${this.actionName} finished successfully. (${stopwatch.toString()})`));
    }
    async _doInstallAsync(installManagerFactoryModule, purgeManager, installManagerOptions) {
        const installManager = await installManagerFactoryModule.InstallManagerFactory.getInstallManagerAsync(this.rushConfiguration, this.rushGlobalFolder, purgeManager, installManagerOptions);
        await (0, performance_1.measureAsyncFn)('rush:installManager:doInstallAsync', () => installManager.doInstallAsync());
    }
    _collectTelemetry(stopwatch, installManagerOptions, success) {
        var _a;
        if (this.parser.telemetry) {
            const extraData = {
                mode: this.actionName,
                clean: (!!this._purgeParameter.value).toString(),
                debug: installManagerOptions.debug.toString(),
                full: installManagerOptions.fullUpgrade.toString(),
                ...this.getParameterStringMap(),
                ...(_a = this._selectionParameters) === null || _a === void 0 ? void 0 : _a.getTelemetry()
            };
            this.parser.telemetry.log({
                name: 'install',
                durationInSeconds: stopwatch.duration,
                result: success ? 'Succeeded' : 'Failed',
                extraData
            });
        }
    }
}
exports.BaseInstallAction = BaseInstallAction;
//# sourceMappingURL=BaseInstallAction.js.map