UNPKG

@rushstack/heft

Version:

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

176 lines 8.94 kB
"use strict"; // 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