{"id":16112834,"url":"https://github.com/tohaker/merge-train","last_synced_at":"2025-10-23T13:31:16.562Z","repository":{"id":44654277,"uuid":"320830480","full_name":"Tohaker/merge-train","owner":"Tohaker","description":"A collection of Node apps hosted in Azure Functions to keep track of merge queues.","archived":true,"fork":false,"pushed_at":"2022-02-01T23:19:09.000Z","size":2547,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-12-19T15:24:35.698Z","etag":null,"topics":["azure-functions","github-api","nodejs","slack","slack-bot","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tohaker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-12-12T12:53:51.000Z","updated_at":"2024-01-02T10:47:26.000Z","dependencies_parsed_at":"2022-09-25T14:52:06.307Z","dependency_job_id":null,"html_url":"https://github.com/Tohaker/merge-train","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tohaker%2Fmerge-train","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tohaker%2Fmerge-train/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tohaker%2Fmerge-train/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tohaker%2Fmerge-train/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tohaker","download_url":"https://codeload.github.com/Tohaker/merge-train/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237834754,"owners_count":19373786,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-functions","github-api","nodejs","slack","slack-bot","typescript"],"created_at":"2024-10-09T20:09:23.512Z","updated_at":"2025-10-23T13:31:11.231Z","avatar_url":"https://github.com/Tohaker.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Merge Train\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n[![Actions Status](https://github.com/tohaker/merge-train/workflows/Deployment/badge.svg)](https://github.com/tohaker/merge-train/actions)\n\nThis project consists of Node JS Azure serverless functions that make up the Merge Train bot. These are as follows;\n\n- A function that can be invoked through a Slack Bot to keep track of a growing list of Merge requests.\n- A function that is invoked by Github Webhooks whenever a PR is modified.\n\n## Using the Slack Bot\n\nThe proposed contract for interacting with the bot on Slack is as follows:\n\n- `/merge next` - Display the next URL in the list. This will not remove it from the list.\n- `/merge list (public)` - Display all URLs in the list, in the order they were added. Add `public` to share this list with the channel.\n- `/merge pause` - Pause the merge train. While paused, no automatic merges will be triggered.\n- `/merge resume` - Resume the merge train.\n- `/merge help` - Display this contract to the user as an ephemeral message.\n\n## Using the Github App\n\nThe Github App function will be invoked when the chosen webhooks are triggered.\n\n| Action           | Output                                                                                                                                                                                                                                                                                                                                                          |\n| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Labeled          | If the label mentions \"Ready for merge\", a receipt will be posted to the \"merge\" Slack channel. If the merge train has been paused previously, this will apply the same `merge train paused` label to the newly labeled PR.                                                                                                                                     |\n| Unlabeled        | If the label mentions \"Ready for merge\", a receipt will be posted to the \"merge\" Slack channel.                                                                                                                                                                                                                                                                 |\n| Review Requested | A message will be posted to the \"reviews\" Slack channel, tagging the users requested. To avoid duplicate calls, this app is setup to only look for a `requested_team` property, as this indicates users have been selected from an organisation team by GitHub. If your organisation does not use this, you will need to modify this code to ignore this field. |\n| Status changed   | If you have a CI pipeline linked to Github, Status changes will trigger the webhook. If a status becomes `success` and applies to the default branch (see Configuration below), it will trigger the next PR in the list to be merged.                                                                                                                           |\n\n## Configuration\n\n### Creating the Slack App\n\nYou'll need to create a Slack App for deployment. You can find instructions on how to do this [in the Slack API documentation](https://api.slack.com/). At least one [Slash Command](https://api.slack.com/interactivity/slash-commands) will be needed for interactivity with Slack Bot.\n\n### Creating the GitHub App\n\nYou'll need to create a GitHub App for deployment. You can find instructions on how to do this [in the GitHub documentation](https://docs.github.com/en/developers/apps/creating-a-github-app).\n\nThe following App permissions will need to be set:\n\n| Permission      | Level        | Reason                                                      |\n| --------------- | ------------ | ----------------------------------------------------------- |\n| Contents        | Read \u0026 Write | Provides ability to merge pull requests                     |\n| Pull requests   | Read \u0026 Write | Provides ability to read pull requests and apply new labels |\n| Commit statuses | Read-only    | Provides status checks for every commit                     |\n\nThis app will need to be installed in the repository you wish to track, and an additional webhook for Status events will need to be set in the repository.\n\n[![](docs/ghapp_1.png)](docs/ghapp_1.png)\n[![](docs/ghapp_2.png)](docs/ghapp_2.png)\n\n### Code config file\n\nSome basic configuration options are available for the functions. The [config](common/config.ts) file contains these options to be changed;\n\n| Option       | Purpose                                                                                                                                                                      | Default                                                                                                     |\n| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |\n| ChannelName  | Slack channels where you wish `merge` and `review` messages to be sent. These do not need to be different.                                                                   | `merge`, `reviews`                                                                                          |\n| Branch       | List of branches that are meaningful within the context of these functions. Only `DEFAULT` is defined, and this corresponds to the default branch of your GitHub repository. | `master`                                                                                                    |\n| Label        | Label names used in Github to represent various PR states                                                                                                                    | `ready for merge`, `merge train paused`                                                                     |\n| mergeMethods | An ordered list of Regular Expressions and merge methods to match particular branch rules you wish to enforce.                                                               | By default, all PRs will be merged with the `SQUASH` method, apart from branches beginning with `release/`. |\n| icon_emoji   | The emoji to use in Slack messages                                                                                                                                           | `:steam_locomotive:`                                                                                        |\n\nIf you want Slack users to be tagged in the reviews, they will need to add their GitHub login ID to their Slack profile under \"What I do\". If this is not filled, the GitHub ID will be posted instead.\n\n## Deployment\n\nDeployment is handled through [Terraform](terraform.io), using an Azure Service Principal to autheticate with an Azure account. See the following table for the Environment Variables the deployment expects;\n\n| Variable Name        | Purpose                                                                                                                                                                                                                                |\n| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| ARM_ACCESS_KEY       | The storage access key for saving Terraform state files. If you don't need this, remove the `backend` block from [the provider file](deployment/provider.tf)                                                                           |\n| ARM_CLIENT_ID        | Azure service principal app ID                                                                                                                                                                                                         |\n| ARM_CLIENT_SECRET    | Azure service principal password                                                                                                                                                                                                       |\n| ARM_SUBSCRIPTION_ID  | Azure subscription to deploy to                                                                                                                                                                                                        |\n| ARM_TENANT_ID        | Azure service principal tenant                                                                                                                                                                                                         |\n| SLACK_BOT_TOKEN      | Slack OAuth token required for posting and listening to messages                                                                                                                                                                       |\n| SLACK_SIGNING_SECRET | Secret used to authenticate messages coming from Slack                                                                                                                                                                                 |\n| GHAPP_SECRET         | GitHub App secret required to authenticate messages coming from GitHub                                                                                                                                                                 |\n| GHAPP_PRIVATE_KEY    | The private RSA key generated by GitHub when your app is first created. This is needed to generate JWTs for the GraphQL endpoint to act as your app.                                                                                   |\n| GH_APP_ID            | The GitHub app ID, found on your app's info page.                                                                                                                                                                                      |\n| GH_INSTALLATION_ID   | The ID of the installation that your app should access. Information on how to find this is [in the GitHub documentation](https://docs.github.com/en/developers/apps/authenticating-with-github-apps#authenticating-as-an-installation) |\n| GH_HOSTNAME          | The hostname for the GraphQL endpoint. If you use public GitHub, **you do not need to set this**; Only set it if you have an Enterprise Github app.                                                                                    |\n| GH_OWNER             | The name of the owner of the repository your app will be monitoring. If your repository is `https://github.com/myOrg/hello-world.git` then `myOrg` is the owner.                                                                       |\n| GH_REPOSITORY        | The name of the repository your app will be monitoring. If your repository is `https://github.com/myOrg/hello-world.git` then `hello-world` is the repository name.                                                                    |\n\nBefore running the terraform deployment, you have to package the apps. This can be done by running\n\n```bash\nnpm i\nnpm run build\n\nnpm ci --only=prod  # Dev dependencies aren't needed and will bloat the function zip files\nnpm i -g @ffflorian/jszip-cli mkdirp\nnpm run build:zip\n```\n\nTo run the deployment manually, you'll need the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) and be logged into an account with an active subscription. Then run the following;\n\n```bash\ncd deployment\nterraform init\nterraform apply\n```\n\nThis repostory also includes a [GitHub Actions workflow](.github/workflows/deploy.yml) as an example of how to deploy the apps in a CI environment.\n\n## Local testing\n\n[Azure Functions Core Tools](https://docs.microsoft.com/en-us/azure/azure-functions/functions-run-local) are needed to run this project locally.\n\nIf you build and run the project in the VSCode DevContainer all prerequisites will be installed automatically - this is the recommended method of development.\n\nTo run the function locally;\n\n```bash\nnpm i\nnpm start\n```\n\nThis will install all dependencies and begin running the function on a random port. You can then send POST requests to interact with the bot.\n\nUnit tests are also available to run with\n\n```bash\nnpm t\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftohaker%2Fmerge-train","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftohaker%2Fmerge-train","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftohaker%2Fmerge-train/lists"}