import { join } from 'path' ; import { TaskAction , CheckupError , CheckupConfig , ErrorKind , FilePathArray , getRegisteredActions , getConfigPath , getFilePathsAsync , getPackageJson , getPackageJsonSource , readConfig , RunOptions , Task , TaskContext , TaskActionsEvaluator , TaskListError , TaskName , TaskFormatter , RegistrationArgs , CheckupLogBuilder , } from '@checkup/core' ; import * as debug from 'debug' ; import * as resolve from 'resolve' ; import { Log , Result } from 'sarif' ; import { PackageJson } from 'type-fest' ; import TaskListImpl from '../task-list' ; import PluginRegistrationProvider from './registration-provider' ; /**
 * Class that is able to run a list of checkup tasks.
 */ export default class CheckupTaskRunner { actions : TaskAction [ ] ; config ! : CheckupConfig ; debug : debug . Debugger ; executedTasks ! : Task [ ] ; options : RunOptions ; plugins : [ ] ; startTime : string = new Date ( ) . toJSON ( ) ; tasks : TaskListImpl ; taskResults : Result [ ] ; taskErrors : TaskListError [ ] ; taskContext ! : TaskContext ; logBuilder : CheckupLogBuilder ; registeredActions : Map < string , TaskActionsEvaluator > = new Map < TaskName , TaskActionsEvaluator > ( ) ; registeredTaskReporters : Map < string , TaskFormatter > = new Map < TaskName , TaskFormatter > ( ) ; pkg : PackageJson ; pkgSource : string ; return { ErrorKind } get taskErrorKind ( ) { if ( this . options . tasks !== undefined ) { return ErrorKind . TasksNotFound ; } else if ( this . options . categories !== undefined ) { return ErrorKind . TaskCategoriesNotFound ; } else if ( this . options . groups !== undefined ) { return ErrorKind . TaskGroupsNotFound ; } return ErrorKind . None ; } return { boolean } get hasTaskFilter ( ) { return [ this . options . tasks , this . options . categories , this . options . groups ] . some ( ( taskFilterType ) => taskFilterType !== undefined && taskFilterType . length > 0 ) ; } param { RunOptions } options param { string } options . cwd param { CheckupConfig } options . config param { string } options . configPath param { string [ ] } options . categories param { string [ ] } options . excludePaths param { string [ ] } options . groups param { boolean } options . listTasks param { string [ ] } options . tasks param { string } options . pluginBaseDir constructor ( options ) { this . debug = debug ( 'checkup' ) ; this . actions = [ ] ; this . plugins = [ ] ; this . tasks = new TaskListImpl ( ) ; this . taskResults = [ ] ; this . taskErrors = [ ] ; this . executedTasks = [ ] ; this . options = options ; this . pkg = getPackageJson ( this . options . cwd ) ; this . pkgSource = getPackageJsonSource ( this . options . cwd ) ; this . logBuilder = new CheckupLogBuilder ( { analyzedPackageJson : this . pkg , options : this . options , } ) ; this . debug ( 'options %O' , this . options ) ; } async run ( ) { await this . loadConfig ( ) ; await this . registerPlugins ( ) ; await this . runTasks ( ) ; await this . runActions ( ) ; await this . logBuilder . annotate ( { config : this . config , actions : this . actions , errors : this . taskErrors , timings : this . tasks . timings , executedTasks : this . executedTasks , } ) ; return this . logBuilder . log ; } return async getAvailableTasks ( ) { await this . loadConfig ( ) ; await this . registerPlugins ( ) ; return this . tasks . fullyQualifiedTaskNames ; } private async runTasks ( ) { if ( this . hasTaskFilter ) { let { tasksFound , tasksNotFound } = this . findTasks ( ) ; if ( tasksFound . length > 0 ) { [ this . taskResults , this . taskErrors ] = await this . tasks . runTasks ( tasksFound ) ; } if ( tasksNotFound . length > 0 ) { throw new CheckupError ( this . taskErrorKind , { tasksNotFound } ) ; } this . executedTasks = tasksFound ; } else { [ this . taskResults , this . taskErrors ] = await this . tasks . runTasks ( ) ; this . executedTasks = this . tasks . getTasks ( ) ; } } private findTasks ( ) { if ( this . options . tasks !== undefined && this . options . tasks . length > 0 ) { return this . tasks . findAllByTaskName ( ... this . options . tasks ) ; } else if ( this . options . categories !== undefined && this . options . categories . length > 0 ) { return this . tasks . findAllByCategory ( ... this . options . categories ) ; } else if ( this . options . groups !== undefined && this . options . groups . length > 0 ) { return this . tasks . findAllByGroup ( ... this . options . groups ) ; } return { tasksFound : [ ] , tasksNotFound : [ ] } ; } private runActions ( ) { return new Promise < void > ( ( resolve , reject ) => { let evaluators = getRegisteredActions ( ) ; for ( let [ taskName , evaluator ] of evaluators ) { let task = this . tasks . findTask ( taskName ) ; let taskResult = this . taskResults . filter ( ( result ) => { return result . properties ?. taskName === taskName ; } ) ; if ( task && taskResult . length > 0 ) { try { this . actions . push ( ... evaluator ( taskResult , task . config ) ) ; } catch ( error ) { reject ( error ) ; } } } resolve ( ) ; } ) ; } private async loadConfig ( ) { try { let configPath = await getConfigPath ( this . options . configPath , this . options . cwd ) ; this . config = this . options . config || readConfig ( configPath ) ; } catch ( error ) { if ( error instanceof CheckupError ) { throw error ; } throw new CheckupError ( ErrorKind . ConfigNotValid , { error } ) ; } } private async registerPlugins ( ) { let excludePaths = this . options . excludePaths || this . config . excludePaths ; let paths = FilePathArray . from ( await getFilePathsAsync ( this . options . cwd , this . options . paths || [ '.' ] , excludePaths ) ) as FilePathArray ; this . logBuilder . args . paths = paths ; this . taskContext = Object . freeze ( { options : this . options , config : this . config , logBuilder : this . logBuilder , pkg : this . pkg , pkgSource : this . pkgSource , paths , } ) ; await this . loadFromPlugin ( { context : this . taskContext , register : new PluginRegistrationProvider ( { registeredActions : this . registeredActions , registeredTaskReporters : this . registeredTaskReporters , registeredTasks : this . tasks , } ) , } ) ; } private async loadFromPlugin ( registrationArgs ) { let pluginBaseDir = this . options . pluginBaseDir || this . options . cwd ; for ( let pluginName of this . config . plugins ) { let pluginDir ; try { pluginDir = resolve . sync ( pluginName , { basedir : pluginBaseDir } ) ; } catch { pluginDir = join ( pluginBaseDir , pluginName ) ; } this . debug ( 'Loading plugin from %s' , pluginDir ) ; let { register } = require ( pluginDir ) ; await register ( registrationArgs ) ; } } }