Skip to content

Commit

Permalink
Update branch option before auto-merge (#30)
Browse files Browse the repository at this point in the history
- Added option to update a branch before enabling `auto-merge`.
  - Resolves #23
- Updated graphql hardcoded strings to work as `.graphql` files with
auto-type generation.
- Updated version to `1.1.0`
  • Loading branch information
Bullrich authored Aug 13, 2024
1 parent 4e6f275 commit d28b19b
Show file tree
Hide file tree
Showing 18 changed files with 3,013 additions and 104 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
dist
.git
src/github/graphql/*
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
!.vscode/extensions.json
!.vscode/settings.json
.idea

src/github/graphql/*.ts
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ WORKDIR /action

COPY package.json yarn.lock ./

RUN yarn install --frozen-lockfile
RUN yarn install --frozen-lockfile --ignore-scripts

COPY . .

RUN yarn run postinstall

RUN yarn run build

FROM node:20-slim
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ You can find all the inputs in [the action file](./action.yml), but let's walk t
- An [unstable PR](https://docs.github.com/en/graphql/reference/enums#mergestatestatus) is a PR that can be merged, but a *non required status check* is failing.
- This is only relevant once the PR can be merged. GitHub's auto-merge always merges unstable PRs
- **Optional**: Defaults to `true`
- `UPDATE_BEFORE_MERGE`: If the bot should try to update the PR to be up to date before enabling auto-merge
- **Optional**: Defaults to false

## Usage

Expand Down
5 changes: 4 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ inputs:
ALLOW_UNSTABLE:
required: false
description: If unstable, ready to merge, PRs can be merged. Defaults to true
UPDATE_BEFORE_MERGE:
required: false
description: If the bot should update the Pull Request Branch before enabling auto-merge
outputs:
repo:
description: 'The name of the repo in owner/repo pattern'

runs:
using: 'docker'
image: 'docker://ghcr.io/paritytech/auto-merge-bot/action:1.0.1'
image: 'docker://ghcr.io/paritytech/auto-merge-bot/action:1.1.0'
15 changes: 15 additions & 0 deletions codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

import type { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = {
overwrite: true,
schema: "./node_modules/@octokit/graphql-schema/schema.graphql",
documents: "src/**/*.gql",
generates: {
"src/github/graphql/index.ts": {
plugins: ["typescript", "typescript-operations"],
}
}
};

export default config;
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{
"name": "auto-merge-bot",
"version": "1.0.1",
"version": "1.1.0",
"description": "Bot which enables or disable auto-merge",
"main": "src/index.ts",
"scripts": {
"start": "node dist",
"build": "ncc build --license LICENSE",
"test": "jest",
"fix": "eslint --fix 'src/**/*'",
"lint": "eslint 'src/**/*'"
"lint": "eslint 'src/**/*'",
"codegen": "graphql-codegen --config codegen.ts",
"copy": "node scripts/copy-files.js",
"postinstall": "npm-run-all codegen copy"
},
"repository": {
"type": "git",
Expand All @@ -25,15 +28,20 @@
"@actions/github": "^6.0.0",
"@octokit/graphql": "^8.0.1",
"@octokit/graphql-schema": "^14.58.0",
"@octokit/webhooks-types": "^7.3.2"
"@octokit/webhooks-types": "^7.4.0",
"graphql": "^16.8.1"
},
"devDependencies": {
"@eng-automation/js-style": "^2.3.0",
"@graphql-codegen/cli": "5.0.2",
"@graphql-codegen/typescript": "4.0.6",
"@graphql-codegen/typescript-resolvers": "^4.0.6",
"@types/jest": "^29.5.12",
"@vercel/ncc": "^0.38.1",
"jest": "^29.7.0",
"jest-mock-extended": "^3.0.5",
"npm-run-all": "^4.1.5",
"ts-jest": "^29.1.2",
"typescript": "^5.3.3"
"typescript": "^5.4.3"
}
}
24 changes: 24 additions & 0 deletions scripts/copy-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const { readdirSync, writeFileSync, readFileSync } = require("fs");

const files = readdirSync("src/github/graphql/");

/**
* Copy a file and replace it's extension
* @param {string} fileName Name of the file to copy
* @param {string} extension New extension to put
*/
const copyFile = (fileName, extension) => {
console.log("Copying content of %s into a .ts file", fileName);
const content = readFileSync(fileName);
const oldExtension = fileName.split(".").pop();
writeFileSync(
fileName.replace(oldExtension, extension),
`// Generated from ${fileName}\nexport default \`${content}\`;`,
);
};

for (const file of files) {
if (file.endsWith(".gql")) {
copyFile(`src/github/graphql/${file}`, "ts");
}
}
4 changes: 4 additions & 0 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class Bot {
private readonly logger: ActionLogger,
private readonly commentsApi: CommentsApi,
private readonly allowlistedUsers: string[],
private readonly updateBeforeMerge: boolean,
private readonly actionUrl: string,
) {}

