mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-14 23:16:26 +00:00
github PR gate (#4070)
## Description: ## Summary Adds a GitHub Action that auto-closes PRs which don't follow the contribution workflow, so maintainer review time goes to legitimate contributions instead of off-roadmap or AI-generated submissions. Triggered on `pull_request_target: [opened, reopened]` and **defaults to dry-run** so it's safe to merge before flipping live. ## Gate logic (first match wins) 1. **Maintainer bypass** — PR carries `bypass-pr-check` label → pass. 2. **Org/repo member** — `author_association` is `OWNER` / `MEMBER` / `COLLABORATOR` → pass. 3. **Approved work** — PR body links an issue (`Closes/Fixes/Resolves #N`) that carries the `approved` label and the PR author is in the issue's assignees → pass. 4. **Small fix** — `additions + deletions ≤ 50` → pass + apply `small-fix` label. 5. **Otherwise** — apply `auto-closed-needs-issue` label, post rejection comment, close. ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: evan
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { LABEL_COLORS, LABEL_DESCRIPTIONS, REPO } from "./config";
|
||||
import type { IssueMetadata, PRFile, PRMetadata } from "./rules";
|
||||
|
||||
export function makeOctokit(token: string): Octokit {
|
||||
return new Octokit({ auth: token });
|
||||
}
|
||||
|
||||
export async function getPR(
|
||||
octokit: Octokit,
|
||||
prNumber: number,
|
||||
): Promise<PRMetadata> {
|
||||
const { data } = await octokit.rest.pulls.get({
|
||||
...REPO,
|
||||
pull_number: prNumber,
|
||||
});
|
||||
return {
|
||||
number: data.number,
|
||||
body: data.body ?? null,
|
||||
user: { login: data.user?.login ?? "" },
|
||||
author_association: data.author_association,
|
||||
labels: (data.labels ?? [])
|
||||
.map((l) => l.name ?? "")
|
||||
.filter((name) => name.length > 0),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPRFiles(
|
||||
octokit: Octokit,
|
||||
prNumber: number,
|
||||
): Promise<PRFile[]> {
|
||||
const files = await octokit.paginate(octokit.rest.pulls.listFiles, {
|
||||
...REPO,
|
||||
pull_number: prNumber,
|
||||
per_page: 100,
|
||||
});
|
||||
return files.map((f) => ({ additions: f.additions, deletions: f.deletions }));
|
||||
}
|
||||
|
||||
export async function getIssue(
|
||||
octokit: Octokit,
|
||||
issueNumber: number,
|
||||
): Promise<IssueMetadata | null> {
|
||||
try {
|
||||
const { data } = await octokit.rest.issues.get({
|
||||
...REPO,
|
||||
issue_number: issueNumber,
|
||||
});
|
||||
return {
|
||||
number: data.number,
|
||||
labels: (data.labels ?? [])
|
||||
.map((l) => (typeof l === "string" ? l : (l.name ?? "")))
|
||||
.filter((name) => name.length > 0),
|
||||
assignees: (data.assignees ?? []).map((a) => ({ login: a.login })),
|
||||
};
|
||||
} catch (err) {
|
||||
if (isStatus(err, 404)) {
|
||||
console.warn(`[pr-gate] Issue #${issueNumber} not found, skipping`);
|
||||
return null;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureLabel(
|
||||
octokit: Octokit,
|
||||
name: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await octokit.rest.issues.getLabel({ ...REPO, name });
|
||||
} catch (err) {
|
||||
if (!isStatus(err, 404)) throw err;
|
||||
await octokit.rest.issues.createLabel({
|
||||
...REPO,
|
||||
name,
|
||||
color: LABEL_COLORS[name],
|
||||
description: LABEL_DESCRIPTIONS[name],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function addLabel(
|
||||
octokit: Octokit,
|
||||
prNumber: number,
|
||||
label: string,
|
||||
): Promise<void> {
|
||||
await octokit.rest.issues.addLabels({
|
||||
...REPO,
|
||||
issue_number: prNumber,
|
||||
labels: [label],
|
||||
});
|
||||
}
|
||||
|
||||
export async function postComment(
|
||||
octokit: Octokit,
|
||||
prNumber: number,
|
||||
body: string,
|
||||
): Promise<void> {
|
||||
await octokit.rest.issues.createComment({
|
||||
...REPO,
|
||||
issue_number: prNumber,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
export async function closePR(
|
||||
octokit: Octokit,
|
||||
prNumber: number,
|
||||
): Promise<void> {
|
||||
await octokit.rest.pulls.update({
|
||||
...REPO,
|
||||
pull_number: prNumber,
|
||||
state: "closed",
|
||||
});
|
||||
}
|
||||
|
||||
function isStatus(err: unknown, status: number): boolean {
|
||||
return (
|
||||
typeof err === "object" &&
|
||||
err !== null &&
|
||||
"status" in err &&
|
||||
(err as { status: unknown }).status === status
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user