Skip to content

Commit

Permalink
feat: add label syncing
Browse files Browse the repository at this point in the history
  • Loading branch information
ijsKoud committed Jun 25, 2023
1 parent 461e7d4 commit 5fb0508
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 1 deletion.
1 change: 1 addition & 0 deletions apps/github-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"@ijsblokje/octocat": "*",
"@ijsblokje/release": "*",
"@ijsblokje/server": "*",
"@ijsblokje/utils": "*",
"dotenv": "16.3.1"
Expand Down
50 changes: 50 additions & 0 deletions apps/github-bot/src/events/LabelSync/LabelSyncUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Octokit } from "@ijsblokje/octokit";

/**
* Deletes a label and handles rejections if any
* @param octokit The authenticated octokit instance
* @param name The name of the label
* @param owner The owner of the repository
* @param repo The name of the repository
*/
export async function DeleteLabel(octokit: Octokit, name: string, owner: string, repo: string) {
try {
await octokit.request("DELETE /repos/{owner}/{repo}/labels/{name}", { name, owner, repo });
} catch (error) {
octokit.logger.warn(`[DeleteLabel]: Unable to delete label with name ${name} (repository: ${owner}/${repo}) `, error);
}
}

/**
* Creates a new label and handles rejections if any
* @param octokit The authenticated octokit instance
* @param name The label name
* @param color The label color
* @param description The label description
* @param owner The owner of the repository
* @param repo The repository name
*/
export async function CreateLabel(octokit: Octokit, name: string, color: string, description: string, owner: string, repo: string) {
try {
await octokit.request("POST /repos/{owner}/{repo}/labels", { name, owner, repo, color: color.replace("#", ""), description });
} catch (error) {
octokit.logger.warn(`[DeleteLabel]: Unable to create label with name ${name} (repository: ${owner}/${repo}) `, error);
}
}

/**
* Updates a label and handles rejections if any
* @param octokit The authenticated octokit instance
* @param name The label name
* @param color The label color
* @param description The label description
* @param owner The owner of the repository
* @param repo The repository name
*/
export async function UpdateLabel(octokit: Octokit, name: string, color: string, description: string, owner: string, repo: string) {
try {
await octokit.request("POST /repos/{owner}/{repo}/labels", { name, owner, repo, color: color.replace("#", ""), description });
} catch (error) {
octokit.logger.warn(`[DeleteLabel]: Unable to update label with name ${name} (repository: ${owner}/${repo}) `, error);
}
}
40 changes: 40 additions & 0 deletions apps/github-bot/src/events/LabelSync/PullRequestEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ApplyOptions, GitHubEvent, GitHubInstallation } from "@ijsblokje/octocat";
import type { Octokit } from "@ijsblokje/octokit";
import type { EmitterWebhookEvent } from "@ijsblokje/server";
import { Commit } from "@ijsblokje/release";

@ApplyOptions({ event: "pull_request" })
export default class extends GitHubEvent {
public override async run(
event: EmitterWebhookEvent<"pull_request.opened" | "pull_request.edited">,
octokit: Octokit,
installation?: GitHubInstallation
) {
const parsedTitle = new Commit({ commit: { message: event.payload.pull_request.title } } as any).parse();
if (!parsedTitle || !installation) return;

const defaultLabels = installation.defaultLabels.filter((label) => label.name.toLowerCase().includes(parsedTitle.type));
const repoLabels = (installation.labels.get(event.payload.repository.name) ?? []).filter((label) =>
label.name.toLowerCase().includes(parsedTitle.type)
);
const currentLabels = (event.payload.pull_request.labels ?? []).filter((label) => label.name.toLowerCase().includes("merge"));

if (event.payload.sender.login === "renovate[bot]") {
const dependencyLabel = installation.defaultLabels.find((label) => label.name.toLowerCase().includes("dependencies"));
if (dependencyLabel) defaultLabels.push(dependencyLabel);
}

await octokit
.request("PUT /repos/{owner}/{repo}/issues/{issue_number}/labels", {
issue_number: event.payload.pull_request.number,
owner: installation.name,
repo: event.payload.repository.name,
labels: [
...defaultLabels.map((label) => label.name),
...repoLabels.map((label) => label.name),
...currentLabels.map((label) => label.name)
]
})
.catch(() => void 0);
}
}
67 changes: 67 additions & 0 deletions apps/github-bot/src/events/LabelSync/PushEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ApplyOptions, GitHubEvent, GitHubInstallation } from "@ijsblokje/octocat";
import type { Octokit } from "@ijsblokje/octokit";
import type { EmitterWebhookEvent } from "@ijsblokje/server";
import { LABEL_CONFIG_LOCATION } from "@ijsblokje/utils/constants.js";
import type { Label } from "@ijsblokje/utils/types.js";
import _ from "lodash";
import { CreateLabel, DeleteLabel, UpdateLabel } from "./LabelSyncUtils.js";

