mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 09:45:24 -04:00
3557a3f495
#3336 reduced the PR-checks workflow to pull-requests:read on the assumption that PR labels/comments only need issues:write (the REST path is /issues/{n}/...). They do not: modifying a pull request's labels or comments requires the pull-requests scope, so issues:write alone returns 403 and crashed the description check on every PR. Restore pull-requests:write, and fail soft in swapLabel so a label-permission error can never mask the description verdict.
106 lines
4.6 KiB
YAML
106 lines
4.6 KiB
YAML
name: ci / PR checks
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, edited, synchronize, reopened, ready_for_review]
|
|
|
|
# pull_request_target runs in the base-repo context (has secrets).
|
|
# The checkout below pins to the base branch so no fork code is executed.
|
|
# The script only reads context.payload and calls the GitHub API.
|
|
# Least privilege: contents:read for the base-ref checkout, pull-requests:write
|
|
# to add/remove labels and post comments on PRs, and issues:write for the same
|
|
# on real issues. NOTE: modifying a *pull request's* labels/comments needs the
|
|
# `pull-requests` scope even though the REST path is under `/issues/{n}/...`;
|
|
# `issues:write` alone returns 403 on PRs.
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
issues: write
|
|
|
|
jobs:
|
|
check-description:
|
|
name: Check PR description
|
|
runs-on: ubuntu-latest
|
|
# Skip bots: they open PRs programmatically and have their own process.
|
|
if: github.event.pull_request.user.type != 'Bot'
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.base_ref }}
|
|
sparse-checkout: .github/scripts
|
|
|
|
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: return require('./.github/scripts/check-pr-description.js')({github, context, core})
|
|
|
|
check-title:
|
|
name: Check PR title (Conventional Commits)
|
|
runs-on: ubuntu-latest
|
|
# Skip bots: they open PRs programmatically and have their own process.
|
|
if: github.event.pull_request.user.type != 'Bot'
|
|
steps:
|
|
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: |
|
|
const title = context.payload.pull_request.title || "";
|
|
// Conventional Commits: type(optional-scope)(optional !): summary
|
|
const re = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([\w .\/-]+\))?!?: .+/;
|
|
if (!re.test(title)) {
|
|
core.setFailed(
|
|
`PR title is not in Conventional Commits format:\n "${title}"\n\n` +
|
|
`Expected: type(scope): summary\n` +
|
|
`Example: fix(search): handle empty query\n` +
|
|
`Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert.`
|
|
);
|
|
} else {
|
|
core.info(`PR title OK: ${title}`);
|
|
}
|
|
|
|
check-mergeable:
|
|
name: Flag unmergeable PRs
|
|
runs-on: ubuntu-latest
|
|
# Skip bots: they open PRs programmatically and have their own process.
|
|
if: github.event.pull_request.user.type != 'Bot'
|
|
steps:
|
|
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: |
|
|
const repo = { owner: context.repo.owner, repo: context.repo.repo };
|
|
const number = context.payload.pull_request.number;
|
|
const READY = "ready for review";
|
|
const CONFLICT = "merge conflict";
|
|
|
|
// Ensure the conflict label exists (red). Ignore if already present.
|
|
try {
|
|
await github.rest.issues.getLabel({ ...repo, name: CONFLICT });
|
|
} catch {
|
|
await github.rest.issues.createLabel({
|
|
...repo, name: CONFLICT, color: "B60205",
|
|
description: "Conflicts with the base branch; needs a rebase before review.",
|
|
}).catch(() => {});
|
|
}
|
|
|
|
// mergeable is computed asynchronously and is often null right after
|
|
// an event, so poll a few times until GitHub has resolved it.
|
|
let pr = null;
|
|
for (let i = 0; i < 5; i++) {
|
|
const { data } = await github.rest.pulls.get({ ...repo, pull_number: number });
|
|
if (data.mergeable !== null) { pr = data; break; }
|
|
await new Promise(r => setTimeout(r, 3000));
|
|
}
|
|
if (!pr || pr.draft) return;
|
|
const labels = pr.labels.map(l => l.name);
|
|
|
|
if (pr.mergeable === false) {
|
|
if (labels.includes(READY)) {
|
|
await github.rest.issues.removeLabel({ ...repo, issue_number: number, name: READY }).catch(() => {});
|
|
}
|
|
if (!labels.includes(CONFLICT)) {
|
|
await github.rest.issues.addLabels({ ...repo, issue_number: number, labels: [CONFLICT] });
|
|
}
|
|
} else if (pr.mergeable === true) {
|
|
if (labels.includes(CONFLICT)) {
|
|
await github.rest.issues.removeLabel({ ...repo, issue_number: number, name: CONFLICT }).catch(() => {});
|
|
}
|
|
}
|