diff --git a/src/bot.ts b/src/bot.ts index 0493de9..55c404a 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -1,7 +1,8 @@ import { Issue, IssueComment } from "@octokit/webhooks-types"; + +import { CommentsApi } from "./github/comments"; import { Merger } from "./github/merger"; import { ActionLogger } from "./github/types"; -import { CommentsApi } from "./github/comments"; const BOT_COMMAND = "/bot"; @@ -18,64 +19,89 @@ For more information see the [documentation](https://github.com/paritytech/auto- `; export class Bot { - constructor(private readonly comment: IssueComment, private readonly pr: Issue, private readonly logger: ActionLogger, private readonly commentsApi: CommentsApi) { } + constructor( + private readonly comment: IssueComment, + private readonly pr: Issue, + private readonly logger: ActionLogger, + private readonly commentsApi: CommentsApi, + ) {} - async canTriggerBot():Promise { - this.logger.debug("Evaluating if user can trigger the bot"); - const author = this.pr.user.id; - if (this.comment.user.id === author) { - this.logger.debug("Author of comment is also author of PR") - return true; - } - this.logger.debug("Author of comment is not the author of the PR"); - - return await this.commentsApi.userBelongsToOrg(this.comment.user.login); + /** Verifies if the author is the also the author of the PR or a member of the org */ + async canTriggerBot(): Promise { + this.logger.debug("Evaluating if user can trigger the bot"); + const author = this.pr.user.id; + if (this.comment.user.id === author) { + this.logger.debug("Author of comment is also author of PR"); + return true; } + this.logger.debug("Author of comment is not the author of the PR"); + return await this.commentsApi.userBelongsToOrg(this.comment.user.login); + } - async run(merger: Merger): Promise { - this.logger.info("Running action on comment: " + this.comment.html_url); - if (!this.comment.body.startsWith(BOT_COMMAND)) { - this.logger.info(`Ignoring comment ${this.comment.html_url} as it does not start with '${BOT_COMMAND}'`); - return; - } + async run(merger: Merger): Promise { + this.logger.info("Running action on comment: " + this.comment.html_url); + if (!this.comment.body.startsWith(BOT_COMMAND)) { + this.logger.info( + `Ignoring comment ${this.comment.html_url} as it does not start with '${BOT_COMMAND}'`, + ); + return; + } - if (!await this.canTriggerBot()) { - const { login } = this.comment.user; - const org = this.commentsApi.pullData.owner; - this.logger.warn("User is not allowed to trigger the bot. " + `He is not the author of the PR and does not *publicly* belong to the org: https://github.com/orgs/${org}/people`); - await this.commentsApi.reactToComment(this.comment.id, "-1"); - await this.commentsApi.comment("## Auto-Merge-Bot\n" + `User @${login} is not the author of the PR and does not [*publicly* belong to the org \`${org}\`](https://github.com/orgs/${org}/people).\n\n` + - "Only author or *public* org members can trigger the bot."); - return; - } - this.logger.debug("User can trigger bot"); + if (this.pr.state === "closed") { + this.logger.info("Ignoring PR as it is closed"); + return; + } - const [_, command] = this.comment.body.split(" "); - try { - switch (command as Command) { - case "merge": - await this.commentsApi.reactToComment(this.comment.id, "+1"); - await merger.enableAutoMerge(); - await this.commentsApi.comment("Enabled `auto-merge` in Pull Request"); - break; - case "cancel": - await this.commentsApi.reactToComment(this.comment.id, "+1"); - await merger.disableAutoMerge(); - await this.commentsApi.comment("Disabled `auto-merge` in Pull Request"); - break; - case "help": - await this.commentsApi.comment('## Auto-Merge-Bot\n' + botCommands); - break; - default: { - await this.commentsApi.reactToComment(this.comment.id, "confused"); - await this.commentsApi.comment('## Auto-Merge-Bot\n' + `Command \`${command}\` not recognized.\n\n` + botCommands); - } - } - } catch (e) { - this.logger.error(e as Error); - throw e; - } + if (!(await this.canTriggerBot())) { + const { login } = this.comment.user; + const org = this.commentsApi.pullData.owner; + this.logger.warn( + "User is not allowed to trigger the bot. " + + `He is not the author of the PR and does not *publicly* belong to the org: https://github.com/orgs/${org}/people`, + ); + await this.commentsApi.reactToComment(this.comment.id, "-1"); + await this.commentsApi.comment( + "## Auto-Merge-Bot\n" + + `User @${login} is not the author of the PR and does not [*publicly* belong to the org \`${org}\`](https://github.com/orgs/${org}/people).\n\n` + + "Only author or *public* org members can trigger the bot.", + ); + return; + } + this.logger.debug("User can trigger bot"); + const [_, command] = this.comment.body.split(" "); + try { + switch (command as Command) { + case "merge": + await this.commentsApi.reactToComment(this.comment.id, "+1"); + await merger.enableAutoMerge(); + await this.commentsApi.comment( + "Enabled `auto-merge` in Pull Request", + ); + break; + case "cancel": + await this.commentsApi.reactToComment(this.comment.id, "+1"); + await merger.disableAutoMerge(); + await this.commentsApi.comment( + "Disabled `auto-merge` in Pull Request", + ); + break; + case "help": + await this.commentsApi.comment("## Auto-Merge-Bot\n" + botCommands); + break; + default: { + await this.commentsApi.reactToComment(this.comment.id, "confused"); + await this.commentsApi.comment( + "## Auto-Merge-Bot\n" + + `Command \`${command}\` not recognized.\n\n` + + botCommands, + ); + } + } + } catch (e) { + this.logger.error(e as Error); + throw e; } + } } diff --git a/src/github/comments.ts b/src/github/comments.ts index b9cf104..52ae815 100644 --- a/src/github/comments.ts +++ b/src/github/comments.ts @@ -1,5 +1,3 @@ -import { PullRequest } from "@octokit/webhooks-types"; - import { ActionLogger, GitHubClient } from "./types"; /** API class that uses the default token to access the data from the pull request and the repository */ @@ -7,25 +5,41 @@ export class CommentsApi { constructor( private readonly api: GitHubClient, private readonly logger: ActionLogger, - public readonly pullData: { repo: string, owner: string, number: number } - ) { } + public readonly pullData: { repo: string; owner: string; number: number }, + ) {} async comment(message: string) { - await this.api.rest.issues.createComment({ ...this.pullData, body: message, issue_number: this.pullData.number }); + await this.api.rest.issues.createComment({ + ...this.pullData, + body: message, + issue_number: this.pullData.number, + }); } - async reactToComment(commentId: number, reaction: "+1" | "-1" | "confused"): Promise { - await this.api.rest.reactions.createForIssueComment({ ...this.pullData, comment_id: commentId, content: reaction }); + async reactToComment( + commentId: number, + reaction: "+1" | "-1" | "confused", + ): Promise { + await this.api.rest.reactions.createForIssueComment({ + ...this.pullData, + comment_id: commentId, + content: reaction, + }); } async userBelongsToOrg(username: string): Promise { const org = this.pullData.owner; - this.logger.debug(`Checking if user ${username} belongs to ${org} as a public user.`); + this.logger.debug( + `Checking if user ${username} belongs to ${org} as a public user.`, + ); // If the user does not belong to the org, this will throw an http error try { - const { status } = await this.api.rest.orgs.checkPublicMembershipForUser({org, username}); + const { status } = await this.api.rest.orgs.checkPublicMembershipForUser({ + org, + username, + }); return status === 204; - } catch (error){ + } catch (error) { this.logger.warn(error as Error); return false; } diff --git a/src/github/merger.ts b/src/github/merger.ts index 35273a2..f045aaa 100644 --- a/src/github/merger.ts +++ b/src/github/merger.ts @@ -1,7 +1,7 @@ import { graphql } from "@octokit/graphql"; -import { ActionLogger } from "./types"; import { PullRequestMergeMethod } from "@octokit/graphql-schema"; +import { ActionLogger } from "./types"; // https://docs.github.com/en/graphql/reference/mutations#enablepullrequestautomerge export const ENABLE_AUTO_MERGE = ` @@ -22,24 +22,29 @@ mutation($prId: ID!) { export type MergeMethod = "SQUASH" | "MERGE" | "REBASE"; export class Merger { - constructor(private readonly nodeId: string, private readonly gql: typeof graphql, private readonly logger: ActionLogger, private readonly mergeMethod: PullRequestMergeMethod) { - - } - - async enableAutoMerge() { - const mergeRequest = await this.gql<{ enablePullRequestAutoMerge: { clientMutationId: unknown } }>(ENABLE_AUTO_MERGE, - { - prId: this.nodeId, - mergeMethod: this.mergeMethod - }); - this.logger.info("Succesfully enabled auto-merge"); - } - - async disableAutoMerge() { - const mergeRequest = await this.gql<{ disablePullRequestAutoMerge: { clientMutationId: unknown } }>(DISABLE_AUTO_MERGE, - { - prId: this.nodeId - }); - this.logger.info("Succesfully disabled auto-merge"); - } + constructor( + private readonly nodeId: string, + private readonly gql: typeof graphql, + private readonly logger: ActionLogger, + private readonly mergeMethod: PullRequestMergeMethod, + ) {} + + async enableAutoMerge() { + const mergeRequest = await this.gql<{ + enablePullRequestAutoMerge: { clientMutationId: unknown }; + }>(ENABLE_AUTO_MERGE, { + prId: this.nodeId, + mergeMethod: this.mergeMethod, + }); + this.logger.info("Succesfully enabled auto-merge"); + } + + async disableAutoMerge() { + const mergeRequest = await this.gql<{ + disablePullRequestAutoMerge: { clientMutationId: unknown }; + }>(DISABLE_AUTO_MERGE, { + prId: this.nodeId, + }); + this.logger.info("Succesfully disabled auto-merge"); + } }