file copyright author Vivek M. Chawla <@VivekMChawla>
 *  summary description version license import { isEmpty } from 'lodash' ; import { SfdxFalconDebug } from '../sfdx-falcon-debug' ; import { SfdxFalconError } from '../sfdx-falcon-error' ; const chalk = require ( 'chalk' ) ; const inquirer = require ( 'inquirer' ) ; const util = require ( 'util' ) ; const dbgNs = 'MODULE:sfdx-falcon-result:' ; interface description export interface SfdxFalconResultDisplayOptions { displayResult ? : boolean ; contextLabel : string ; detailInspectDepth : number ; childInspectDepth : number ; errorInspectDepth : number ; } interface extends SfdxFalconResultDisplayOptions description export interface SfdxFalconResultRenderOptions extends SfdxFalconResultDisplayOptions { headerColor : string ; labelColor : string ; errorLabelColor : string ; valueColor : string ; } interface description export interface SfdxFalconResultOptions { startNow ? : boolean ; bubbleError ? : boolean ; bubbleFailure ? : boolean ; failureIsError ? : boolean ; } enum SfdxFalconResultStatus description export const enum SfdxFalconResultStatus { INITIALIZED = 'INITIALIZED' , WAITING = 'WAITING' , SUCCESS = 'SUCCESS' , FAILURE = 'FAILURE' , WARNING = 'WARNING' , ERROR = 'ERROR' , UNKNOWN = 'UNKNOWN' } enum SfdxFalconResultType description export const enum SfdxFalconResultType { ACTION = 'ACTION' , COMMAND = 'COMMAND' , ENGINE = 'ENGINE' , EXECUTOR = 'EXECUTOR' , FUNCTION = 'FUNCTION' , GENERATOR = 'GENERATOR' , INQUIRER = 'INQUIRER' , LISTR = 'LISTR' , RECIPE = 'RECIPE' , UNKNOWN = 'UNKNOWN' , UTILITY = 'UTILITY' } class description public export class SfdxFalconResult { method param { object } objectToCheck returns { void } description public static public static isValid ( objectToCheck ) { if ( objectToCheck instanceof SfdxFalconResult ) { return ; } const falconError = new SfdxFalconError ( `ERROR_INVALID_RESULT: Expected an SfdxFalconResult` , 'SfdxFalconError' ) ; falconError . message += ( objectToCheck . constructor ) ? ` but got '${ objectToCheck . constructor . name }'` : '' ; falconError . data = { unknownObject : objectToCheck } ; throw falconError ; } method param { unknown } objectToCheck param { SfdxFalconResultType } expectedResultType param { SfdxFalconResultStatus } status returns { boolean } description public static public static validate ( objectToCheck , expectedType ? , expectedStatus ? ) { if ( ( objectToCheck instanceof SfdxFalconResult ) !== true ) { return false ; } if ( expectedType ) { if ( ( objectToCheck as SfdxFalconResult ) . type !== expectedType ) { return false ; } } if ( expectedStatus ) { if ( ( objectToCheck as SfdxFalconResult ) . status !== expectedStatus ) { return false ; } } return true ; } method param { unknown } thingToWrap param { SfdxFalconResultType } resultType param { string } resultName param { string } resultSource returns { SfdxFalconResult } description public static public static wrap ( thingToWrap , resultType , resultName , resultSource ? ) { if ( thingToWrap instanceof SfdxFalconResult ) { return thingToWrap ; } resultType = resultType || SfdxFalconResultType . UNKNOWN ; resultName = resultName || `Unknown` ; resultSource = resultSource || `${ resultType }:${ resultName }` ; if ( thingToWrap instanceof Error ) { const falconError = SfdxFalconError . wrap ( thingToWrap , resultSource ) ; const newFalconErrorResult = new SfdxFalconResult ( resultName , resultType ) ; newFalconErrorResult . error ( falconError ) ; return newFalconErrorResult ; } const newFalconResult = new SfdxFalconResult ( resultName , resultType ) ; newFalconResult . setDetail ( thingToWrap ) ; newFalconResult . unknown ( ) ; return newFalconResult ; } method param { unknown } rejectedPromiseData param { SfdxFalconResultType } resultType param { string } resultName param { string } resultSource returns { SfdxFalconResult } description public static public static wrapRejectedPromise ( rejectedPromiseData , resultType , resultName , resultSource ? ) { if ( rejectedPromiseData instanceof SfdxFalconResult ) { return rejectedPromiseData ; } resultType = resultType || SfdxFalconResultType . UNKNOWN ; resultName = resultName || `Unknown` ; resultSource = resultSource || `${ resultType }:${ resultName }` ; const rejectedPromiseError = SfdxFalconError . wrap ( rejectedPromiseData , resultSource ) ; return SfdxFalconResult . wrap ( rejectedPromiseError , resultType , resultName , resultSource ) ; } method param { SfdxFalconResult } result param { SfdxFalconResultRenderOptions } options returns { void } description private static private static renderActionDetail ( result , options ) { return SfdxFalconResult . renderAnyDetail ( result , options ) ; } method param { SfdxFalconResult } result param { SfdxFalconResultRenderOptions } options returns { string } description private static private static renderAnyDetail ( result , options ) { let renderResult = '' ; if ( isEmpty ( result . children ) === false && options . childInspectDepth >= 3 ) { renderResult += chalk `\n{${ options . labelColor } Child Results: (Depth=${ options . childInspectDepth })}\n{reset ${ util . inspect ( result . children , { depth : options . childInspectDepth , colors : true } ) }}` ; } return renderResult ; } method param { SfdxFalconResult } result param { SfdxFalconResultRenderOptions } options returns { string } description private static private static renderBaseDetail ( result , options ) { let renderResult = chalk `\n{${ options . headerColor } ${ result . type }_RESULT_DEBUG${ options . contextLabel ? ' (' + options . contextLabel + ')' : '' }:}` + chalk `\n{${ options . labelColor } Result Type:}        {${ options . valueColor } ${ result . type }}` + chalk `\n{${ options . labelColor } Result Name:}        {${ options . valueColor } ${ result . name }}` + chalk `\n{${ options . labelColor } Result Status:}      {${ options . valueColor } ${ result . status }}` + chalk `\n{${ options . labelColor } Result Start Time:}  {${ options . valueColor } ${ result . _startTime } (UTC)}` + chalk `\n{${ options . labelColor } Result End Time:}    {${ options . valueColor } ${ result . _endTime } (UTC)}` + chalk `\n{${ options . labelColor } Result Duration:}    {${ options . valueColor } ${ result . duration / 1000 } seconds}` + chalk `\n{${ options . labelColor } Number of Children:} {${ options . valueColor } ${ result . children . length }}` ; if ( isEmpty ( result . detail ) === false ) { renderResult += chalk `\n{${ options . labelColor } Result Detail: (Depth=${ options . detailInspectDepth })}\n{reset ${ util . inspect ( result . detail , { depth : options . detailInspectDepth , colors : true } ) }}` ; } return renderResult ; } method param { SfdxFalconResult } result param { SfdxFalconResultRenderOptions } options returns { string } description private static private static renderErrorDetail ( result , options ) { if ( ( ( result . errObj instanceof Error ) === false ) || isEmpty ( result . errObj ) ) { return '' ; } let renderResult = '' ; const separator = '---------------' ; const errors = [ ] as SfdxFalconError [ ] ; let currentError = result . errObj ; let foundLastError = false ; while ( foundLastError === false ) { errors . push ( currentError ) ; if ( currentError . cause instanceof Error ) { currentError = currentError . cause as SfdxFalconError ; } else { foundLastError = true ; } } for ( let i = 0 ; i < errors . length ; i ++ ) { renderResult += chalk `\n{yellow ${ separator }--------------${ separator }}` ; renderResult += chalk `\n{yellow ${ separator } ERROR ${ errors . length - i } of ${ errors . length } ${ separator }}` ; renderResult += chalk `\n{yellow ${ separator }--------------${ separator }}` ; renderResult += SfdxFalconError . renderError ( errors [ i ] , options . childInspectDepth , options . detailInspectDepth , options . errorInspectDepth ) ; } renderResult += chalk `\n{yellow ${ separator }---------------${ separator }}` ; renderResult += chalk `\n{yellow ${ separator } END OF ERRORS ${ separator }}` ; renderResult += chalk `\n{yellow ${ separator }---------------${ separator }}` ; return renderResult ; } public name : string ; public type : SfdxFalconResultType ; public children : SfdxFalconResult [ ] ; private _detail : unknown ; private _errObj : SfdxFalconError ; private _bubbleError : boolean ; private _bubbleFailure : boolean ; private _failureIsError : boolean ; private _status : SfdxFalconResultStatus ; private _startTime : number ; private _endTime : number ; public get detail ( ) : object { return this . _detail as object ; } public set detail ( incomingDetail ) { this . setDetail ( incomingDetail ) ; } public get duration ( ) : number { if ( this . _startTime === 0 ) { return 0 ; } if ( this . _endTime !== 0 ) { return this . _endTime - this . _startTime ; } return ( new Date ( ) . getTime ( ) - this . _startTime ) ; } public get durationSecs ( ) : number { return this . duration / 1000 ; } public get durationString ( ) : string { const durationSeconds = Math . floor ( this . durationSecs ) ; if ( durationSeconds < 60 ) return `${ durationSeconds }s` ; return `${ Math . floor ( durationSeconds / 60 ) }m ${ durationSeconds % 60 }s` ; } public get errObj ( ) : SfdxFalconError { return this . _errObj ; } public get resultTypeErrorName ( ) : string { switch ( this . type ) { case SfdxFalconResultType . ACTION : return 'FailedAction' ; case SfdxFalconResultType . COMMAND : return 'FailedCommand' ; case SfdxFalconResultType . ENGINE : return 'FailedEngine' ; case SfdxFalconResultType . EXECUTOR : return 'FailedExecutor' ; case SfdxFalconResultType . INQUIRER : return 'FailedInquirer' ; case SfdxFalconResultType . LISTR : return 'FailedListr' ; case SfdxFalconResultType . RECIPE : return 'FailedRecipe' ; case SfdxFalconResultType . UNKNOWN : return 'UnknownFailure' ; case SfdxFalconResultType . UTILITY : return 'FailedUtility' ; case SfdxFalconResultType . FUNCTION : return 'FailedFunction' ; default : return 'UnknownFailure' ; } } public get lastChild ( ) : SfdxFalconResult { return this . children [ this . children . length - 1 ] ; } public get status ( ) : SfdxFalconResultStatus { return this . _status ; } constructs param { string } name param { SfdxFalconResultType } type param { SfdxFalconResultOptions } options description public public constructor ( name , type , options = { } ) { this . name = name ; this . type = type ; this . children = new Array < SfdxFalconResult > ( ) ; this . _detail = { } as unknown ; this . _errObj = { } as SfdxFalconError ; this . _status = SfdxFalconResultStatus . INITIALIZED ; this . _startTime = 0 ; this . _endTime = 0 ; const resolvedOptions = { startNow : true , bubbleError : true , bubbleFailure : true , failureIsError : true , ... options } as SfdxFalconResultOptions ; if ( resolvedOptions . startNow ) { this . start ( ) ; } this . _bubbleError = ( resolvedOptions . bubbleError ) ? true : false ; this . _bubbleFailure = ( resolvedOptions . bubbleFailure ) ? true : false ; this . _failureIsError = ( resolvedOptions . failureIsError ) ? true : false ; } method param { unknown } incomingDetail returns { void } description public public setDetail ( incomingDetail = null ) { if ( incomingDetail == null || typeof incomingDetail === 'undefined' ) { return ; } if ( incomingDetail instanceof Error ) { this . error ( incomingDetail ) ; return ; } if ( typeof incomingDetail !== 'object' ) { this . _detail = { rawResult : `${ incomingDetail }` } ; } else { this . _detail = incomingDetail ; } return ; } method returns { this } description public public start ( ) { if ( this . _startTime === 0 ) { const d = new Date ( ) ; this . _startTime = d . getTime ( ) ; } if ( this . _status === SfdxFalconResultStatus . INITIALIZED ) { this . _status = SfdxFalconResultStatus . WAITING ; } return this ; } method returns { this } description public public success ( ) { this . finish ( SfdxFalconResultStatus . SUCCESS ) ; return this ; } method param { unknown } anyResult returns { void } description public public throw ( anyResult ) { this . error ( anyResult ) ; throw this ; } method returns { this } description public public warning ( ) { this . finish ( SfdxFalconResultStatus . WARNING ) ; return this ; } method returns { this } description public public unknown ( ) { this . finish ( SfdxFalconResultStatus . UNKNOWN ) ; return this ; } method param { SfdxFalconResult } childResult param { boolean } requireValidChildResult returns { this } description public public addChild ( childResult , requireValidChildResult = true ) { SfdxFalconDebug . obj ( `${ dbgNs }${ this . type }:addChild` , childResult , `childResult: ` ) ; if ( requireValidChildResult ) { SfdxFalconResult . isValid ( childResult ) ; } if ( ( childResult instanceof SfdxFalconResult ) !== true ) { childResult = SfdxFalconResult . wrap ( childResult , SfdxFalconResultType . UNKNOWN , 'UNKNOWN RESULT' ) ; return this . addChild ( childResult ) ; } this . start ( ) ; this . children . push ( childResult ) ; if ( childResult . status === SfdxFalconResultStatus . FAILURE && this . _failureIsError === false ) { if ( this . _bubbleFailure ) { const failureErrObject = new SfdxFalconError ( `${ this . type }:${ this . name } has failed because the child operation ` + `${ childResult . type }:${ childResult . name } was marked as FAILED ` + `${ ( childResult . errObj instanceof Error ) ? '(' + childResult . errObj . name + ')' : '' }` , this . resultTypeErrorName , `${ dbgNs }addChild` , ( childResult . errObj instanceof Error ) ? SfdxFalconError . wrap ( childResult . errObj ) : null ) ; failureErrObject . setDetail ( { resultStatus : 'BUBBLED_FAILURE' , resultDuration : this . duration + ' ms' } ) ; failureErrObject . setData ( { sfdxFalconResult : this } ) ; this . failure ( failureErrObject ) ; } else { this . _status = SfdxFalconResultStatus . WARNING ; } } else if ( childResult . status === SfdxFalconResultStatus . ERROR || ( childResult . status === SfdxFalconResultStatus . FAILURE && this . _failureIsError === true ) ) { if ( this . _bubbleError ) { const errorErrObject = new SfdxFalconError ( `${ this . type }:${ this . name } has failed because the child operation ` + `${ childResult . type }:${ childResult . name } threw an error ` + `${ ( childResult . errObj instanceof Error ) ? '(' + childResult . errObj . name + ')' : '' }` , this . resultTypeErrorName , `${ dbgNs }addChild` , ( childResult . errObj instanceof Error ) ? SfdxFalconError . wrap ( childResult . errObj ) : null ) ; errorErrObject . setDetail ( { resultStatus : ( childResult . status === SfdxFalconResultStatus . FAILURE ) ? 'BUBBLED_FAILURE_AS_ERROR' : 'BUBBLED_ERROR' , resultDuration : this . duration + ' ms' } ) ; errorErrObject . setData ( { sfdxFalconResult : this } ) ; this . throw ( errorErrObject ) ; } else { this . _status = SfdxFalconResultStatus . WARNING ; } } return this ; } method param { unknown } rejectedPromise param { SfdxFalconResultType } resultType param { string } resultName param { string } resultSource returns { this } description public public addRejectedChild ( rejectedPromise , resultType , resultName , resultSource ? ) { SfdxFalconDebug . obj ( `${ dbgNs }${ this . type }:addRejectedChild:` , { rejectedPromise : rejectedPromise } , `rejectedPromise: ` ) ; if ( typeof resultSource === 'undefined' ) { resultSource = `${ resultType }:${ resultName }` ; } const rejectedChild = SfdxFalconResult . wrap ( rejectedPromise , resultType , resultName , resultSource ) ; rejectedChild . debugResult ( `Rejected Child Result` , `${ dbgNs }addRejectedChild:` ) ; return this . addChild ( rejectedChild ) ; } method param { unknown } resolvedPromise param { SfdxFalconResultType } resultType param { string } resultName param { string } resultSource returns { this } description public public addResolvedChild ( resolvedPromise , resultType , resultName , resultSource ? ) { SfdxFalconDebug . obj ( `${ dbgNs }${ this . type }:addResolvedChild:` , { resolvedPromise : resolvedPromise } , `resolvedPromise: ` ) ; if ( typeof resultSource === 'undefined' ) { resultSource = `${ resultType }:${ resultName }` ; } const resolvedChild = SfdxFalconResult . wrap ( resolvedPromise , resultType , resultName , resultSource ) ; resolvedChild . debugResult ( `Resolved Child Result` , `${ dbgNs }addResolvedChild:` ) ; return this . addChild ( resolvedChild ) ; } method param { string } contextLabel param { string } debugNamespace param { number } childInspectDepth param { number } detailInspectDepth param { number } errorInspectDepth returns { void } description public public debugResult ( contextLabel = '' , debugNamespace = '' , childInspectDepth = 1 , detailInspectDepth = 4 , errorInspectDepth = 1 ) { const debugOutput = '-\n' + this . renderResult ( contextLabel , childInspectDepth , detailInspectDepth , errorInspectDepth ) + '\n-\n-\n-\n-\n-\n-\n-\n-\n-\n-' ; if ( debugNamespace ) { SfdxFalconDebug . msg ( `${ debugNamespace }` , debugOutput ) ; } else { SfdxFalconDebug . debugMessage ( `${ this . type }_RESULT:` , debugOutput ) ; } } method param { boolean } showErrorDebug param { boolean } promptUser returns { Promise < void > } description public async public async displayErrorDebugInfo ( showErrorDebug , promptUser ) { if ( showErrorDebug !== true ) { return ; } const displayOptions = { displayResult : true , contextLabel : '' , detailInspectDepth : 2 , childInspectDepth : 2 , errorInspectDepth : 1 } as SfdxFalconResultDisplayOptions ; if ( promptUser === true ) { const iqPromptOne = [ { type : 'confirm' , name : 'showDebug' , default : true , message : `${ chalk . red ( 'ERROR DETECTED:' ) } Would you like to view detailed debug information?` , when : true } ] ; console . log ( '' ) ; const userSelectionsOne = await inquirer . prompt ( iqPromptOne ) ; if ( userSelectionsOne . showDebug !== true ) { return ; } } do { this . displayResult ( displayOptions ) ; } while ( false ) ; return ; } method param { SfdxFalconResultDisplayOptions } displayOptions returns { void } description public public displayResult ( displayOptions = null ) { if ( typeof displayOptions !== 'object' ) { displayOptions = { } as SfdxFalconResultDisplayOptions ; } displayOptions . displayResult = displayOptions . displayResult || true ; displayOptions . contextLabel = displayOptions . contextLabel || '' ; displayOptions . detailInspectDepth = displayOptions . detailInspectDepth || 2 ; displayOptions . childInspectDepth = displayOptions . childInspectDepth || 2 ; displayOptions . errorInspectDepth = displayOptions . errorInspectDepth || 1 ; if ( displayOptions . displayResult !== true ) { return ; } console . log ( this . renderResult ( displayOptions . contextLabel , displayOptions . childInspectDepth , displayOptions . detailInspectDepth , displayOptions . errorInspectDepth ) ) ; } method param { Error | unknown } errorObject returns { this } description public public error ( errorObject ) { if ( ( errorObject instanceof Error ) !== true ) { if ( typeof errorObject === 'string' ) { errorObject = new Error ( errorObject ) ; } else { errorObject = new Error ( `An unknown error occured while building results for ${ this . name }` ) ; } } const falconError = SfdxFalconError . wrap ( errorObject ) ; falconError . addToStack ( `ERROR at '${ this . type }:${ this . name }' caused by '${ falconError . source }' (duration ${ this . duration }ms)` ) ; this . _errObj = falconError ; this . finish ( SfdxFalconResultStatus . ERROR ) ; return this ; } method param { Error | unknown } errorObject returns { this } description public public failure ( errorObject ) { let falconError = null ; if ( errorObject instanceof Error ) { falconError = SfdxFalconError . wrap ( errorObject ) ; } else { falconError = new SfdxFalconError ( `Unknown failure state set in ${ this . type } Result '${ this . name }'.` , `UnknownFailure` ) ; falconError . setData ( { rawResult : errorObject } ) ; } falconError . addToStack ( `FAILURE at '${ this . type }:${ this . name }' caused by '${ falconError . source }' (duration ${ this . duration }ms)` ) ; this . _errObj = falconError ; this . finish ( SfdxFalconResultStatus . FAILURE ) ; return this ; } method param { SfdxFalconResultStatus } finalStatus returns { SfdxFalconResultStatus } description private private finish ( finalStatus = SfdxFalconResultStatus . UNKNOWN ) { if ( finalStatus === SfdxFalconResultStatus . INITIALIZED || finalStatus === SfdxFalconResultStatus . WAITING ) { throw new SfdxFalconError ( `ERROR_INVALID_ARGUMENT: SfdxFalconResult.finish() does ` + `not accept INITIALIZED or WAITING for Final Status` ) ; } this . start ( ) ; switch ( finalStatus ) { case SfdxFalconResultStatus . ERROR : this . _status = finalStatus ; break ; case SfdxFalconResultStatus . FAILURE : if ( this . _status !== SfdxFalconResultStatus . ERROR ) { this . _status = finalStatus ; } break ; case SfdxFalconResultStatus . WARNING : if ( this . _status !== SfdxFalconResultStatus . ERROR && this . _status !== SfdxFalconResultStatus . FAILURE ) { this . _status = finalStatus ; } break ; case SfdxFalconResultStatus . SUCCESS : if ( this . _status !== SfdxFalconResultStatus . ERROR && this . _status !== SfdxFalconResultStatus . FAILURE && this . _status !== SfdxFalconResultStatus . WARNING ) { this . _status = finalStatus ; } break ; case SfdxFalconResultStatus . UNKNOWN : if ( this . _status !== SfdxFalconResultStatus . ERROR && this . _status !== SfdxFalconResultStatus . FAILURE && this . _status !== SfdxFalconResultStatus . WARNING && this . _status !== SfdxFalconResultStatus . SUCCESS ) { this . _status = finalStatus ; } break ; } if ( this . _endTime === 0 && this . _startTime !== 0 ) { const d = new Date ( ) ; this . _endTime = d . getTime ( ) ; } return this . _status ; } method param { string } contextLabel param { number } childInspectDepth param { number } detailInspectDepth param { number } errorInspectDepth returns { string } description private private renderResult ( contextLabel = '' , childInspectDepth = 1 , detailInspectDepth = 4 , errorInspectDepth = 1 ) { const renderOptions = { headerColor : `yellow` , labelColor : `blue` , errorLabelColor : `red` , valueColor : `reset` , contextLabel : contextLabel , childInspectDepth : childInspectDepth , detailInspectDepth : detailInspectDepth , errorInspectDepth : errorInspectDepth } ; let renderOutput = SfdxFalconResult . renderBaseDetail ( this , renderOptions ) ; renderOutput += SfdxFalconResult . renderErrorDetail ( this , renderOptions ) ; switch ( this . type ) { case SfdxFalconResultType . ACTION : renderOutput += SfdxFalconResult . renderActionDetail ( this , renderOptions ) ; break ; default : renderOutput += SfdxFalconResult . renderAnyDetail ( this , renderOptions ) ; break ; } renderOutput += '\n' ; return renderOutput ; } }