Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/actions/post-review-comment/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Post review comment
description: >-
Create or update the single repository-rules review comment on the PR.
Report text is HTML-escaped and wrapped in <pre>; it is treated as data.

inputs:
report-path:
description: Path to the plain-text review report
required: false
default: review-report.txt
max-report-chars:
description: Truncate the report beyond this many characters
required: false
default: "60000"

runs:
using: composite
steps:
- uses: actions/github-script@v7
env:
REPORT_PATH: ${{ inputs.report-path }}
MAX_REPORT_CHARS: ${{ inputs.max-report-chars }}
with:
script: |
const fs = require("node:fs");
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.payload.pull_request.number;
const reportPath = process.env.REPORT_PATH;
const maxReportChars = Number(process.env.MAX_REPORT_CHARS);

function escapeReport(text) {
let value = text;
if (value.length > maxReportChars) {
value = value.slice(0, maxReportChars) + "\n...[truncated]";
}
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/@/g, "@&#8203;");
}

let body = "## Repository rules review\n\n";
if (fs.existsSync(reportPath)) {
body += "<pre>" + escapeReport(fs.readFileSync(reportPath, "utf8")) + "</pre>\n";
} else {
body += "Review step did not produce a report.\n";
}

const marker = "<!-- repository-rules-review -->";
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number,
per_page: 100,
});
const existing = comments.find(
(comment) => comment.user.type === "Bot" && comment.body.includes(marker)
);

body += `\n${marker}`;
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body,
});
}
80 changes: 80 additions & 0 deletions .github/actions/verify-review-label/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
name: Verify review label
description: >-
Fail unless the named label was applied by a maintain/admin collaborator
after the latest PR head update. Prevents a stale or non-maintainer label
from skipping the repository-rules review.

inputs:
label:
description: Label name that grants the skip or waiver
required: true

runs:
using: composite
steps:
- uses: actions/github-script@v7
env:
REVIEW_LABEL: ${{ inputs.label }}
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.payload.pull_request.number;
const labelName = process.env.REVIEW_LABEL;
const allowed = new Set(["admin", "maintain"]);

const events = await github.paginate(github.rest.issues.listEventsForTimeline, {
owner,
repo,
issue_number,
per_page: 100,
});
const labeledEvents = events.filter(
(event) =>
event.event === "labeled" &&
event.label?.name === labelName &&
event.actor?.login
);
const labelEvent = labeledEvents.length
? labeledEvents[labeledEvents.length - 1]
: null;
if (!labelEvent) {
core.setFailed(`Could not find a timeline event for ${labelName}; remove and reapply the label.`);
return;
}
if (context.payload.action === "synchronize") {
core.setFailed(`The ${labelName} label must be reapplied after the latest PR push.`);
return;
}
const labelTime = Date.parse(labelEvent.created_at);
if (!Number.isFinite(labelTime)) {
core.setFailed(`Could not determine when ${labelName} was applied; remove and reapply the label.`);
return;
}
const headUpdateTimes = events
.filter(
(event) =>
["committed", "head_ref_force_pushed"].includes(event.event) &&
event.created_at
)
.map((event) => Date.parse(event.created_at))
.filter((time) => Number.isFinite(time));
const latestHeadUpdate = headUpdateTimes.length
? Math.max(...headUpdateTimes)
: null;
if (latestHeadUpdate !== null && labelTime <= latestHeadUpdate) {
core.setFailed(`The ${labelName} label is older than the latest PR head update; reapply it.`);
return;
}
const labelActor = labelEvent.actor.login;
const {data} = await github.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username: labelActor,
});
if (!allowed.has(data.role_name)) {
core.setFailed(
`Actor ${labelActor} has ${data.role_name} repository role; ` +
`the ${labelName} label requires the maintain or admin role.`
);
}
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ jobs:
- name: Run facade README doctests
run: cargo test -p strided-rs --doc

scripts:
name: maintenance scripts
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test the repository-rules review script
run: python3 scripts/test-repository-rules-review.py

coverage:
name: coverage
runs-on: ubuntu-latest
Expand Down
Loading
Loading