@rushstack/heft
Version:
Build all your JavaScript projects the same way: A way that works.
176 lines • 8.94 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskOperationRunner = void 0;
exports.runAndMeasureAsync = runAndMeasureAsync;
const node_crypto_1 = require("node:crypto");
const fast_glob_1 = require("fast-glob");
const operation_graph_1 = require("@rushstack/operation-graph");
const node_core_library_1 = require("@rushstack/node-core-library");
const CopyFilesPlugin_1 = require("../../plugins/CopyFilesPlugin");
const DeleteFilesPlugin_1 = require("../../plugins/DeleteFilesPlugin");
const FileGlobSpecifier_1 = require("../../plugins/FileGlobSpecifier");
const WatchFileSystemAdapter_1 = require("../../utilities/WatchFileSystemAdapter");
/**
* Log out a start message, run a provided function, and log out an end message
*/
async function runAndMeasureAsync(fn, startMessageFn, endMessageFn, logFn) {
logFn(startMessageFn());
const startTime = performance.now();
try {
return await fn();
}
finally {
const endTime = performance.now();
logFn(`${endMessageFn()} (${endTime - startTime}ms)`);
}
}
class TaskOperationRunner {
get name() {
const { taskName, parentPhase } = this._options.task;
return `Task ${JSON.stringify(taskName)} of phase ${JSON.stringify(parentPhase.phaseName)}`;
}
constructor(options) {
this._fileOperations = undefined;
this._watchFileSystemAdapter = undefined;
this.silent = false;
this._options = options;
}
async executeAsync(context) {
const { internalHeftSession, task } = this._options;
const { parentPhase } = task;
const phaseSession = internalHeftSession.getSessionForPhase(parentPhase);
const taskSession = phaseSession.getSessionForTask(task);
return await this._executeTaskAsync(context, taskSession);
}
async _executeTaskAsync(context, taskSession) {
const { abortSignal, requestRun } = context;
const { hooks, logger } = taskSession;
// Need to clear any errors or warnings from the previous invocation, particularly
// if this is an immediate rerun
logger.resetErrorsAndWarnings();
const rootFolderPath = this._options.internalHeftSession.heftConfiguration.buildFolderPath;
const isWatchMode = taskSession.parameters.watch && !!requestRun;
const { terminal } = logger;
// Exit the task early if cancellation is requested
if (abortSignal.aborted) {
return operation_graph_1.OperationStatus.Aborted;
}
if (!this._fileOperations && hooks.registerFileOperations.isUsed()) {
const fileOperations = await hooks.registerFileOperations.promise({
copyOperations: new Set(),
deleteOperations: new Set()
});
let copyConfigHash;
const { copyOperations } = fileOperations;
if (copyOperations.size > 0) {
// Do this here so that we only need to do it once for each Heft invocation
const hasher = (0, node_crypto_1.createHash)('sha256');
const absolutePathCopyOperations = new Set();
for (const copyOperation of fileOperations.copyOperations) {
// The paths in the `fileOperations` object may be either absolute or relative
// For execution we need absolute paths.
const absoluteOperation = (0, CopyFilesPlugin_1.asAbsoluteCopyOperation)(rootFolderPath, copyOperation);
absolutePathCopyOperations.add(absoluteOperation);
// For portability of the hash we need relative paths.
const portableCopyOperation = (0, CopyFilesPlugin_1.asRelativeCopyOperation)(rootFolderPath, absoluteOperation);
hasher.update(JSON.stringify(portableCopyOperation));
}
fileOperations.copyOperations = absolutePathCopyOperations;
copyConfigHash = hasher.digest('base64');
}
this._fileOperations = fileOperations;
this._copyConfigHash = copyConfigHash;
}
const shouldRunIncremental = isWatchMode && hooks.runIncremental.isUsed();
let watchFileSystemAdapter;
const getWatchFileSystemAdapter = () => {
if (!watchFileSystemAdapter) {
watchFileSystemAdapter = this._watchFileSystemAdapter || (this._watchFileSystemAdapter = new WatchFileSystemAdapter_1.WatchFileSystemAdapter());
watchFileSystemAdapter.setBaseline();
}
return watchFileSystemAdapter;
};
const shouldRun = hooks.run.isUsed() || shouldRunIncremental;
if (!shouldRun && !this._fileOperations) {
terminal.writeVerboseLine('Task execution skipped, no implementation provided');
return operation_graph_1.OperationStatus.NoOp;
}
const runResult = shouldRun
? await runAndMeasureAsync(async () => {
// Create the options and provide a utility method to obtain paths to copy
const runHookOptions = {
abortSignal,
globAsync: fast_glob_1.glob
};
// Run the plugin run hook
try {
if (shouldRunIncremental) {
const runIncrementalHookOptions = {
...runHookOptions,
watchGlobAsync: (pattern, options = {}) => {
return (0, FileGlobSpecifier_1.watchGlobAsync)(pattern, {
...options,
fs: getWatchFileSystemAdapter()
});
},
get watchFs() {
return getWatchFileSystemAdapter();
},
requestRun: requestRun
};
await hooks.runIncremental.promise(runIncrementalHookOptions);
}
else {
await hooks.run.promise(runHookOptions);
}
}
catch (e) {
// Log out using the task logger, and return an error status
if (!(e instanceof node_core_library_1.AlreadyReportedError)) {
logger.emitError(e);
}
return operation_graph_1.OperationStatus.Failure;
}
if (abortSignal.aborted) {
return operation_graph_1.OperationStatus.Aborted;
}
return operation_graph_1.OperationStatus.Success;
}, () => `Starting ${shouldRunIncremental ? 'incremental ' : ''}task execution`, () => {
const finishedWord = abortSignal.aborted ? 'Aborted' : 'Finished';
return `${finishedWord} ${shouldRunIncremental ? 'incremental ' : ''}task execution`;
}, terminal.writeVerboseLine.bind(terminal))
: // This branch only occurs if only file operations are defined.
operation_graph_1.OperationStatus.Success;
if (this._fileOperations) {
const { copyOperations, deleteOperations } = this._fileOperations;
const copyConfigHash = this._copyConfigHash;
await Promise.all([
copyConfigHash
? (0, CopyFilesPlugin_1.copyFilesAsync)(copyOperations, logger.terminal, `${taskSession.tempFolderPath}/file-copy.json`, copyConfigHash, isWatchMode ? getWatchFileSystemAdapter() : undefined)
: Promise.resolve(),
deleteOperations.size > 0
? (0, DeleteFilesPlugin_1.deleteFilesAsync)(rootFolderPath, deleteOperations, logger.terminal)
: Promise.resolve()
]);
}
if (watchFileSystemAdapter) {
if (!requestRun) {
throw new node_core_library_1.InternalError(`watchFileSystemAdapter was initialized but requestRun is not defined!`);
}
watchFileSystemAdapter.watch(requestRun);
}
// Even if the entire process has completed, we should mark the operation as cancelled if
// cancellation has been requested.
if (abortSignal.aborted) {
return operation_graph_1.OperationStatus.Aborted;
}
if (logger.hasErrors) {
return operation_graph_1.OperationStatus.Failure;
}
return runResult;
}
}
exports.TaskOperationRunner = TaskOperationRunner;
//# sourceMappingURL=TaskOperationRunner.js.map