"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.RushXCommandLine = void 0;
const path = __importStar(require("node:path"));
const pnpm_sync_lib_1 = require("pnpm-sync-lib");
const node_core_library_1 = require("@rushstack/node-core-library");
const terminal_1 = require("@rushstack/terminal");
const Utilities_1 = require("../utilities/Utilities");
const ProjectCommandSet_1 = require("../logic/ProjectCommandSet");
const Rush_1 = require("../api/Rush");
const RushConfiguration_1 = require("../api/RushConfiguration");
const NodeJsCompatibility_1 = require("../logic/NodeJsCompatibility");
const RushStartupBanner_1 = require("./RushStartupBanner");
const EventHooksManager_1 = require("../logic/EventHooksManager");
const EventHooks_1 = require("../api/EventHooks");
const EnvironmentConfiguration_1 = require("../api/EnvironmentConfiguration");
const RushConstants_1 = require("../logic/RushConstants");
const PnpmSyncUtilities_1 = require("../utilities/PnpmSyncUtilities");
const dotenv_1 = require("../logic/dotenv");
const executionUtilities_1 = require("../utilities/executionUtilities");
class ProcessError extends Error {
    constructor(message, exitCode) {
        super(message);
        // Manually set the prototype, as we can no longer extend built-in classes like Error, Array, Map, etc.
        // https://github.com/microsoft/TypeScript-wiki/blob/main/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
        //
        // Note: the prototype must also be set on any classes which extend this one
        this.__proto__ = ProcessError.prototype; // eslint-disable-line @typescript-eslint/no-explicit-any
        this.exitCode = exitCode;
    }
}
class RushXCommandLine {
    static async launchRushXAsync(launcherVersion, options) {
        try {
            const rushxArguments = RushXCommandLine._parseCommandLineArguments();
            const rushJsonFilePath = RushConfiguration_1.RushConfiguration.tryFindRushJsonLocation({
                showVerbose: false
            });
            const { isDebug, help, ignoreHooks } = rushxArguments;
            const terminalProvider = new terminal_1.ConsoleTerminalProvider({
                debugEnabled: isDebug,
                verboseEnabled: isDebug
            });
            const terminal = new terminal_1.Terminal(terminalProvider);
            (0, dotenv_1.initializeDotEnv)(terminal, rushJsonFilePath);
            const rushConfiguration = rushJsonFilePath
                ? RushConfiguration_1.RushConfiguration.loadFromConfigurationFile(rushJsonFilePath)
                : undefined;
            const eventHooksManager = rushConfiguration
                ? new EventHooksManager_1.EventHooksManager(rushConfiguration)
                : undefined;
            const suppressHooks = process.env[EnvironmentConfiguration_1.EnvironmentVariableNames._RUSH_RECURSIVE_RUSHX_CALL] === '1';
            const attemptHooks = !suppressHooks && !help;
            if (attemptHooks) {
                try {
                    eventHooksManager === null || eventHooksManager === void 0 ? void 0 : eventHooksManager.handle(EventHooks_1.Event.preRushx, isDebug, ignoreHooks);
                }
                catch (error) {
                    // eslint-disable-next-line no-console
                    console.error(terminal_1.Colorize.red('PreRushx hook error: ' + error.message));
                }
            }
            // Node.js can sometimes accidentally terminate with a zero exit code  (e.g. for an uncaught
            // promise exception), so we start with the assumption that the exit code is 1
            // and set it to 0 only on success.
            process.exitCode = 1;
            await RushXCommandLine._launchRushXInternalAsync(terminal, rushxArguments, rushConfiguration, options);
            if (attemptHooks) {
                try {
                    eventHooksManager === null || eventHooksManager === void 0 ? void 0 : eventHooksManager.handle(EventHooks_1.Event.postRushx, isDebug, ignoreHooks);
                }
                catch (error) {
                    // eslint-disable-next-line no-console
                    console.error(terminal_1.Colorize.red('PostRushx hook error: ' + error.message));
                }
            }
            // Getting here means that we are all done with no major errors
            process.exitCode = 0;
        }
        catch (error) {
            if (error instanceof ProcessError) {
                process.exitCode = error.exitCode;
            }
            else {
                process.exitCode = 1;
            }
            // eslint-disable-next-line no-console
            console.error(terminal_1.Colorize.red('Error: ' + error.message));
        }
    }
    static async _launchRushXInternalAsync(terminal, rushxArguments, rushConfiguration, options) {
        const { quiet, help, commandName, commandArgs } = rushxArguments;
        if (!quiet) {
            RushStartupBanner_1.RushStartupBanner.logStreamlinedBanner(Rush_1.Rush.version, options.isManaged);
        }
        // Are we in a Rush repo?
        NodeJsCompatibility_1.NodeJsCompatibility.warnAboutCompatibilityIssues({
            isRushLib: true,
            alreadyReportedNodeTooNewError: options.alreadyReportedNodeTooNewError || false,
            rushConfiguration
        });
        // Find the governing package.json for this folder:
        const packageJsonLookup = new node_core_library_1.PackageJsonLookup();
        const packageJsonFilePath = packageJsonLookup.tryGetPackageJsonFilePathFor(process.cwd());
        if (!packageJsonFilePath) {
            throw Error('This command should be used inside a project folder. ' +
                'Unable to find a package.json file in the current working directory or any of its parents.');
        }
        if (rushConfiguration && !rushConfiguration.tryGetProjectForPath(process.cwd())) {
            // GitHub #2713: Users reported confusion resulting from a situation where "rush install"
            // did not install the project's dependencies, because the project was not registered.
            // eslint-disable-next-line no-console
            console.log(terminal_1.Colorize.yellow('Warning: You are invoking "rushx" inside a Rush repository, but this project is not registered in ' +
                `${RushConstants_1.RushConstants.rushJsonFilename}.`));
        }
        const packageJson = packageJsonLookup.loadPackageJson(packageJsonFilePath);
        const projectCommandSet = new ProjectCommandSet_1.ProjectCommandSet(packageJson);
        if (help) {
            RushXCommandLine._showUsage(packageJson, projectCommandSet);
            return;
        }
        const scriptBody = projectCommandSet.tryGetScriptBody(commandName);
        if (scriptBody === undefined) {
            let errorMessage = `The command "${commandName}" is not defined in the package.json file for this project.`;
            if (projectCommandSet.commandNames.length > 0) {
                errorMessage +=
                    '\nAvailable commands for this project are: ' +
                        projectCommandSet.commandNames.map((x) => `"${x}"`).join(', ');
            }
            throw Error(errorMessage);
        }
        let commandWithArgs = scriptBody;
        let commandWithArgsForDisplay = scriptBody;
        if (commandArgs.length > 0) {
            const escapedRemainingArgs = commandArgs.map((x) => (0, executionUtilities_1.escapeArgumentIfNeeded)(x));
            commandWithArgs += ' ' + escapedRemainingArgs.join(' ');
            // Display it nicely without the extra quotes
            commandWithArgsForDisplay += ' ' + commandArgs.join(' ');
        }
        if (!quiet) {
            // eslint-disable-next-line no-console
            console.log(`> ${JSON.stringify(commandWithArgsForDisplay)}\n`);
        }
        const packageFolder = path.dirname(packageJsonFilePath);
        const exitCode = Utilities_1.Utilities.executeLifecycleCommand(commandWithArgs, {
            rushConfiguration,
            workingDirectory: packageFolder,
            // If there is a rush.json then use its .npmrc from the temp folder.
            // Otherwise look for npmrc in the project folder.
            initCwd: rushConfiguration ? rushConfiguration.commonTempFolder : packageFolder,
            handleOutput: false,
            environmentPathOptions: {
                includeProjectBin: true
            }
        });
        if ((rushConfiguration === null || rushConfiguration === void 0 ? void 0 : rushConfiguration.isPnpm) && (rushConfiguration === null || rushConfiguration === void 0 ? void 0 : rushConfiguration.experimentsConfiguration)) {
            const { configuration: experiments } = rushConfiguration === null || rushConfiguration === void 0 ? void 0 : rushConfiguration.experimentsConfiguration;
            if (experiments === null || experiments === void 0 ? void 0 : experiments.usePnpmSyncForInjectedDependencies) {
                const pnpmSyncJsonPath = `${packageFolder}/${RushConstants_1.RushConstants.nodeModulesFolderName}/${RushConstants_1.RushConstants.pnpmSyncFilename}`;
                if (await node_core_library_1.FileSystem.existsAsync(pnpmSyncJsonPath)) {
                    const { PackageExtractor } = await Promise.resolve().then(() => __importStar(require(
                    /* webpackChunkName: 'PackageExtractor' */
                    '@rushstack/package-extractor')));
                    await (0, pnpm_sync_lib_1.pnpmSyncCopyAsync)({
                        pnpmSyncJsonPath,
                        ensureFolderAsync: node_core_library_1.FileSystem.ensureFolderAsync,
                        forEachAsyncWithConcurrency: node_core_library_1.Async.forEachAsync,
                        getPackageIncludedFiles: PackageExtractor.getPackageIncludedFilesAsync,
                        logMessageCallback: (logMessageOptions) => PnpmSyncUtilities_1.PnpmSyncUtilities.processLogMessage(logMessageOptions, terminal)
                    });
                }
            }
        }
        if (exitCode > 0) {
            throw new ProcessError(`Failed calling ${commandWithArgs}.  Exit code: ${exitCode}`, exitCode);
        }
    }
    static _parseCommandLineArguments() {
        // 0 = node.exe
        // 1 = rushx
        const args = process.argv.slice(2);
        const unknownArgs = [];
        let help = false;
        let quiet = false;
        let commandName = '';
        let isDebug = false;
        let ignoreHooks = false;
        const commandArgs = [];
        for (let index = 0; index < args.length; index++) {
            const argValue = args[index];
            if (!commandName) {
                if (argValue === '-q' || argValue === '--quiet') {
                    quiet = true;
                }
                else if (argValue === '-h' || argValue === '--help') {
                    help = true;
                }
                else if (argValue === '-d' || argValue === '--debug') {
                    isDebug = true;
                }
                else if (argValue === '--ignore-hooks') {
                    ignoreHooks = true;
                }
                else if (argValue.startsWith('-')) {
                    unknownArgs.push(args[index]);
                }
                else {
                    commandName = args[index];
                }
            }
            else {
                commandArgs.push(args[index]);
            }
        }
        if (!commandName) {
            help = true;
        }
        if (unknownArgs.length > 0) {
            // Future TODO: Instead of just displaying usage info, we could display a
            // specific error about the unknown flag the user tried to pass to rushx.
            // eslint-disable-next-line no-console
            console.log(terminal_1.Colorize.red(`Unknown arguments: ${unknownArgs.map((x) => JSON.stringify(x)).join(', ')}`));
            help = true;
        }
        return {
            help,
            quiet,
            isDebug,
            ignoreHooks,
            commandName,
            commandArgs
        };
    }
    static _showUsage(packageJson, projectCommandSet) {
        // eslint-disable-next-line no-console
        console.log('usage: rushx [-h]');
        // eslint-disable-next-line no-console
        console.log('       rushx [-q/--quiet] [-d/--debug] [--ignore-hooks] <command> ...\n');
        // eslint-disable-next-line no-console
        console.log('Optional arguments:');
        // eslint-disable-next-line no-console
        console.log('  -h, --help            Show this help message and exit.');
        // eslint-disable-next-line no-console
        console.log('  -q, --quiet           Hide rushx startup information.');
        // eslint-disable-next-line no-console
        console.log('  -d, --debug           Run in debug mode.\n');
        if (projectCommandSet.commandNames.length > 0) {
            // eslint-disable-next-line no-console
            console.log(`Project commands for ${terminal_1.Colorize.cyan(packageJson.name)}:`);
            // Calculate the length of the longest script name, for formatting
            let maxLength = 0;
            for (const commandName of projectCommandSet.commandNames) {
                maxLength = Math.max(maxLength, commandName.length);
            }
            for (const commandName of projectCommandSet.commandNames) {
                const escapedScriptBody = JSON.stringify(projectCommandSet.getScriptBody(commandName));
                // The length of the string e.g. "  command: "
                const firstPartLength = 2 + maxLength + 2;
                // The length for truncating the escaped escapedScriptBody so it doesn't wrap
                // to the next line
                const consoleWidth = terminal_1.PrintUtilities.getConsoleWidth() || terminal_1.DEFAULT_CONSOLE_WIDTH;
                const truncateLength = Math.max(0, consoleWidth - firstPartLength) - 1;
                // eslint-disable-next-line no-console
                console.log(
                // Example: "  command: "
                '  ' +
                    terminal_1.Colorize.cyan(node_core_library_1.Text.padEnd(commandName + ':', maxLength + 2)) +
                    // Example: "do some thin..."
                    node_core_library_1.Text.truncateWithEllipsis(escapedScriptBody, truncateLength));
            }
            if (projectCommandSet.malformedScriptNames.length > 0) {
                // eslint-disable-next-line no-console
                console.log('\n' +
                    terminal_1.Colorize.yellow('Warning: Some "scripts" entries in the package.json file' +
                        ' have malformed names: ' +
                        projectCommandSet.malformedScriptNames.map((x) => `"${x}"`).join(', ')));
            }
        }
        else {
            // eslint-disable-next-line no-console
            console.log(terminal_1.Colorize.yellow('Warning: No commands are defined yet for this project.'));
            // eslint-disable-next-line no-console
            console.log('You can define a command by adding a "scripts" table to the project\'s package.json file.');
        }
    }
}
exports.RushXCommandLine = RushXCommandLine;
//# sourceMappingURL=RushXCommandLine.js.map