@ApplyOptions({ event: "push" })
export default class extends GitHubEvent {
public override async run(event: EmitterWebhookEvent<"push">, octokit: Octokit, installation?: GitHubInstallation) {
if (!installation || !installation.defaultLabels) return;
if (event.payload.repository.name !== (installation.isUser ? installation!.name : ".github")) return;

if (event.payload.commits.some((commit) => commit.removed.includes(LABEL_CONFIG_LOCATION))) {
installation!.defaultLabels = [];
installation!.labels.clear();

return;
}

if (!event.payload.commits.some((commit) => [...commit.added, ...commit.modified].includes(LABEL_CONFIG_LOCATION))) return;

const labelConfig = await this.getLabelConfig(octokit, installation.name, event.payload.repository.name);
if (!labelConfig) return;

const owner = installation.name;
installation.updateLabels(Buffer.from(labelConfig, "base64").toString());

for (const repo of installation.configs.keys()) {
const labels = [...installation.defaultLabels, ...(installation.labels.get(repo) ?? [])];
const existingLbs = await octokit.request("GET /repos/{owner}/{repo}/labels", { owner, repo }).catch(() => ({ data: [] }));
const existingLabels: Label[] = existingLbs.data.map((label) => ({
name: label.name,
description: label.description ?? "",
color: `#${label.color}`
}));

const addLabels = labels.filter((label) => !existingLabels.find((l) => l.name === label.name)); // Labels that have to be created
const removeLabels = existingLabels.filter((label) => !labels.find((l) => l.name === label.name)); // Labels that have to be deleted
const commonLabels = labels.filter((label) => {
const cLabel = existingLabels.find((l) => l.name === label.name);
if (!cLabel) return false;
return !_.isEqual(cLabel, label);
}); // Labels that have to be updated

await Promise.allSettled(removeLabels.map((label) => DeleteLabel(octokit, label.name, owner, repo)));
await Promise.allSettled(addLabels.map((label) => CreateLabel(octokit, label.name, label.color, label.description, owner, repo)));
await Promise.allSettled(commonLabels.map((label) => UpdateLabel(octokit, label.name, label.color, label.description, owner, repo)));
}
}

private async getLabelConfig(octokit: Octokit, owner: string, repo: string) {
try {
const response = await octokit.request("GET /repos/{owner}/{repo}/contents/{path}", {
owner,
repo,
path: LABEL_CONFIG_LOCATION
});

return "content" in response.data ? response.data.content : null;
} catch (error) {
octokit.logger.error(`[LabelSync(PushEvent)]: Failed to fetch label config content from ${owner}/${repo}`, error);
return null;
}
}
}
3 changes: 2 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ __metadata:
languageName: unknown
linkType: soft

"@ijsblokje/release@workspace:packages/release":
"@ijsblokje/release@*, @ijsblokje/release@workspace:packages/release":
version: 0.0.0-use.local
resolution: "@ijsblokje/release@workspace:packages/release"
dependencies:
Expand Down Expand Up @@ -3228,6 +3228,7 @@ __metadata:
resolution: "github-bot@workspace:apps/github-bot"
dependencies:
"@ijsblokje/octocat": "*"
"@ijsblokje/release": "*"
"@ijsblokje/server": "*"
"@ijsblokje/utils": "*"
"@types/node": ^18.16.18
Expand Down

0 comments on commit 5fb0508

Please sign in to comment.