import { RequestError } from "@octokit/request-error" ; import webhooks from "@octokit/webhooks" ; import * as probot from "probot" ; import { ProbotOctokit } from "probot" ; import * as config from "./config" ; import { formatFiles } from "./format-files" ; import * as github from "./github" ; import { pretendToUse } from "./helpers/pretend-to-use" ; import { concatToSet , removeAllFromSet } from "./helpers/set-tools" ; import { DevError , logDevError } from "./logging/dev-error" ; import { LoggedError } from "./logging/logged-error" ; import { logUserError , UserError } from "./logging/user-error" ; import * as prettier from "./prettier" ; interface PushState { readonly author : string ; readonly branch : string ; readonly commitSha : string ; defaultBranchName : string ; readonly octokit : InstanceType < typeof ProbotOctokit > ; readonly org : string ; prettierConfig : prettier . Options ; prettierIgnore : string ; prettifierConfig : config . Data ; pullRequestId : string ; pullRequestNumber : number ; readonly repo : string ; } /** called when this bot gets notified about a push on Github */ export async function onPush ( context ) { let state ; let changedFiles ; const prettifiedFiles = new prettier . Result ( ) ; try { state = { author : context . payload . pusher . name , branch : context . payload . ref . replace ( "refs/heads/" , "" ) , commitSha : context . payload . after . substring ( 0 , 7 ) , octokit : context . octokit , org : context . payload . repository . owner . login , prettierConfig : { } , prettierIgnore : "" , prettifierConfig : config . defaultValues ( ) , pullRequestId : "" , pullRequestNumber : 0 , repo : context . payload . repository . name , defaultBranchName : "" , } ; const repoPrefix = `${ state . org }/${ state . repo }|${ state . branch }|${ state . commitSha }` ; if ( state . commitSha === "0000000" ) { console . log ( `${ repoPrefix }: IGNORING BRANCH DELETION` ) ; return ; } console . log ( `${ repoPrefix }: PUSH DETECTED` ) ; if ( state . author === "prettifier[bot]" ) { console . log ( `${ repoPrefix }: IGNORING COMMIT BY PRETTIFIER` ) ; return ; } try { state = await loadPushContext ( state ) ; } catch ( e ) { if ( e instanceof Error && e . message . startsWith ( "Could not resolve to a Repository with the name" ) ) { console . log ( `${ repoPrefix }: THIS BOT DOESN'T HAVE ACCESS RIGHTS TO ${ state . org }/${ state . repo }, IGNORING` ) ; } else { console . log ( `${ repoPrefix }: CAN'T LOAD PUSH CONTEXT:` , e ) ; await logDevError ( new DevError ( `${ repoPrefix }: CAN'T LOAD PUSH CONTEXT:` , e , state ) ) ; } return ; } console . log ( `${ repoPrefix }: BOT CONFIG: ${ JSON . stringify ( state . prettifierConfig , null , "__" ) }` ) ; console . log ( `${ repoPrefix }: PRETTIER CONFIG: ${ JSON . stringify ( state . prettierConfig , null , "__" ) }` ) ; console . log ( `${ repoPrefix }: DEFAULT BRANCH: ${ state . defaultBranchName }` ) ; const configReader = new config . Reader ( state . prettifierConfig , state . prettierIgnore ) ; if ( configReader . shouldIgnoreBranch ( state . branch ) ) { console . log ( `${ repoPrefix }: IGNORING THIS BRANCH PER BOT CONFIG` ) ; return ; } if ( state . prettifierConfig . pullsOnly ) { if ( state . pullRequestNumber === 0 ) { console . log ( `${ repoPrefix }: IGNORING THIS BRANCH BECAUSE IT HAS NO OPEN PULL REQUEST` ) ; return ; } console . log ( `${ repoPrefix }: THIS BRANCH HAS PULL REQUEST #${ state . pullRequestNumber }` ) ; } changedFiles = findChangedFiles ( context . payload . commits ) ; const { prettifiedFiles , configChange } = await formatFiles ( { branch : state . branch , configReader , files : Array . from ( changedFiles ) , octokit : state . octokit , org : state . org , prettierConfig : state . prettierConfig , repo : state . repo , repoPrefix , } ) ; pretendToUse ( configChange ) ; if ( prettifiedFiles . length === 0 ) { console . log ( `${ repoPrefix }: ALL FILES ALREADY FORMATTED` ) ; return ; } let fixesCommitted = false ; if ( state . prettifierConfig . fixByCommit ) { const message = configReader . commitMessage ( { commitSha : state . commitSha , files : prettifiedFiles . paths ( ) } ) ; try { await github . createCommit ( { ... state , files : prettifiedFiles . formattedFiles ( ) , message , } ) ; fixesCommitted = true ; console . log ( `${ repoPrefix }: COMMITTED ${ prettifiedFiles . length } PRETTIFIED FILES` ) ; } catch ( e ) { if ( e instanceof RequestError && [ 422 , 403 , 404 ] . includes ( e . status ) ) { } else { throw new DevError ( "couldn't commit" , e ) ; } } } if ( state . pullRequestId !== "" && fixesCommitted && configReader . hasPrettifiedMessage ( ) ) { const hasComment = await github . hasCommentFromPrettifier ( state ) ; if ( hasComment ) { console . log ( `${ repoPrefix }: PULL REQUEST ALREADY HAS COMMENT, SKIPPING` ) ; } else { const args = { ... state , issueId : state . pullRequestId , text : configReader . prettifiedMessage ( { files : prettifiedFiles . paths ( ) } ) , } ; try { await github . addComment ( context . octokit , args ) ; console . log ( `${ repoPrefix }: ADDED PRETTIFIED-MESSAGE` ) ; } catch ( e ) { throw new DevError ( "cannot comment prettified message" , e , args ) ; } } } if ( fixesCommitted ) { return ; } if ( state . prettifierConfig . fixByPullRequest ) { const args = { ... state , body : "Formats recently committed files. No content changes." , branch : `prettifier-${ state . commitSha }` , files : prettifiedFiles . formattedFiles ( ) , message : configReader . commitMessage ( { commitSha : state . commitSha , files : prettifiedFiles . paths ( ) , } ) , parentBranch : state . defaultBranchName , } ; try { await github . createPullRequest ( args ) ; console . log ( `${ repoPrefix }: CREATED PULL REQUEST FOR ${ prettifiedFiles . length } PRETTIFIED FILES` ) ; } catch ( e ) { if ( e instanceof LoggedError ) { return ; } await logDevError ( new DevError ( `${ repoPrefix }: Cannot create pull request with formatting fixes` , e , args ) ) ; return ; } } } catch ( e ) { if ( e instanceof LoggedError ) { return ; } const errorContext = { ... state , event : "on-push" , payload : context . payload , changedFiles , prettifiedFiles , } ; if ( e instanceof DevError ) { e . enrich ( errorContext ) ; await logDevError ( e ) ; return ; } if ( e instanceof UserError ) { e . enrich ( errorContext ) ; await logUserError ( e , context . octokit ) ; return ; } await logDevError ( new DevError ( "unknown dev error" , e , errorContext ) ) ; } } /** provides an updated state with additional Push context data loaded from GitHub */ async function loadPushContext ( state ) { const pushContextData = await github . loadPushContextData ( state . octokit , state ) ; state . pullRequestNumber = pushContextData . pullRequestNumber ; state . pullRequestId = pushContextData . pullRequestId ; state . prettierIgnore = pushContextData . prettierIgnore ; state . prettifierConfig = config . parseYML ( pushContextData . prettifierConfig ) ; state . prettierConfig = prettier . loadConfig ( pushContextData ) ; state . defaultBranchName = pushContextData . defaultBranchName ; return state ; } function findChangedFiles ( commits ) { const result = new Set < string > ( ) ; for ( const commit of commits ) { concatToSet ( result , commit . added ) ; concatToSet ( result , commit . modified ) ; removeAllFromSet ( result , commit . removed ) ; } return result ; }