Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Created base action #5

Merged
merged 14 commits into from
Jan 9, 2024
Binary file added .github/update-branch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions .github/workflows/up-to-date.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Up to date

on:
push:
branches:
- 'main'

jobs:
updatePullRequests:
runs-on: ubuntu-latest
steps:
- name: Update all the PRs
uses: paritytech/up-to-date-action@main
with:
GITHUB_TOKEN: ${{ github.token }}
79 changes: 73 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,76 @@
# Parity GitHub Action template
# Up to date action

Template used to generate GitHub Actions.
Keep your pull request up to date when the target branch changes.

## To start
## Why?

- Remember to modify the `action.yml` file to have your required attributes and details.
- You can use [GitHub Action Brandings cheatsheet](https://github.com/haya14busa/github-action-brandings) to set the style of the action.
- Remember to modify the name in the `package.json`.
This action was created on the need to keep all the PRs up to date, so they would always be tested on latest.

When a repo has too many PRs, it can became a bit troublesome to keep all the PRs up to date.

It basically does the same thing as pressing the following button:

![update-button](./.github/update-branch.png)

## Configuration

```yml
name: Up to date

on:
push:
branches:
- 'main'

jobs:
updatePullRequests:
runs-on: ubuntu-latest
steps:
- name: Update all the PRs
uses: paritytech/up-to-date-action@main
with:
GITHUB_TOKEN: ${{ secret.PAT }}
```

### Inputs

#### `GITHUB_TOKEN`
- Required
- Has to be a [Personal Access Token](https://github.com/settings/tokens/) with `repo` permissions.
- It can not be GitHub's action token because a push made by an action secret does not trigger new actions (and the new tests would not be triggered)
- [Related reading](https://github.com/orgs/community/discussions/25702#discussioncomment-3248819)

##### Using a GitHub app instead of a PAT
In some cases, specially in big organizations, it is more organized to use a GitHub app to authenticate, as it allows us to give it permissions per repository and we can fine-grain them even better. If you wish to do that, you need to create a GitHub app with the following permissions:
- Repository permissions:
- Pull Requests
- [x] Write
- Contents
- [x] Write

Because this project is intended to be used with a token we need to do an extra step to generate one from the GitHub app:
- After you create the app, copy the *App ID* and the *private key* and set them as secrets.
- Then you need to modify the workflow file to have an extra step:
```yml
steps:
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v1
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- name: Update all the PRs
uses: paritytech/up-to-date-action@main
with:
# The previous step generates a token which is used as the input for this action
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
```

## Development
To work on this app, you require
- `Node 18.x`
- `yarn`

Use `yarn install` to set up the project.

`yarn build` compiles the TypeScript code to JavaScript.
8 changes: 4 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
name: "Example Action"
description: "This values need to be changed"
name: "Up to Date PRs"
description: "Keep all your PRs up to date when a new commit is pushed to the main branch"
author: Bullrich
branding:
icon: copy
color: yellow
icon: git-merge
color: gray-dark
inputs:
GITHUB_TOKEN:
required: true
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "parity-action-template",
"name": "up-to-date-action",
"version": "0.0.1",
"description": "GitHub action template for Parity",
"description": "Keep all your PRs up to date when a new commit is pushed to the main branch",
"main": "src/index.ts",
"scripts": {
"start": "node dist",
Expand All @@ -12,14 +12,14 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/Bullrich/parity-action-template.git"
"url": "git+https://github.com/paritytech/up-to-date-action.git"
},
"author": "Javier Bullrich <javier@bullrich.dev>",
"license": "MIT",
"bugs": {
"url": "https://github.com/Bullrich/parity-action-template/issues"
"url": "https://github.com/paritytech/up-to-date-action/issues"
},
"homepage": "https://github.com/Bullrich/parity-action-template#readme",
"homepage": "https://github.com/paritytech/up-to-date-action#readme",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
Expand Down
32 changes: 26 additions & 6 deletions src/github/pullRequest.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import { PullRequest } from "@octokit/webhooks-types";

import { ActionLogger, GitHubClient } from "./types";
import { GitHubClient } from "./types";

/** API class that uses the default token to access the data from the pull request and the repository */
export class PullRequestApi {
constructor(
private readonly api: GitHubClient,
private readonly logger: ActionLogger,
private readonly repo: { owner: string; repo: string },
) {}

getPrAuthor(pr: PullRequest): string {
return pr.user.login;
async listPRs(
onlyAutoMerge: boolean,
): Promise<{ number: number; title: string }[]> {
const openPRs = await this.api.paginate(this.api.rest.pulls.list, {
...this.repo,
state: "open",
});

if (onlyAutoMerge) {
return openPRs
.filter((pr) => pr.auto_merge)
.map(({ number, title }) => ({ number, title }) as const);
} else {
return openPRs.map(({ number, title }) => ({ number, title }) as const);
}
}

async update(number: number): Promise<string | undefined> {
const { data } = await this.api.rest.pulls.updateBranch({
...this.repo,
pull_number: number,
});

return data.message;
}
}
52 changes: 45 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getInput, info, setOutput } from "@actions/core";
import { getInput, setFailed, setOutput, summary } from "@actions/core";
import { SummaryTableRow } from "@actions/core/lib/summary";
import { context, getOctokit } from "@actions/github";
import { Context } from "@actions/github/lib/context";
import { PullRequest } from "@octokit/webhooks-types";

import { PullRequestApi } from "./github/pullRequest";
import { generateCoreLogger } from "./util";
Expand All @@ -23,10 +23,48 @@ const getRepo = (ctx: Context) => {
const repo = getRepo(context);

setOutput("repo", `${repo.owner}/${repo.repo}`);
const logger = generateCoreLogger();

if (context.payload.pull_request) {
const action = async () => {
const token = getInput("GITHUB_TOKEN", { required: true });
const api = new PullRequestApi(getOctokit(token), generateCoreLogger());
const author = api.getPrAuthor(context.payload.pull_request as PullRequest);
info("Author of the PR is " + author);
}
const repoInfo = getRepo(context);
const api = new PullRequestApi(getOctokit(token), repoInfo);
const prs = await api.listPRs(false);
if (prs.length > 0) {
logger.info(`About to update ${prs.length} PRs 🗄️`);
const rows: SummaryTableRow[] = [
[
{ data: "PR Number", header: true },
{ data: "Title", header: true },
{ data: "Result", header: true },
],
];
for (const { number, title } of prs.sort((a, b) => a.number - b.number)) {
logger.info(`📡 - Updating '${title}' #${number}`);
const repoTxt = `${repoInfo.owner}/${repoInfo.repo}#${number}`;
try {
await api.update(number);
rows.push([repoTxt, title, "Pass ✅"]);
logger.info(`📥 - Updated #${number}`);
} catch (error) {
logger.error(error as string | Error);
rows.push([repoTxt, title, "Fail ❌"]);
}
}
logger.info("🪄 - Finished updating PRs");
await summary
.addHeading("Up to date", 1)
.addHeading("PRs updated", 3)
.addTable(rows)
.write();
} else {
logger.info("No matching PRs found. Aborting");
summary.addHeading("Up to date", 1).addHeading("No matching PRs found");
}
};

action()
.then(() => {
logger.info("Operation completed");
})
.catch(setFailed);
Loading