UNPKG

@rushstack/heft

Version:

Build all your JavaScript projects the same way: A way that works.

196 lines 10.1 kB
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. import os from 'node:os'; import { CommandLineParser } from '@rushstack/ts-command-line'; import { InternalError, AlreadyReportedError } from '@rushstack/node-core-library'; import { Terminal, ConsoleTerminalProvider } from '@rushstack/terminal'; import { MetricsCollector } from '../metrics/MetricsCollector'; import { HeftConfiguration } from '../configuration/HeftConfiguration'; import { InternalHeftSession } from '../pluginFramework/InternalHeftSession'; import { LoggingManager } from '../pluginFramework/logging/LoggingManager'; import { CleanAction } from './actions/CleanAction'; import { PhaseAction } from './actions/PhaseAction'; import { RunAction } from './actions/RunAction'; import { AliasAction } from './actions/AliasAction'; import { getToolParameterNamesFromArgs } from '../utilities/CliUtilities'; import { Constants } from '../utilities/Constants'; const HEFT_TOOL_FILENAME = 'heft'; export class HeftCommandLineParser extends CommandLineParser { constructor() { var _a, _b; super({ toolFilename: HEFT_TOOL_FILENAME, toolDescription: 'Heft is a pluggable build system designed for web projects.' }); // Initialize the debug flag as a parameter on the tool itself this._debugFlag = this.defineFlagParameter({ parameterLongName: Constants.debugParameterLongName, description: 'Show the full call stack if an error occurs while executing the tool' }); // Initialize the unmanaged flag as a parameter on the tool itself. While this parameter // is only used during version selection, we need to support parsing it here so that we // don't throw due to an unrecognized parameter. this._unmanagedFlag = this.defineFlagParameter({ parameterLongName: Constants.unmanagedParameterLongName, description: 'Disables the Heft version selector: When Heft is invoked via the shell path, normally it' + " will examine the project's package.json dependencies and try to use the locally installed version" + ' of Heft. Specify "--unmanaged" to force the invoked version of Heft to be used. This is useful for' + ' example if you want to test a different version of Heft.' }); // Pre-initialize with known argument values to determine state of "--debug" const preInitializationArgumentValues = this._getPreInitializationArgumentValues(); this._debug = !!preInitializationArgumentValues.debug; // Enable debug and verbose logging if the "--debug" flag is set this._terminalProvider = new ConsoleTerminalProvider({ debugEnabled: this._debug, verboseEnabled: this._debug }); this.globalTerminal = new Terminal(this._terminalProvider); this._loggingManager = new LoggingManager({ terminalProvider: this._terminalProvider }); if (this._debug) { // Enable printing stacktraces if the "--debug" flag is set this._loggingManager.enablePrintStacks(); InternalError.breakInDebugger = true; } const numberOfCores = (_b = (_a = os.availableParallelism) === null || _a === void 0 ? void 0 : _a.call(os)) !== null && _b !== void 0 ? _b : os.cpus().length; this._heftConfiguration = HeftConfiguration.initialize({ cwd: process.cwd(), terminalProvider: this._terminalProvider, numberOfCores }); this._metricsCollector = new MetricsCollector(); } async executeAsync(args) { // Defensively set the exit code to 1 so if the tool crashes for whatever reason, // we'll have a nonzero exit code. process.exitCode = 1; try { this._normalizeCwd(); const internalHeftSession = await InternalHeftSession.initializeAsync({ debug: this._debug, heftConfiguration: this._heftConfiguration, loggingManager: this._loggingManager, metricsCollector: this._metricsCollector }); this._internalHeftSession = internalHeftSession; const actionOptions = { internalHeftSession: internalHeftSession, terminal: this.globalTerminal, loggingManager: this._loggingManager, metricsCollector: this._metricsCollector, heftConfiguration: this._heftConfiguration }; // Add the clean action, the run action, and the individual phase actions this.addAction(new CleanAction(actionOptions)); this.addAction(new RunAction(actionOptions)); for (const phase of internalHeftSession.phases) { this.addAction(new PhaseAction({ ...actionOptions, phase })); } // Add the watch variant of the run action and the individual phase actions this.addAction(new RunAction({ ...actionOptions, watch: true })); for (const phase of internalHeftSession.phases) { this.addAction(new PhaseAction({ ...actionOptions, phase, watch: true })); } // Add the action aliases last, since we need the targets to be defined before we can add the aliases const aliasActions = []; for (const [aliasName, { actionName, defaultParameters }] of internalHeftSession.actionReferencesByAlias) { const existingAction = this.tryGetAction(aliasName); if (existingAction) { throw new Error(`The alias "${aliasName}" specified in heft.json cannot be used because an action ` + 'with that name already exists.'); } const targetAction = this.tryGetAction(actionName); if (!targetAction) { throw new Error(`The action "${actionName}" referred to by alias "${aliasName}" in heft.json could not be found.`); } aliasActions.push(new AliasAction({ terminal: this.globalTerminal, toolFilename: HEFT_TOOL_FILENAME, aliasName, targetAction, defaultParameters })); } // Add the alias actions. Do this in a second pass to disallow aliases that refer to other aliases. for (const aliasAction of aliasActions) { this.addAction(aliasAction); } return await super.executeAsync(args); } catch (e) { await this._reportErrorAndSetExitCodeAsync(e); return false; } } async onExecuteAsync() { try { const selectedAction = this.selectedAction; let commandName = ''; let unaliasedCommandName = ''; if (selectedAction) { commandName = selectedAction.actionName; if (selectedAction instanceof AliasAction) { unaliasedCommandName = selectedAction.targetAction.actionName; } else { unaliasedCommandName = selectedAction.actionName; } } this._internalHeftSession.parsedCommandLine = { commandName, unaliasedCommandName }; await super.onExecuteAsync(); } catch (e) { await this._reportErrorAndSetExitCodeAsync(e); } // If we make it here, things are fine and reset the exit code back to 0 process.exitCode = 0; } _normalizeCwd() { const buildFolder = this._heftConfiguration.buildFolderPath; const currentCwd = process.cwd(); if (currentCwd !== buildFolder) { // Update the CWD to the project's build root. Some tools, like Jest, use process.cwd() this.globalTerminal.writeVerboseLine(`CWD is "${currentCwd}". Normalizing to "${buildFolder}".`); // If `process.cwd()` and `buildFolder` differ only by casing on Windows, the chdir operation will not fix the casing, which is the entire purpose of the exercise. // As such, chdir to a different directory first. That directory needs to exist, so use the parent of the current directory. // This will not work if the current folder is the drive root, but that is a rather exotic case. process.chdir(__dirname); process.chdir(buildFolder); } } _getPreInitializationArgumentValues(args = process.argv) { if (!this._debugFlag) { // The `this._debugFlag` parameter (the parameter itself, not its value) // has not yet been defined. Parameters need to be defined before we // try to evaluate any parameters. This is to ensure that the // `--debug` flag is defined correctly before we do this not-so-rigorous // parameter parsing. throw new InternalError('parameters have not yet been defined.'); } const toolParameters = getToolParameterNamesFromArgs(args); return { debug: toolParameters.has(this._debugFlag.longName), unmanaged: toolParameters.has(this._unmanagedFlag.longName) }; } async _reportErrorAndSetExitCodeAsync(error) { if (!(error instanceof AlreadyReportedError)) { this.globalTerminal.writeErrorLine(error.toString()); } if (this._debug) { this.globalTerminal.writeLine(); this.globalTerminal.writeErrorLine(error.stack); } const exitCode = process.exitCode; if (!exitCode || typeof exitCode !== 'number' || exitCode > 0) { process.exit(exitCode); } else { process.exit(1); } } } //# sourceMappingURL=HeftCommandLineParser.js.map