Expand Down Expand Up @@ -94,6 +95,9 @@ export class Bot {
// Simply `/merge`
case undefined:
await this.commentsApi.reactToComment(this.comment.id, "+1");
if (this.updateBeforeMerge) {
await merger.updatePR();
}
await merger.enableAutoMerge();
await this.commentsApi.comment(
"Enabled `auto-merge` in Pull Request\n\n" + detailCommands,
Expand Down
5 changes: 5 additions & 0 deletions src/github/graphql/DisableAutoMerge.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation DisableAutoMerge($prId: ID!) {
disablePullRequestAutoMerge(input: {pullRequestId: $prId}) {
clientMutationId
}
}
5 changes: 5 additions & 0 deletions src/github/graphql/EnableAutoMerge.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation EnableAutoMerge($prId: ID!, $mergeMethod: PullRequestMergeMethod!) {
enablePullRequestAutoMerge(input: {pullRequestId: $prId, mergeMethod: $mergeMethod}) {
clientMutationId
}
}
5 changes: 5 additions & 0 deletions src/github/graphql/MergePullRequest.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mutation MergePullRequest($prId: ID!, $mergeMethod: PullRequestMergeMethod!) {
mergePullRequest(input: {pullRequestId: $prId, mergeMethod: $mergeMethod}) {
clientMutationId
}
}
10 changes: 10 additions & 0 deletions src/github/graphql/UpdatePullRequest.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
mutation UpdatePullRequestBranch($prId: ID!) {
updatePullRequestBranch(input: {pullRequestId: $prId}) {
clientMutationId
pullRequest {
title
number
headRefName
}
}
}
96 changes: 57 additions & 39 deletions src/github/merger.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,29 @@
import { graphql } from "@octokit/graphql";
import { PullRequestMergeMethod } from "@octokit/graphql-schema";
import { RequestParameters } from "@octokit/graphql/dist-types/types";

import {
DisableAutoMergeMutation,
DisableAutoMergeMutationVariables,
EnableAutoMergeMutation,
EnableAutoMergeMutationVariables,
MergePullRequestMutation,
MergePullRequestMutationVariables,
PullRequestMergeMethod,
UpdatePullRequestBranchMutation,
UpdatePullRequestBranchMutationVariables,
} from "./graphql";
import DISABLE_AUTO_MERGE from "./graphql/DisableAutoMerge";
import ENABLE_AUTO_MERGE from "./graphql/EnableAutoMerge";
import MERGE_PULL_REQUEST from "./graphql/MergePullRequest";
import UPDATE_PULL_REQUEST from "./graphql/UpdatePullRequest";
import { ActionLogger } from "./types";

// https://docs.github.com/en/graphql/reference/mutations#enablepullrequestautomerge
export const ENABLE_AUTO_MERGE = `
mutation($prId: ID!, $mergeMethod: PullRequestMergeMethod!) {
enablePullRequestAutoMerge(input: {pullRequestId: $prId, mergeMethod: $mergeMethod}) {
clientMutationId
}
}`;

// https://docs.github.com/en/graphql/reference/mutations#disablepullrequestautomerge
export const DISABLE_AUTO_MERGE = `
mutation($prId: ID!) {
disablePullRequestAutoMerge(input: {pullRequestId: $prId}) {
clientMutationId
}
}`;

// https://docs.github.com/en/graphql/reference/mutations#mergepullrequest
export const MERGE_PULL_REQUEST = `
mutation($prId: ID!, $mergeMethod: PullRequestMergeMethod!) {
mergePullRequest(input: {pullRequestId: $prId, mergeMethod: $mergeMethod}) {
clientMutationId
}
}`;

export type MergeMethod = "SQUASH" | "MERGE" | "REBASE";

export class Merger {
constructor(
private readonly nodeId: string,
private readonly gql: typeof graphql,
private readonly gqlApi: typeof graphql,
private readonly logger: ActionLogger,
private readonly mergeMethod: PullRequestMergeMethod,
private readonly allowUnstable: boolean = false,
Expand All @@ -55,24 +46,45 @@ export class Merger {
return false;
}

async enableAutoMerge(): Promise<void> {
async updatePR(): Promise<void> {
this.logger.info("Updating branch before enabling auto-merge");
try {
await this.gql<{
enablePullRequestAutoMerge: { clientMutationId: unknown };
}>(ENABLE_AUTO_MERGE, {
const update = await this.gql<
UpdatePullRequestBranchMutationVariables,
UpdatePullRequestBranchMutation
>(UPDATE_PULL_REQUEST, {
prId: this.nodeId,
mergeMethod: this.mergeMethod,
});
this.logger.info(
`Succesfully updated ${
update.updatePullRequestBranch?.pullRequest?.headRefName ?? "unknown"
} branch`,
);
} catch (err) {
this.logger.warn(err as Error);
}
}

async enableAutoMerge(): Promise<void> {
try {
await this.gql<EnableAutoMergeMutationVariables, EnableAutoMergeMutation>(
ENABLE_AUTO_MERGE,
{
prId: this.nodeId,
mergeMethod: this.mergeMethod as PullRequestMergeMethod,
},
);
this.logger.info("Succesfully enabled auto-merge");
} catch (error) {
this.logger.warn(error as Error);
if (error instanceof Error && this.errorPermitsToMerge(error)) {
this.logger.warn(
"Pull Request is ready to merge. Running merge command instead",
);
await this.gql<{
mergePullRequest: { clientMutationId: unknown };
}>(MERGE_PULL_REQUEST, {
await this.gql<
MergePullRequestMutationVariables,
MergePullRequestMutation
>(MERGE_PULL_REQUEST, {
prId: this.nodeId,
mergeMethod: this.mergeMethod,
});
Expand All @@ -84,11 +96,17 @@ export class Merger {
}

async disableAutoMerge(): Promise<void> {
await this.gql<{
disablePullRequestAutoMerge: { clientMutationId: unknown };
}>(DISABLE_AUTO_MERGE, {
prId: this.nodeId,
});
await this.gql<DisableAutoMergeMutationVariables, DisableAutoMergeMutation>(
DISABLE_AUTO_MERGE,
{ prId: this.nodeId },
);
this.logger.info("Succesfully disabled auto-merge");
}

async gql<Params extends RequestParameters, Output>(
query: string,
params: Params,
): Promise<Output> {
return await this.gqlApi<Output>(query, params);
}
}
9 changes: 7 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { getInput, setFailed, setOutput } from "@actions/core";
import { context, getOctokit } from "@actions/github";
import { Context } from "@actions/github/lib/context";
import { graphql } from "@octokit/graphql/dist-types/types";
import { PullRequestMergeMethod } from "@octokit/graphql-schema";
import { Issue, IssueComment } from "@octokit/webhooks-types";

import { Bot } from "./bot";
import { CommentsApi } from "./github/comments";
import { PullRequestMergeMethod } from "./github/graphql";
import { Merger } from "./github/merger";
import { generateCoreLogger, getallowlistedUsers } from "./util";

Expand Down Expand Up @@ -59,6 +59,9 @@ const getAllowUnstable = (): boolean => {

const silentMode = getInput("SILENT", { required: false }) === "true";

const getupdateBeforeMerge = (): boolean =>
getInput("UPDATE_BEFORE_MERGE", { required: false }) === "true";

logger.info(
`Silent mode is ${
silentMode ? "enabled" : "disabled. Bot will comment actions"
Expand Down Expand Up @@ -87,11 +90,12 @@ if (context.payload.comment) {
}) as graphql;
const mergeMethod = getMergeMethod();
const unstableAllowed = getAllowUnstable();
const updateBeforeMerge = getupdateBeforeMerge();
const merger = new Merger(
issue.node_id,
gql,
logger,
mergeMethod,
mergeMethod as PullRequestMergeMethod,
unstableAllowed,
);
const bot = new Bot(
Expand All @@ -100,6 +104,7 @@ if (context.payload.comment) {
logger,
commentsApi,
allowlistedUsers,
updateBeforeMerge,
actionUrl,
);
bot
Expand Down
13 changes: 8 additions & 5 deletions src/test/queries.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { validate } from "@octokit/graphql-schema";

import {
DISABLE_AUTO_MERGE,
ENABLE_AUTO_MERGE,
MERGE_PULL_REQUEST,
} from "../github/merger";
import DISABLE_AUTO_MERGE from "../github/graphql/DisableAutoMerge";
import ENABLE_AUTO_MERGE from "../github/graphql/EnableAutoMerge";
import MERGE_PULL_REQUEST from "../github/graphql/MergePullRequest";
import UPDATE_PULL_REQUEST from "../github/graphql/UpdatePullRequest";

describe("Schemas", () => {
test("ENABLE_AUTO_MERGE", () => {
Expand All @@ -18,4 +17,8 @@ describe("Schemas", () => {
test("MERGE_PULL_REQUEST", () => {
expect(validate(MERGE_PULL_REQUEST)).toEqual([]);
});

test("UPDATE_PULL_REQUEST", () => {
expect(validate(UPDATE_PULL_REQUEST)).toEqual([]);
});
});
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
},
"exclude": [
"node_modules",
"codegen.ts"
]
}
Loading

0 comments on commit d28b19b

Please sign in to comment.