"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.RushCommandLineParser = void 0;
const path = __importStar(require("node:path"));
const ts_command_line_1 = require("@rushstack/ts-command-line");
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const RushConfiguration_1 = require("../api/RushConfiguration");
const RushConstants_1 = require("../logic/RushConstants");
const CommandLineConfiguration_1 = require("../api/CommandLineConfiguration");
const AddAction_1 = require("./actions/AddAction");
const AlertAction_1 = require("./actions/AlertAction");
const BridgePackageAction_1 = require("./actions/BridgePackageAction");
const ChangeAction_1 = require("./actions/ChangeAction");
const CheckAction_1 = require("./actions/CheckAction");
const DeployAction_1 = require("./actions/DeployAction");
const InitAction_1 = require("./actions/InitAction");
const InitAutoinstallerAction_1 = require("./actions/InitAutoinstallerAction");
const InitDeployAction_1 = require("./actions/InitDeployAction");
const InstallAction_1 = require("./actions/InstallAction");
const InstallAutoinstallerAction_1 = require("./actions/InstallAutoinstallerAction");
const LinkAction_1 = require("./actions/LinkAction");
const LinkPackageAction_1 = require("./actions/LinkPackageAction");
const ListAction_1 = require("./actions/ListAction");
const PublishAction_1 = require("./actions/PublishAction");
const PurgeAction_1 = require("./actions/PurgeAction");
const RemoveAction_1 = require("./actions/RemoveAction");
const ScanAction_1 = require("./actions/ScanAction");
const UnlinkAction_1 = require("./actions/UnlinkAction");
const UpdateAction_1 = require("./actions/UpdateAction");
const UpdateAutoinstallerAction_1 = require("./actions/UpdateAutoinstallerAction");
const UpdateCloudCredentialsAction_1 = require("./actions/UpdateCloudCredentialsAction");
const UpgradeInteractiveAction_1 = require("./actions/UpgradeInteractiveAction");
const VersionAction_1 = require("./actions/VersionAction");
const GlobalScriptAction_1 = require("./scriptActions/GlobalScriptAction");
const PhasedScriptAction_1 = require("./scriptActions/PhasedScriptAction");
const Telemetry_1 = require("../logic/Telemetry");
const RushGlobalFolder_1 = require("../api/RushGlobalFolder");
const NodeJsCompatibility_1 = require("../logic/NodeJsCompatibility");
const SetupAction_1 = require("./actions/SetupAction");
const PluginManager_1 = require("../pluginFramework/PluginManager");
const RushSession_1 = require("../pluginFramework/RushSession");
const InitSubspaceAction_1 = require("./actions/InitSubspaceAction");
const RushAlerts_1 = require("../utilities/RushAlerts");
const dotenv_1 = require("../logic/dotenv");
const performance_1 = require("../utilities/performance");
class RushCommandLineParser extends ts_command_line_1.CommandLineParser {
    /**
     * The current working directory that was used to find the Rush configuration.
     */
    get cwd() {
        return this._rushOptions.cwd;
    }
    constructor(options) {
        super({
            toolFilename: 'rush',
            toolDescription: 'Rush makes life easier for JavaScript developers who develop, build, and publish' +
                ' many packages from a central Git repo.  It is designed to handle very large repositories' +
                ' supporting many projects and people.  Rush provides policies, protections, and customizations' +
                ' that help coordinate teams and safely onboard new contributors.  Rush also generates change logs' +
                ' and automates package publishing.  It can manage decoupled subsets of projects with different' +
                ' release and versioning strategies.  A full API is included to facilitate integration with other' +
                ' automation tools.  If you are looking for a proven turnkey solution for monorepo management,' +
                ' Rush is for you.',
            enableTabCompletionAction: true
        });
        this._restrictConsoleOutput = RushCommandLineParser.shouldRestrictConsoleOutput();
        this._debugParameter = this.defineFlagParameter({
            parameterLongName: '--debug',
            parameterShortName: '-d',
            description: 'Show the full call stack if an error occurs while executing the tool'
        });
        this._quietParameter = this.defineFlagParameter({
            parameterLongName: '--quiet',
            parameterShortName: '-q',
            description: 'Hide rush startup information'
        });
        const terminalProvider = new terminal_1.ConsoleTerminalProvider();
        this._terminalProvider = terminalProvider;
        const terminal = new terminal_1.Terminal(this._terminalProvider);
        this._terminal = terminal;
        this._rushOptions = this._normalizeOptions(options || {});
        const { cwd, alreadyReportedNodeTooNewError, builtInPluginConfigurations } = this._rushOptions;
        let rushJsonFilePath;
        try {
            rushJsonFilePath = RushConfiguration_1.RushConfiguration.tryFindRushJsonLocation({
                startingFolder: cwd,
                showVerbose: !this._restrictConsoleOutput
            });
            (0, dotenv_1.initializeDotEnv)(terminal, rushJsonFilePath);
            if (rushJsonFilePath) {
                this.rushConfiguration = RushConfiguration_1.RushConfiguration.loadFromConfigurationFile(rushJsonFilePath);
            }
        }
        catch (error) {
            this._reportErrorAndSetExitCode(error);
        }
        NodeJsCompatibility_1.NodeJsCompatibility.warnAboutCompatibilityIssues({
            isRushLib: true,
            alreadyReportedNodeTooNewError,
            rushConfiguration: this.rushConfiguration
        });
        this.rushGlobalFolder = new RushGlobalFolder_1.RushGlobalFolder();
        this.rushSession = new RushSession_1.RushSession({
            getIsDebugMode: () => this.isDebug,
            terminalProvider
        });
        this.pluginManager = new PluginManager_1.PluginManager({
            rushSession: this.rushSession,
            rushConfiguration: this.rushConfiguration,
            terminal,
            builtInPluginConfigurations,
            restrictConsoleOutput: this._restrictConsoleOutput,
            rushGlobalFolder: this.rushGlobalFolder
        });
        const pluginCommandLineConfigurations = this.pluginManager.tryGetCustomCommandLineConfigurationInfos();
        const hasBuildCommandInPlugin = pluginCommandLineConfigurations.some((x) => x.commandLineConfiguration.commands.has(RushConstants_1.RushConstants.buildCommandName));
        // If the plugin has a build command, we don't need to autocreate the default build command.
        this._autocreateBuildCommand = !hasBuildCommandInPlugin;
        this._populateActions();
        for (const { commandLineConfiguration, pluginLoader } of pluginCommandLineConfigurations) {
            try {
                this._addCommandLineConfigActions(commandLineConfiguration);
            }
            catch (e) {
                this._reportErrorAndSetExitCode(new Error(`Error from plugin ${pluginLoader.pluginName} by ${pluginLoader.packageName}: ${e.toString()}`));
            }
        }
    }
    get isDebug() {
        return this._debugParameter.value;
    }
    get isQuiet() {
        return this._quietParameter.value;
    }
    get terminal() {
        return this._terminal;
    }
    /**
     * Utility to determine if the app should restrict writing to the console.
     */
    static shouldRestrictConsoleOutput() {
        if (ts_command_line_1.CommandLineHelper.isTabCompletionActionRequest(process.argv)) {
            return true;
        }
        for (let i = 2; i < process.argv.length; i++) {
            const arg = process.argv[i];
            if (arg === '-q' || arg === '--quiet' || arg === '--json') {
                return true;
            }
        }
        return false;
    }
    flushTelemetry() {
        var _a;
        (_a = this.telemetry) === null || _a === void 0 ? void 0 : _a.flush();
    }
    async executeAsync(args) {
        // debugParameter will be correctly parsed during super.executeAsync(), so manually parse here.
        this._terminalProvider.verboseEnabled = this._terminalProvider.debugEnabled =
            process.argv.indexOf('--debug') >= 0;
        await (0, performance_1.measureAsyncFn)('rush:initializeUnassociatedPlugins', () => this.pluginManager.tryInitializeUnassociatedPluginsAsync());
        return await super.executeAsync(args);
    }
    async onExecuteAsync() {
        var _a;
        // Defensively set the exit code to 1 so if Rush crashes for whatever reason, we'll have a nonzero exit code.
        // For example, Node.js currently has the inexcusable design of terminating with zero exit code when
        // there is an uncaught promise exception.  This will supposedly be fixed in Node.js 9.
        // Ideally we should do this for all the Rush actions, but "rush build" is the most critical one
        // -- if it falsely appears to succeed, we could merge bad PRs, publish empty packages, etc.
        process.exitCode = 1;
        if (this._debugParameter.value) {
            node_core_library_1.InternalError.breakInDebugger = true;
        }
        try {
            await this._wrapOnExecuteAsync();
            // TODO: rushConfiguration is typed as "!: RushConfiguration" here, but can sometimes be undefined
            if (this.rushConfiguration) {
                try {
                    const { configuration: experiments } = this.rushConfiguration.experimentsConfiguration;
                    if (experiments.rushAlerts) {
                        // TODO: Fix this
                        // eslint-disable-next-line @typescript-eslint/no-explicit-any
                        const actionName = this
                            ._getArgumentParser()
                            .parseArgs(process.argv.slice(2)).action;
                        // only display alerts when certain specific actions are triggered
                        if (RushAlerts_1.RushAlerts.alertTriggerActions.includes(actionName)) {
                            this._terminal.writeDebugLine('Checking Rush alerts...');
                            const rushAlerts = await RushAlerts_1.RushAlerts.loadFromConfigurationAsync(this.rushConfiguration, this._terminal);
                            // Print out alerts if have after each successful command actions
                            await rushAlerts.printAlertsAsync();
                        }
                    }
                }
                catch (error) {
                    if (error instanceof node_core_library_1.AlreadyReportedError) {
                        throw error;
                    }
                    // Generally the RushAlerts implementation should handle its own error reporting; if not,
                    // clarify the source, since the Rush Alerts behavior is nondeterministic and may not repro easily:
                    this._terminal.writeErrorLine(`\nAn unexpected error was encountered by the Rush alerts feature:`);
                    this._terminal.writeErrorLine(error.message);
                    throw new node_core_library_1.AlreadyReportedError();
                }
            }
            // If we make it here, everything went fine, so reset the exit code back to 0
            process.exitCode = 0;
        }
        catch (error) {
            this._reportErrorAndSetExitCode(error);
        }
        // This only gets hit if the wrapped execution completes successfully
        await ((_a = this.telemetry) === null || _a === void 0 ? void 0 : _a.ensureFlushedAsync());
    }
    _normalizeOptions(options) {
        return {
            cwd: options.cwd || process.cwd(),
            alreadyReportedNodeTooNewError: options.alreadyReportedNodeTooNewError || false,
            builtInPluginConfigurations: options.builtInPluginConfigurations || []
        };
    }
    async _wrapOnExecuteAsync() {
        if (this.rushConfiguration) {
            this.telemetry = new Telemetry_1.Telemetry(this.rushConfiguration, this.rushSession);
        }
        try {
            await (0, performance_1.measureAsyncFn)('rush:commandLineParser:onExecuteAsync', () => super.onExecuteAsync());
        }
        finally {
            if (this.telemetry) {
                this.flushTelemetry();
            }
        }
    }
    _populateActions() {
        try {
            // Alphabetical order
            this.addAction(new AddAction_1.AddAction(this));
            this.addAction(new ChangeAction_1.ChangeAction(this));
            this.addAction(new CheckAction_1.CheckAction(this));
            this.addAction(new DeployAction_1.DeployAction(this));
            this.addAction(new InitAction_1.InitAction(this));
            this.addAction(new InitAutoinstallerAction_1.InitAutoinstallerAction(this));
            this.addAction(new InitDeployAction_1.InitDeployAction(this));
            this.addAction(new InitSubspaceAction_1.InitSubspaceAction(this));
            this.addAction(new InstallAction_1.InstallAction(this));
            this.addAction(new LinkAction_1.LinkAction(this));
            this.addAction(new ListAction_1.ListAction(this));
            this.addAction(new PublishAction_1.PublishAction(this));
            this.addAction(new PurgeAction_1.PurgeAction(this));
            this.addAction(new RemoveAction_1.RemoveAction(this));
            this.addAction(new ScanAction_1.ScanAction(this));
            this.addAction(new SetupAction_1.SetupAction(this));
            this.addAction(new UnlinkAction_1.UnlinkAction(this));
            this.addAction(new UpdateAction_1.UpdateAction(this));
            this.addAction(new InstallAutoinstallerAction_1.InstallAutoinstallerAction(this));
            this.addAction(new UpdateAutoinstallerAction_1.UpdateAutoinstallerAction(this));
            this.addAction(new UpdateCloudCredentialsAction_1.UpdateCloudCredentialsAction(this));
            this.addAction(new UpgradeInteractiveAction_1.UpgradeInteractiveAction(this));
            this.addAction(new VersionAction_1.VersionAction(this));
            this.addAction(new AlertAction_1.AlertAction(this));
            this.addAction(new BridgePackageAction_1.BridgePackageAction(this));
            this.addAction(new LinkPackageAction_1.LinkPackageAction(this));
            this._populateScriptActions();
        }
        catch (error) {
            this._reportErrorAndSetExitCode(error);
        }
    }
    _populateScriptActions() {
        // If there is not a rush.json file, we still want "build" and "rebuild" to appear in the
        // command-line help
        let commandLineConfigFilePath;
        if (this.rushConfiguration) {
            commandLineConfigFilePath = path.join(this.rushConfiguration.commonRushConfigFolder, RushConstants_1.RushConstants.commandLineFilename);
        }
        // If a build action is already added by a plugin, we don't want to add a default "build" script
        const doNotIncludeDefaultBuildCommands = !this._autocreateBuildCommand;
        const commandLineConfiguration = CommandLineConfiguration_1.CommandLineConfiguration.loadFromFileOrDefault(commandLineConfigFilePath, doNotIncludeDefaultBuildCommands);
        this._addCommandLineConfigActions(commandLineConfiguration);
    }
    _addCommandLineConfigActions(commandLineConfiguration) {
        // Register each custom command
        for (const command of commandLineConfiguration.commands.values()) {
            this._addCommandLineConfigAction(commandLineConfiguration, command);
        }
    }
    _addCommandLineConfigAction(commandLineConfiguration, command) {
        if (this.tryGetAction(command.name)) {
            throw new Error(`${RushConstants_1.RushConstants.commandLineFilename} defines a command "${command.name}"` +
                ` using a name that already exists`);
        }
        switch (command.commandKind) {
            case RushConstants_1.RushConstants.globalCommandKind: {
                this._addGlobalScriptAction(commandLineConfiguration, command);
                break;
            }
            case RushConstants_1.RushConstants.phasedCommandKind: {
                this._addPhasedCommandLineConfigAction(commandLineConfiguration, command);
                break;
            }
            default:
                throw new Error(`${RushConstants_1.RushConstants.commandLineFilename} defines a command "${command.name}"` +
                    ` using an unsupported command kind "${command.commandKind}"`);
        }
    }
    _getSharedCommandActionOptions(commandLineConfiguration, command) {
        return {
            actionName: command.name,
            summary: command.summary,
            documentation: command.description || command.summary,
            safeForSimultaneousRushProcesses: command.safeForSimultaneousRushProcesses,
            command,
            parser: this,
            commandLineConfiguration: commandLineConfiguration
        };
    }
    _addGlobalScriptAction(commandLineConfiguration, command) {
        if (command.name === RushConstants_1.RushConstants.buildCommandName ||
            command.name === RushConstants_1.RushConstants.rebuildCommandName) {
            throw new Error(`${RushConstants_1.RushConstants.commandLineFilename} defines a command "${command.name}" using ` +
                `the command kind "${RushConstants_1.RushConstants.globalCommandKind}". This command can only be designated as a command ` +
                `kind "${RushConstants_1.RushConstants.bulkCommandKind}" or "${RushConstants_1.RushConstants.phasedCommandKind}".`);
        }
        const sharedCommandOptions = this._getSharedCommandActionOptions(commandLineConfiguration, command);
        this.addAction(new GlobalScriptAction_1.GlobalScriptAction({
            ...sharedCommandOptions,
            shellCommand: command.shellCommand,
            autoinstallerName: command.autoinstallerName
        }));
    }
    _addPhasedCommandLineConfigAction(commandLineConfiguration, command) {
        var _a, _b;
        const baseCommandOptions = this._getSharedCommandActionOptions(commandLineConfiguration, command);
        this.addAction(new PhasedScriptAction_1.PhasedScriptAction({
            ...baseCommandOptions,
            enableParallelism: command.enableParallelism,
            incremental: command.incremental || false,
            disableBuildCache: command.disableBuildCache || false,
            // The Async.forEachAsync() API defaults allowOversubscription=false, whereas Rush historically
            // defaults allowOversubscription=true to favor faster builds rather than strictly staying below
            // the CPU limit.
            allowOversubscription: (_a = command.allowOversubscription) !== null && _a !== void 0 ? _a : true,
            initialPhases: command.phases,
            originalPhases: command.originalPhases,
            watchPhases: command.watchPhases,
            watchDebounceMs: (_b = command.watchDebounceMs) !== null && _b !== void 0 ? _b : RushConstants_1.RushConstants.defaultWatchDebounceMs,
            phases: commandLineConfiguration.phases,
            alwaysWatch: command.alwaysWatch,
            alwaysInstall: command.alwaysInstall
        }));
    }
    _reportErrorAndSetExitCode(error) {
        if (!(error instanceof node_core_library_1.AlreadyReportedError)) {
            const prefix = 'ERROR: ';
            // The colors package will eat multi-newlines, which could break formatting
            // in user-specified messages and instructions, so we prefer to color each
            // line individually.
            const message = node_core_library_1.Text.splitByNewLines(terminal_1.PrintUtilities.wrapWords(prefix + error.message))
                .map((line) => terminal_1.Colorize.red(line))
                .join('\n');
            // eslint-disable-next-line no-console
            console.error(`\n${message}`);
        }
        if (this._debugParameter.value) {
            // If catchSyncErrors() called this, then show a call stack similar to what Node.js
            // would show for an uncaught error
            // eslint-disable-next-line no-console
            console.error(`\n${error.stack}`);
        }
        this.flushTelemetry();
        const handleExit = () => {
            // Ideally we want to eliminate all calls to process.exit() from our code, and replace them
            // with normal control flow that properly cleans up its data structures.
            // For this particular call, we have a problem that the RushCommandLineParser constructor
            // performs nontrivial work that can throw an exception.  Either the Rush class would need
            // to handle reporting for those exceptions, or else _populateActions() should be moved
            // to a RushCommandLineParser lifecycle stage that can handle it.
            if (process.exitCode !== undefined) {
                process.exit(process.exitCode);
            }
            else {
                process.exit(1);
            }
        };
        if (this.telemetry && this.rushSession.hooks.flushTelemetry.isUsed()) {
            this.telemetry.ensureFlushedAsync().then(handleExit).catch(handleExit);
        }
        else {
            handleExit();
        }
    }
}
exports.RushCommandLineParser = RushCommandLineParser;
//# sourceMappingURL=RushCommandLineParser.js.map