github-helper.ts
1 import * as core from '@actions/core' 2 import {Inputs} from './create-pull-request' 3 import {Octokit, OctokitOptions} from './octokit-client' 4 5 const ERROR_PR_REVIEW_FROM_AUTHOR = 6 'Review cannot be requested from pull request author' 7 8 interface Repository { 9 owner: string 10 repo: string 11 } 12 13 export class GitHubHelper { 14 private octokit: InstanceType<typeof Octokit> 15 16 constructor(token: string) { 17 const options: OctokitOptions = {} 18 if (token) { 19 options.auth = `${token}` 20 } 21 this.octokit = new Octokit(options) 22 } 23 24 private parseRepository(repository: string): Repository { 25 const [owner, repo] = repository.split('/') 26 return { 27 owner: owner, 28 repo: repo 29 } 30 } 31 32 private async createOrUpdate( 33 inputs: Inputs, 34 baseRepository: string, 35 headBranch: string 36 ): Promise<number> { 37 // Try to create the pull request 38 try { 39 const {data: pull} = await this.octokit.pulls.create({ 40 ...this.parseRepository(baseRepository), 41 title: inputs.title, 42 head: headBranch, 43 base: inputs.base, 44 body: inputs.body, 45 draft: inputs.draft 46 }) 47 core.info( 48 `Created pull request #${pull.number} (${headBranch} => ${inputs.base})` 49 ) 50 return pull.number 51 } catch (e) { 52 if ( 53 !e.message || 54 !e.message.includes(`A pull request already exists for ${headBranch}`) 55 ) { 56 throw e 57 } 58 } 59 60 // Update the pull request that exists for this branch and base 61 const {data: pulls} = await this.octokit.pulls.list({ 62 ...this.parseRepository(baseRepository), 63 state: 'open', 64 head: headBranch, 65 base: inputs.base 66 }) 67 const {data: pull} = await this.octokit.pulls.update({ 68 ...this.parseRepository(baseRepository), 69 pull_number: pulls[0].number, 70 title: inputs.title, 71 body: inputs.body, 72 draft: inputs.draft 73 }) 74 core.info( 75 `Updated pull request #${pull.number} (${headBranch} => ${inputs.base})` 76 ) 77 return pull.number 78 } 79 80 async getRepositoryParent(headRepository: string): Promise<string> { 81 const {data: headRepo} = await this.octokit.repos.get({ 82 ...this.parseRepository(headRepository) 83 }) 84 if (!headRepo.parent) { 85 throw new Error( 86 `Repository '${headRepository}' is not a fork. Unable to continue.` 87 ) 88 } 89 return headRepo.parent.full_name 90 } 91 92 async createOrUpdatePullRequest( 93 inputs: Inputs, 94 baseRepository: string, 95 headRepository: string 96 ): Promise<void> { 97 const [headOwner] = headRepository.split('/') 98 const headBranch = `${headOwner}:${inputs.branch}` 99 100 // Create or update the pull request 101 const pullNumber = await this.createOrUpdate( 102 inputs, 103 baseRepository, 104 headBranch 105 ) 106 107 // Set outputs 108 core.startGroup('Setting outputs') 109 core.setOutput('pull-request-number', pullNumber) 110 core.exportVariable('PULL_REQUEST_NUMBER', pullNumber) 111 core.endGroup() 112 113 // Set milestone, labels and assignees 114 const updateIssueParams = {} 115 if (inputs.milestone) { 116 updateIssueParams['milestone'] = inputs.milestone 117 core.info(`Applying milestone '${inputs.milestone}'`) 118 } 119 if (inputs.labels.length > 0) { 120 updateIssueParams['labels'] = inputs.labels 121 core.info(`Applying labels '${inputs.labels}'`) 122 } 123 if (inputs.assignees.length > 0) { 124 updateIssueParams['assignees'] = inputs.assignees 125 core.info(`Applying assignees '${inputs.assignees}'`) 126 } 127 if (Object.keys(updateIssueParams).length > 0) { 128 await this.octokit.issues.update({ 129 ...this.parseRepository(baseRepository), 130 issue_number: pullNumber, 131 ...updateIssueParams 132 }) 133 } 134 135 // Request reviewers and team reviewers 136 const requestReviewersParams = {} 137 if (inputs.reviewers.length > 0) { 138 requestReviewersParams['reviewers'] = inputs.reviewers 139 core.info(`Requesting reviewers '${inputs.reviewers}'`) 140 } 141 if (inputs.teamReviewers.length > 0) { 142 requestReviewersParams['team_reviewers'] = inputs.teamReviewers 143 core.info(`Requesting team reviewers '${inputs.teamReviewers}'`) 144 } 145 if (Object.keys(requestReviewersParams).length > 0) { 146 try { 147 await this.octokit.pulls.requestReviewers({ 148 ...this.parseRepository(baseRepository), 149 pull_number: pullNumber, 150 ...requestReviewersParams 151 }) 152 } catch (e) { 153 if (e.message && e.message.includes(ERROR_PR_REVIEW_FROM_AUTHOR)) { 154 core.warning(ERROR_PR_REVIEW_FROM_AUTHOR) 155 } else { 156 throw e 157 } 158 } 159 } 160 } 161 }