/ src / github-helper.ts
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  }