@rushstack/heft
Version:
Build all your JavaScript projects the same way: A way that works.
203 lines • 10.8 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HeftCommandLineParser = void 0;
const node_os_1 = __importDefault(require("node:os"));
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 MetricsCollector_1 = require("../metrics/MetricsCollector");
const HeftConfiguration_1 = require("../configuration/HeftConfiguration");
const InternalHeftSession_1 = require("../pluginFramework/InternalHeftSession");
const LoggingManager_1 = require("../pluginFramework/logging/LoggingManager");
const CleanAction_1 = require("./actions/CleanAction");
const PhaseAction_1 = require("./actions/PhaseAction");
const RunAction_1 = require("./actions/RunAction");
const AliasAction_1 = require("./actions/AliasAction");
const CliUtilities_1 = require("../utilities/CliUtilities");
const Constants_1 = require("../utilities/Constants");
const HEFT_TOOL_FILENAME = 'heft';
class HeftCommandLineParser extends ts_command_line_1.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_1.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_1.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 terminal_1.ConsoleTerminalProvider({
debugEnabled: this._debug,
verboseEnabled: this._debug
});
this.globalTerminal = new terminal_1.Terminal(this._terminalProvider);
this._loggingManager = new LoggingManager_1.LoggingManager({ terminalProvider: this._terminalProvider });
if (this._debug) {
// Enable printing stacktraces if the "--debug" flag is set
this._loggingManager.enablePrintStacks();
node_core_library_1.InternalError.breakInDebugger = true;
}
const numberOfCores = (_b = (_a = node_os_1.default.availableParallelism) === null || _a === void 0 ? void 0 : _a.call(node_os_1.default)) !== null && _b !== void 0 ? _b : node_os_1.default.cpus().length;
this._heftConfiguration = HeftConfiguration_1.HeftConfiguration.initialize({
cwd: process.cwd(),
terminalProvider: this._terminalProvider,
numberOfCores
});
this._metricsCollector = new MetricsCollector_1.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_1.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_1.CleanAction(actionOptions));
this.addAction(new RunAction_1.RunAction(actionOptions));
for (const phase of internalHeftSession.phases) {
this.addAction(new PhaseAction_1.PhaseAction({ ...actionOptions, phase }));
}
// Add the watch variant of the run action and the individual phase actions
this.addAction(new RunAction_1.RunAction({ ...actionOptions, watch: true }));
for (const phase of internalHeftSession.phases) {
this.addAction(new PhaseAction_1.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_1.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_1.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 node_core_library_1.InternalError('parameters have not yet been defined.');
}
const toolParameters = (0, CliUtilities_1.getToolParameterNamesFromArgs)(args);
return {
debug: toolParameters.has(this._debugFlag.longName),
unmanaged: toolParameters.has(this._unmanagedFlag.longName)
};
}
async _reportErrorAndSetExitCodeAsync(error) {
if (!(error instanceof node_core_library_1.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);
}
}
}
exports.HeftCommandLineParser = HeftCommandLineParser;
//# sourceMappingURL=HeftCommandLineParser.js.map