Files
flathub/.github/workflows/stale-blocked.yml
T
2026-02-26 06:57:11 +05:30

154 lines
5.7 KiB
YAML

name: "Close stale blocked PRs"
on:
schedule:
- cron: "0 0 * * 1"
workflow_dispatch:
inputs:
dry_run:
description: "Dry run"
type: boolean
default: false
jobs:
close-blocked:
permissions:
pull-requests: write
runs-on: ubuntu-latest
steps:
- name: Close stale blocked PRs
# 8.0.0
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
env:
DRY_RUN: ${{ inputs.dry_run || 'false' }}
DAYS_THRESHOLD: "14"
LABEL_BLOCKED: "blocked"
LABEL_CHECKS: "pr-check-blocked"
COMMENT_BLOCKED: |-
This pull request has been marked as blocked for {days} days
and is not ready for inclusion. It is being automatically closed.
COMMENT_CHECKS: |-
This pull request is failing checks for {days} days
and is not ready for inclusion. It is being automatically closed.
COMMENT_BOTH: |-
This pull request has been marked as blocked and is failing
checks for {days} days. It is not ready for inclusion. It is being
automatically closed.
EXEMPT_LABEL: "leave-open"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const labelBlocked = process.env.LABEL_BLOCKED || "blocked";
const labelChecks = process.env.LABEL_CHECKS || "pr-check-blocked";
const daysThreshold = parseInt(process.env.DAYS_THRESHOLD || "14", 10);
const exemptLabel = process.env.EXEMPT_LABEL || "leave-open";
const dryRun = process.env.DRY_RUN === "true";
if (daysThreshold < 2) throw new Error(`DAYS_THRESHOLD must be >= 2, got ${daysThreshold}`);
const now = new Date();
const commentTemplates = {
blocked: process.env.COMMENT_BLOCKED,
checks: process.env.COMMENT_CHECKS,
both: process.env.COMMENT_BOTH,
};
for (const [key, val] of Object.entries(commentTemplates)) {
if (!val?.trim()) throw new Error(`Comment template "${key}" is empty or missing`);
}
const prs = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
per_page: 100
});
for (const pr of prs) {
const prLabels = pr.labels.map(l => l.name);
const hasBlocked = prLabels.includes(labelBlocked);
const hasChecks = prLabels.includes(labelChecks);
if (!hasBlocked && !hasChecks) continue;
if (pr.draft) continue;
if (prLabels.includes(exemptLabel)) continue;
const effectiveThreshold = hasChecks
? Math.floor(daysThreshold / 2)
: daysThreshold;
const commentTemplate = hasBlocked && hasChecks
? commentTemplates.both
: hasChecks
? commentTemplates.checks
: commentTemplates.blocked;
try {
const events = await github.paginate(
github.rest.issues.listEventsForTimeline,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number
}
);
const relevantLabels = [];
if (hasBlocked) relevantLabels.push(labelBlocked);
if (hasChecks) relevantLabels.push(labelChecks);
const labelEvents = events
.filter(e =>
e.event === "labeled" &&
relevantLabels.includes(e.label?.name)
)
.map(e => new Date(e.created_at));
if (labelEvents.length === 0) continue;
const appliedDate = new Date(
Math.min(...labelEvents.map(d => d.getTime()))
);
const diffDays = (now - appliedDate) / (1000 * 60 * 60 * 24);
if (diffDays >= effectiveThreshold) {
const days = Math.floor(diffDays);
let reason;
if (hasBlocked && hasChecks) {
reason = "marked as blocked and failing checks";
} else if (hasChecks) {
reason = "failing checks";
} else {
reason = "marked as blocked";
}
const msg = `Closing PR ${pr.html_url} because it has been ${reason} for ${days} days`;
if (dryRun) {
console.log(`[DRY RUN] ${msg}`);
} else {
console.log(msg);
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentTemplate.replace("{days}", Math.floor(diffDays))
});
} catch (err) {
console.warn(`Failed to comment on PR ${pr.html_url}: ${err.message}`);
}
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
state: "closed"
});
}
}
} catch (err) {
console.error(`Error processing PR ${pr.html_url}: ${err.message}`);
}
}