UNPKG

@sfdx-falcon/task

Version:

Specialized abstraction of a single Listr Task. Part of the SFDX-Falcon Library.

406 lines 27.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const rxjs_1 = require("rxjs"); // Class. Used to communicate status with Listr. const rxjs_2 = require("rxjs"); // Class. Implements the Observer interface and extends the Subscription class. // Import SFDX-Falcon Libraries const util_1 = require("@sfdx-falcon/util"); // Library. Async utility helper functions. const validator_1 = require("@sfdx-falcon/validator"); // Library of Type Validation helper functions. // Import SFDX-Falcon Classes & Functions const debug_1 = require("@sfdx-falcon/debug"); // Class. Provides custom "debugging" services (ie. debug-style info to console.log()). const error_1 = require("@sfdx-falcon/error"); // Class. Extends SfdxError to provide specialized error structures for SFDX-Falcon modules. const status_1 = require("@sfdx-falcon/status"); // Class. Status tracking object for use with Generators derived from SfdxFalconGenerator. const status_2 = require("@sfdx-falcon/status"); // Class. Implements a framework for creating results-driven, informational objects with a concept of heredity (child results) and the ability to "bubble up" both Errors (thrown exceptions) and application-defined "failures". const status_3 = require("@sfdx-falcon/status"); // Class. Manages status notification messages for SFDX-Falcon Tasks. const status_4 = require("@sfdx-falcon/status"); // Class. Simplifies access to a TaskStatusOptions data structure to help ensure that an external caller can read/set messsages in a way that won't break normal functionality. // Set the File Local Debug Namespace const dbgNs = '@sfdx-falcon:task'; debug_1.SfdxFalconDebug.msg(`${dbgNs}:`, `Debugging initialized for ${dbgNs}`); //─────────────────────────────────────────────────────────────────────────────────────────────────┐ /** * @class ObservableTaskResult * @description Creates the structure needed to wrap the output of any task as an `SfdxFalconResult` * while also managing access to the Observer that Listr uses to monitor the progress * of a task. * @public */ //─────────────────────────────────────────────────────────────────────────────────────────────────┘ class ObservableTaskResult { //───────────────────────────────────────────────────────────────────────────┐ /** * @constructs ObservableTaskResult * @param {ObservableTaskResultOptions} opts Required. Options that * determine how this `ObservableTaskResult` will be constructed. * @description Constructs an `ObservableTaskResult` object. * @public */ //───────────────────────────────────────────────────────────────────────────┘ constructor(opts) { // Set function-local debug namespace and examine incoming arguments. const dbgNsLocal = `${dbgNs}:ObservableTaskResult:constructor`; debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:arguments:`, arguments); // Validate the REQUIRED contents of the options object. validator_1.TypeValidator.throwOnEmptyNullInvalidObject(opts, `${dbgNsLocal}`, `ObservableTaskResultOptions`); validator_1.TypeValidator.throwOnNullInvalidObject(opts.extCtx, `${dbgNsLocal}`, `ObservableTaskResultOptions.extCtx`); validator_1.TypeValidator.throwOnEmptyNullInvalidString(opts.extCtx.dbgNs, `${dbgNsLocal}`, `ObservableTaskResultOptions.extCtx.dbgNs`); validator_1.TypeValidator.throwOnNullInvalidObject(opts.taskCtx, `${dbgNsLocal}`, `ObservableTaskResultOptions.taskCtx`); validator_1.TypeValidator.throwOnEmptyNullInvalidObject(opts.taskObj, `${dbgNsLocal}`, `ObservableTaskResultOptions.taskObj`); validator_1.TypeValidator.throwOnEmptyNullInvalidObject(opts.subscriber, `${dbgNsLocal}`, `ObservableTaskResultOptions.subscriber`); validator_1.TypeValidator.throwOnNullInvalidString(opts.statusMsg, `${dbgNsLocal}`, `ObservableTaskResultOptions.statusMsg`); validator_1.TypeValidator.throwOnNullInvalidNumber(opts.minRuntime, `${dbgNsLocal}`, `ObservableTaskResultOptions.minRuntime`); validator_1.TypeValidator.throwOnNullInvalidBoolean(opts.showTimer, `${dbgNsLocal}`, `ObservableTaskResultOptions.showTimer`); // Validate the OPTIONAL contents of the External Context options object. if (opts.extCtx.sharedData) validator_1.TypeValidator.throwOnNullInvalidObject(opts.extCtx.sharedData, `${dbgNsLocal}`, `ObservableTaskResultOptions.extCtx.sharedData`); if (opts.extCtx.parentResult) validator_1.TypeValidator.throwOnNullInvalidInstance(opts.extCtx.parentResult, status_2.SfdxFalconResult, `${dbgNsLocal}`, `ObservableTaskResultOptions.extCtx.parentResult`); if (opts.extCtx.generatorStatus) validator_1.TypeValidator.throwOnNullInvalidInstance(opts.extCtx.generatorStatus, status_1.GeneratorStatus, `${dbgNsLocal}`, `ObservableTaskResultOptions.extCtx.generatorStatus`); // Validate the DEEPER contents of the options object. validator_1.TypeValidator.throwOnNullInvalidInstance(opts.subscriber, rxjs_2.Subscriber, `${dbgNsLocal}`, `ObservableTaskResultOptions.subscriber`); // Initialize member variables. this._extCtx = opts.extCtx; this._taskCtx = opts.taskCtx; this._taskObj = opts.taskObj; this._subscriber = opts.subscriber; this._minRunTime = opts.minRuntime; this._showTimer = opts.showTimer; // Initialize the Task Status Messages object. this._taskStatusMessages = { defaultMessage: opts.statusMsg, currentMessage: opts.statusMsg, showTimer: this._showTimer, prevMessage: null, nextMessage: null }; // Initialize the Task Status Message object. this._taskStatusMessage = new status_4.TaskStatusMessage(this._taskStatusMessages); // Initialize an SFDX-Falcon Result object. this._taskResult = new status_2.SfdxFalconResult(this._extCtx.dbgNs, "TASK" /* TASK */, { startNow: true, bubbleError: false, bubbleFailure: false }); // Let the parent Result handle failures (no bubbling) debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:_taskResult:`, this._taskResult); // Initialize the Task Result Detail. this._taskResultDetail = { extCtx: this._extCtx, taskCtx: this._taskCtx, taskObj: this._taskObj, statusMsgs: this._taskStatusMessages }; this._taskResult.setDetail(this._taskResultDetail); debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:_taskResult.detail:`, this._taskResult.detail); // Set the initial Task Detail message. if (this._showTimer) { this._subscriber.next(`[0s] ${this._taskStatusMessages.defaultMessage}`); } else { if (this._taskStatusMessages.defaultMessage) { this._subscriber.next(`${this._taskStatusMessages.defaultMessage}`); } } // Set up Task Progress Notifications and store a reference to the Timer. this._notificationTimer = status_3.TaskStatus.start(this._taskStatusMessages, 1000, this._taskResult, this._subscriber); } // Public accessors /** Represents the Status Message for the associated `SfdxFalconTask`. Allows external logic to read and set status messages. */ get status() { return this._taskStatusMessage; } //───────────────────────────────────────────────────────────────────────────┐ /** * @method finalizeFailure * @param {ErrorOrResult} errorOrResult Required. * @returns {void} * @description Finalizes this `ObservableTaskResult` in a manner that * indicates the associated task was NOT successful. * @public */ //───────────────────────────────────────────────────────────────────────────┘ finalizeFailure(errorOrResult) { // Set function-local debug namespace and examine incoming arguments. const dbgNsLocal = `${dbgNs}:ObservableTaskResult:finalizeFailure`; debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `finalizeFailure() has been called`); debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:arguments:`, arguments); // Define a special "subscriber throw" function. const subscriberThrow = (error) => { try { this._subscriber.error(error_1.SfdxFalconError.wrap(error)); } catch (noSubscriberError) { // The only reason we should get here is if this._subscriber lost its Subscriber reference. // If that happens, handle it gracefully by just sending a Debug message and nothing else. debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:subscriberThrow:noSubscriberError`, noSubscriberError); } }; // Validate incoming arguments. if (validator_1.TypeValidator.isEmptyNullInvalidObject(errorOrResult)) { return subscriberThrow(new error_1.SfdxFalconError(`${validator_1.TypeValidator.errMsgEmptyNullInvalidObject(errorOrResult, `errorOrResult`)}`, `TypeError`, `${dbgNsLocal}`)); } if (validator_1.TypeValidator.isInvalidInstance(errorOrResult, Error) && validator_1.TypeValidator.isInvalidInstance(errorOrResult, status_2.SfdxFalconResult)) { return subscriberThrow(new error_1.SfdxFalconError(`Expected errorOrResult to be an instance of Error or SfdxFalconResult but got '${(errorOrResult.constructor) ? errorOrResult.constructor.name : 'unknown'}' instead.`, `TypeError`, `${dbgNsLocal}`)); } // Validate current state of key instance variables. if (validator_1.TypeValidator.isInvalidInstance(this._taskResult, status_2.SfdxFalconResult)) { return subscriberThrow(new error_1.SfdxFalconError(`${validator_1.TypeValidator.errMsgInvalidInstance(this._taskResult, status_2.SfdxFalconResult, `this._taskResult`)}`, `TypeError`, `${dbgNsLocal}`)); } if (validator_1.TypeValidator.isNullInvalidObject(this._taskResult.detail)) { return subscriberThrow(new error_1.SfdxFalconError(`${validator_1.TypeValidator.errMsgNullInvalidObject(this._taskResult.detail, `this._taskResult.detail`)}`, `TypeError`, `${dbgNsLocal}`)); } // Finish any Task Progress Notifications attached to this Observable Task Result. status_3.TaskStatus.finish(this._notificationTimer); // Set the final ERROR state of the Task Result. if (errorOrResult instanceof Error) { this._taskResult.error(errorOrResult); } if (errorOrResult instanceof status_2.SfdxFalconResult) { try { this._taskResult.addChild(errorOrResult); } catch (_a) { // No need to do anything here. We are just suppressing any // bubbled errors from the previous addChild() call. } } // Add the Task Result as a child of the Parent Result (if present). if (this._extCtx.parentResult instanceof status_2.SfdxFalconResult) { try { this._extCtx.parentResult.addChild(this._taskResult); } catch (bubbledError) { // If we get here, it means the parent was set to Bubble Errors. // That means that bubbledError should be an SfdxFalconResult return subscriberThrow(error_1.SfdxFalconError.wrap(bubbledError.errObj)); } } // Finalize the Subscriber with "error" if (errorOrResult instanceof Error) { return subscriberThrow(error_1.SfdxFalconError.wrap(errorOrResult)); } else { if (validator_1.TypeValidator.isNotInvalidInstance(errorOrResult.errObj, Error)) { return subscriberThrow(error_1.SfdxFalconError.wrap(errorOrResult.errObj)); } else { const unexpectedError = new error_1.SfdxFalconError(`Received an Error Result that did not contain an Error Object. See error.detail for more information.`, `MissingErrObj`, `${dbgNsLocal}`, null, errorOrResult); return subscriberThrow(unexpectedError); } } } //───────────────────────────────────────────────────────────────────────────┐ /** * @method finalizeSuccess * @returns {void} * @description Finalizes this `ObservableTaskResult` in a manner that * indicates the associated task was successful. * @public */ //───────────────────────────────────────────────────────────────────────────┘ finalizeSuccess() { // Set function-local debug namespace. const dbgNsLocal = `${dbgNs}:ObservableTaskResult:finalizeSuccess`; debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `finalizeSuccess() has been called`); // Finish any Task Progress Notifications attached to this Observable Task Result. status_3.TaskStatus.finish(this._notificationTimer); // Set the final SUCCESS state of the Task Result. this._taskResult.success(); // Add the Task Result as a child of the Parent Result (if present). if (this._extCtx.parentResult instanceof status_2.SfdxFalconResult) { try { this._extCtx.parentResult.addChild(this._taskResult); } catch (bubbledError) { // If we get here, it means the parent was set to Bubble Errors. // It also means that the Task Result was NOT successful, despite // the fact that somebody called the finalizeSuccess() method. // TODO: Not sure what the right behavior should be here. debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}`, bubbledError); } } // Finalize the Subscriber with "complete" try { this._subscriber.complete(); debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}`, `Subscriber.complete() has been called`); } catch (noSubscriberError) { // The only reason we should get here is if this._subscriber lost its Subscriber reference. // If that happens, handle it gracefully by just sending a Debug message and nothing else. debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:noSubscriberError:`, noSubscriberError); } } //───────────────────────────────────────────────────────────────────────────┐ /** * @method remainingRuntime * @returns {number} * @description Finds out how long it's been since the Task Result started and * subtracts that value from the minimum runtime (`_minRunTime`) * to compute the remaining runtime. * @public */ //───────────────────────────────────────────────────────────────────────────┘ remainingRuntime() { const currentRuntime = this._taskResult.durationSecs; const remainingRuntime = this._minRunTime - currentRuntime; return (remainingRuntime <= 0) ? 0 : Math.ceil(remainingRuntime); } } exports.ObservableTaskResult = ObservableTaskResult; //─────────────────────────────────────────────────────────────────────────────────────────────────┐ /** * @class SfdxFalconTask * @description Abstraction of a single Listr Task with a lot of extra functionality bundled in. * @public */ //─────────────────────────────────────────────────────────────────────────────────────────────────┘ class SfdxFalconTask { //───────────────────────────────────────────────────────────────────────────┐ /** * @constructs SfdxFalconTask * @param {SfdxFalconTaskOptions} opts Required. Options used to * construct this instance of an `SfdxFalconTask` object. * @description Constructs an `SfdxFalconTask` object. * @public */ //───────────────────────────────────────────────────────────────────────────┘ constructor(opts) { // Set function-local debug namespace and examine incoming arguments. const dbgNsLocal = `${dbgNs}:SfdxFalconTask:constructor`; debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:arguments:`, arguments); // Make sure the caller passed in an options object. validator_1.TypeValidator.throwOnEmptyNullInvalidObject(opts, `${dbgNsLocal}`, `opts`); // Build a set of resolved options by mixing what the caller supplied with our defaults. const resolvedOpts = Object.assign({ extCtxReqs: { sharedData: false, parentResult: false, generatorStatus: false }, statusMsg: '', minRuntime: 0, showTimer: false }, opts // Mixin the options provided by the caller. ); // Validate the contents of the resolved options. validator_1.TypeValidator.throwOnEmptyNullInvalidString(resolvedOpts.title, `${dbgNsLocal}`, `SfdxFalconTaskOptions.title`); validator_1.TypeValidator.throwOnNullInvalidFunction(resolvedOpts.task, `${dbgNsLocal}`, `SfdxFalconTaskOptions.task`); if (typeof resolvedOpts.skip !== 'undefined') validator_1.TypeValidator.throwOnNullInvalidFunction(resolvedOpts.skip, `${dbgNsLocal}`, `SfdxFalconTaskOptions.skip`); if (typeof resolvedOpts.enabled !== 'undefined') validator_1.TypeValidator.throwOnNullInvalidFunction(resolvedOpts.enabled, `${dbgNsLocal}`, `SfdxFalconTaskOptions.enabled`); validator_1.TypeValidator.throwOnNullInvalidObject(resolvedOpts.extCtx, `${dbgNsLocal}`, `SfdxFalconTaskOptions.extCtx`); validator_1.TypeValidator.throwOnEmptyNullInvalidString(resolvedOpts.extCtx.dbgNs, `${dbgNsLocal}`, `SfdxFalconTaskOptions.extCtx.dbgNs`); validator_1.TypeValidator.throwOnEmptyNullInvalidObject(resolvedOpts.extCtxReqs, `${dbgNsLocal}`, `SfdxFalconTaskOptions.extCtxReqs`); if (resolvedOpts.extCtxReqs.sharedData || typeof resolvedOpts.extCtx.sharedData !== 'undefined') validator_1.TypeValidator.throwOnNullInvalidObject(resolvedOpts.extCtx.sharedData, `${dbgNsLocal}`, `SfdxFalconTaskOptions.extCtx.sharedData`); if (resolvedOpts.extCtxReqs.parentResult || typeof resolvedOpts.extCtx.parentResult !== 'undefined') validator_1.TypeValidator.throwOnNullInvalidInstance(resolvedOpts.extCtx.parentResult, status_2.SfdxFalconResult, `${dbgNsLocal}`, `SfdxFalconTaskOptions.extCtx.parentResult`); if (resolvedOpts.extCtxReqs.generatorStatus || typeof resolvedOpts.extCtx.generatorStatus !== 'undefined') validator_1.TypeValidator.throwOnNullInvalidInstance(resolvedOpts.extCtx.generatorStatus, status_1.GeneratorStatus, `${dbgNsLocal}`, `SfdxFalconTaskOptions.extCtx.generatorStatus`); validator_1.TypeValidator.throwOnNullInvalidString(resolvedOpts.statusMsg, `${dbgNsLocal}`, `SfdxFalconTaskOptions.statusMsg`); validator_1.TypeValidator.throwOnNullInvalidNumber(resolvedOpts.minRuntime, `${dbgNsLocal}`, `SfdxFalconTaskOptions.minRuntime`); validator_1.TypeValidator.throwOnNullInvalidBoolean(resolvedOpts.showTimer, `${dbgNsLocal}`, `SfdxFalconTaskOptions.showTimer`); // Initialize Listr-specific member variables. this._title = resolvedOpts.title; this._extTask = resolvedOpts.task; this._extSkip = resolvedOpts.skip; this._extEnabled = resolvedOpts.enabled; // Initialize SFDX-Falcon Task-specific variables. this._extCtx = resolvedOpts.extCtx; this._statusMsg = resolvedOpts.statusMsg; this._minRuntime = resolvedOpts.minRuntime; this._showTimer = resolvedOpts.showTimer; } //───────────────────────────────────────────────────────────────────────────┐ /** * @method build * @returns {ListrTask} * @description Builds an Observable `ListrTask` object based on the info * that the caller provided to us when this `SfdxFalconTask` * object was constructed. * @public */ //───────────────────────────────────────────────────────────────────────────┘ build() { // Set function-local debug namespace. const dbgNsLocal = `${dbgNs}:SfdxFalconTask:build`; // Build the specialized Observer based task. this._task = (taskCtx, taskObj) => { return new rxjs_1.Observable((subscriber) => { // Initialize an OTR (Observable Task Result). this._otr = new ObservableTaskResult({ taskCtx: taskCtx, taskObj: taskObj, subscriber: subscriber, extCtx: this._extCtx, statusMsg: this._statusMsg, minRuntime: this._minRuntime, showTimer: this._showTimer }); // Initialize the External Task that's associated with this instance. let extTaskPromise; try { // Hoist the External Task function into the Listr task we're creating. extTaskPromise = this._extTask(taskCtx, taskObj, this._otr.status, this._extCtx); } catch (error) { const extTaskSetupError = new error_1.SfdxFalconError(`External Task could not be initialized. ${error.message}`, `TaskFunctionInitError`, `${dbgNsLocal}`, error, this._extTask); return this._otr.finalizeFailure(extTaskSetupError); } // Make sure the External Task returned a Promise. if ((extTaskPromise instanceof Promise) !== true) { const invalidExtTaskFunctionError = new error_1.SfdxFalconError(`SFDX-Falcon Task Functions must be asynchronous and return Promise<void>`, `InvalidTaskFunction`, `${dbgNsLocal}`, null, this._extTask); return this._otr.finalizeFailure(invalidExtTaskFunctionError); } // Set THEN and CATCH callbacks for the External Task. extTaskPromise .then(async () => { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `extTaskPromise() has been RESOVLED.`); // Make sure the task appears to run for the minimum runtime. await util_1.AsyncUtil.waitASecond(this._otr.remainingRuntime()); // Finalize the OTR as a SUCCESS. return this._otr.finalizeSuccess(); }) .catch(async (rejectedResult) => { debug_1.SfdxFalconDebug.msg(`${dbgNsLocal}:`, `extTaskPromise() has been REJECTED.`); debug_1.SfdxFalconDebug.obj(`${dbgNsLocal}:rejectedResult:`, { rejectedResult: rejectedResult }); // Make sure the task appears to run for the minimum runtime. await util_1.AsyncUtil.waitASecond(this._otr.remainingRuntime()); // Declare variables used to construct an Error. let errorMessage; let errorCause; let errorDetail; // Determine the Error Message, Cause, and Detail based on what kind of Rejected Result we got. if (rejectedResult instanceof status_2.SfdxFalconResult) { errorMessage = `Task Function failed and returned the SfdxFalconResult '${rejectedResult.name}'.`; errorCause = rejectedResult.errObj; errorDetail = rejectedResult; } else if (rejectedResult instanceof Error) { errorMessage = `Task Function failed with the following error: ${rejectedResult.message}`; errorCause = rejectedResult; errorDetail = {}; } else { errorMessage = `Task Function failed with an unexpected result.`; errorCause = undefined; errorDetail = { rejectedResult: rejectedResult }; } // Construct the Task Error. const taskError = new error_1.SfdxFalconError(`${errorMessage}`, `TaskFunctionError`, `${dbgNsLocal}`, errorCause, errorDetail); // Finalize the OTR as a FAILURE. return this._otr.finalizeFailure(taskError); }); }); }; // Build the SKIP and ENABLED functions if (typeof this._extSkip === 'function') { this._skip = (taskCtx) => this._extSkip(taskCtx, this._extCtx); } if (typeof this._extEnabled === 'function') { this._enabled = (taskCtx) => this._extEnabled(taskCtx, this._extCtx); } // Construct and return a ListrTask return { title: this._title, task: this._task, enabled: this._enabled, skip: this._skip }; } } exports.SfdxFalconTask = SfdxFalconTask; //# sourceMappingURL=task.js.map