xref: /expo/tools/src/GitHub.ts (revision 8285c032)
1import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
2
3const octokit = new Octokit({
4  auth: process.env.GITHUB_TOKEN,
5});
6
7// Predefine some params used across almost all requests.
8const owner = 'expo';
9const repo = 'expo';
10
11/**
12 * Returns public informations about the currently authenticated (by GitHub API token) user.
13 */
14export async function getAuthenticatedUserAsync() {
15  const { data } = await octokit.users.getAuthenticated();
16  return data;
17}
18
19/**
20 * Requests for the pull request object.
21 */
22export async function getPullRequestAsync(pull_number: number): Promise<PullRequest> {
23  const { data } = await octokit.pulls.get({
24    owner,
25    repo,
26    pull_number,
27  });
28  return data;
29}
30
31/**
32 * Gets a list of reviews left in the pull request with given ID.
33 */
34export async function listPullRequestReviewsAsync(
35  pull_number: number
36): Promise<PullRequestReview[]> {
37  const { data } = await octokit.pulls.listReviews({
38    owner,
39    repo,
40    pull_number,
41  });
42  return data;
43}
44
45/**
46 * Creates pull request review. By default the review is pending which needs to be submitted in order to be visible for other users.
47 * Provide `event` option to create and submit at once.
48 */
49export async function createPullRequestReviewAsync<T>(
50  pull_number: number,
51  options?: T
52): Promise<PullRequestReview> {
53  const { data } = await octokit.pulls.createReview({
54    owner,
55    repo,
56    pull_number,
57    ...options,
58  });
59  return data;
60}
61
62/**
63 * Updates pull request review with a new main comment.
64 */
65export async function updatePullRequestReviewAsync(
66  pull_number: number,
67  review_id: number,
68  body: string
69) {
70  const { data } = await octokit.pulls.updateReview({
71    owner,
72    repo,
73    pull_number,
74    review_id,
75    body,
76  });
77  return data;
78}
79
80/**
81 * Gets a list of comments in review.
82 */
83export async function listPullRequestReviewCommentsAsync(pull_number: number, review_id: number) {
84  const { data } = await octokit.pulls.listReviewComments({
85    owner,
86    repo,
87    pull_number,
88    review_id,
89  });
90  return data;
91}
92
93/**
94 * Deletes a comment left under pull request review.
95 */
96export async function deletePullRequestReviewCommentAsync(comment_id: number) {
97  const { data } = await octokit.pulls.deleteReviewComment({
98    owner,
99    repo,
100    comment_id,
101  });
102  return data;
103}
104
105/**
106 * Deletes all comments from given review.
107 */
108export async function deleteAllPullRequestReviewCommentsAsync(
109  pull_number: number,
110  review_id: number
111) {
112  const comments = await listPullRequestReviewCommentsAsync(pull_number, review_id);
113
114  await Promise.all(
115    comments
116      .filter((comment) => comment.pull_request_review_id === review_id)
117      .map((comment) => deletePullRequestReviewCommentAsync(comment.id))
118  );
119}
120
121/**
122 * Requests given users to review the pull request.
123 * If the user already reviewed the PR, it resets his review state.
124 */
125export async function requestPullRequestReviewersAsync(pull_number: number, reviewers: string[]) {
126  const { data } = await octokit.pulls.requestReviewers({
127    owner,
128    repo,
129    pull_number,
130    reviewers,
131  });
132  return data;
133}
134
135/**
136 * Returns an issue object with given issue number.
137 */
138export async function getIssueAsync(issue_number: number) {
139  const { data } = await octokit.issues.get({
140    owner,
141    repo,
142    issue_number,
143  });
144  return data;
145}
146
147/**
148 * Creates an issue comment with given body.
149 */
150export async function createCommentAsync(issue_number: number, body: string) {
151  const { data } = await octokit.issues.createComment({
152    owner,
153    repo,
154    issue_number,
155    body,
156  });
157  return data;
158}
159
160/**
161 * Lists commits in given issue.
162 */
163export async function listCommentsAsync(
164  issue_number: number,
165  options: Partial<ListCommentsOptions>
166) {
167  const { data } = await octokit.issues.listComments({
168    owner,
169    repo,
170    issue_number,
171    ...options,
172  });
173  return data;
174}
175
176/**
177 * Returns a list of issue comments gathered from all pages.
178 */
179export async function listAllCommentsAsync(issue_number: number) {
180  const issue = await getIssueAsync(issue_number);
181  const comments = [] as ListCommentsResponse['data'];
182  const pageSize = 100;
183
184  for (let page = 1, maxPage = Math.ceil(issue.comments / pageSize); page <= maxPage; page++) {
185    const commentsPage = await listCommentsAsync(issue_number, {
186      page,
187      per_page: pageSize,
188    });
189    comments.push(...commentsPage);
190  }
191  return comments;
192}
193
194/**
195 * Deletes an issue comment with given identifier.
196 */
197export async function deleteCommentAsync(comment_id: number) {
198  const { data } = await octokit.issues.deleteComment({
199    owner,
200    repo,
201    comment_id,
202  });
203  return data;
204}
205
206/**
207 * Adds labels to the issue. Throws an error when any of given labels doesn't exist.
208 */
209export async function addIssueLabelsAsync(issue_number: number, labels: string[]) {
210  const { data } = await octokit.issues.addLabels({
211    owner,
212    repo,
213    issue_number,
214    labels,
215  });
216  return data;
217}
218
219/**
220 * Removes single label from the issue.
221 * Throws an error when given label doesn't exist and when the label isn't added.
222 */
223export async function removeIssueLabelAsync(issue_number: number, name: string) {
224  const { data } = await octokit.issues.removeLabel({
225    owner,
226    repo,
227    issue_number,
228    name,
229  });
230  return data;
231}
232
233// Octokit's types are autogenerated and so inconvenient to use if you want to refer to them.
234// We re-export some of them to make it easier.
235export type PullRequestReviewEvent = 'COMMENT' | 'APPROVE' | 'REQUEST_CHANGES';
236export type PullRequest = RestEndpointMethodTypes['pulls']['get']['response']['data'];
237export type PullRequestReview = RestEndpointMethodTypes['pulls']['getReview']['response']['data'];
238export type IssueComment = RestEndpointMethodTypes['issues']['getComment']['response']['data'];
239export type ListCommentsOptions = RestEndpointMethodTypes['issues']['listComments']['parameters'];
240export type ListCommentsResponse = RestEndpointMethodTypes['issues']['listComments']['response'];
241