chore(ci): externalize workflow scripts and relocate main flow doc (#722)
* feat: Add GitHub Actions workflows for security audits, CodeQL analysis, contributor updates, performance benchmarks, integration tests, fuzz testing, and reusable Rust build jobs - Implemented `sec-audit.yml` for Rust package security audits using `rustsec/audit-check` and `cargo-deny-action`. - Created `sec-codeql.yml` for CodeQL analysis scheduled twice daily. - Added `sync-contributors.yml` to update the NOTICE file with new contributors automatically. - Introduced `test-benchmarks.yml` for performance benchmarks using Criterion. - Established `test-e2e.yml` for running integration and end-to-end tests. - Developed `test-fuzz.yml` for fuzz testing with configurable runtime. - Created `test-rust-build.yml` as a reusable job for executing Rust commands with customizable parameters. - Documented main branch delivery flows in `main-branch-flow.md` for clarity on CI/CD processes. * ci(workflows): update workflow scripts and rename for clarity; remove obsolete lint feedback script * chore(ci): externalize workflow scripts and relocate main flow doc
This commit is contained in:
parent
41da46e2b2
commit
69a3b54968
34 changed files with 2090 additions and 1777 deletions
161
.github/workflows/scripts/pr_check_status_nudge.js
vendored
Normal file
161
.github/workflows/scripts/pr_check_status_nudge.js
vendored
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
// Extracted from pr-check-status.yml step: Nudge PRs that need rebase or CI refresh
|
||||
|
||||
module.exports = async ({ github, context, core }) => {
|
||||
const staleHours = Number(process.env.STALE_HOURS || "48");
|
||||
const ignoreLabels = new Set(["no-stale", "stale", "maintainer", "no-pr-hygiene"]);
|
||||
const marker = "<!-- pr-hygiene-nudge -->";
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const openPrs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: "open",
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const activePrs = openPrs.filter((pr) => {
|
||||
if (pr.draft) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const labels = new Set((pr.labels || []).map((label) => label.name));
|
||||
return ![...ignoreLabels].some((label) => labels.has(label));
|
||||
});
|
||||
|
||||
core.info(`Scanning ${activePrs.length} open PR(s) for hygiene nudges.`);
|
||||
|
||||
let nudged = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const pr of activePrs) {
|
||||
const { data: headCommit } = await github.rest.repos.getCommit({
|
||||
owner,
|
||||
repo,
|
||||
ref: pr.head.sha,
|
||||
});
|
||||
|
||||
const headCommitAt =
|
||||
headCommit.commit?.committer?.date || headCommit.commit?.author?.date;
|
||||
if (!headCommitAt) {
|
||||
skipped += 1;
|
||||
core.info(`#${pr.number}: missing head commit timestamp, skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ageHours = (Date.now() - new Date(headCommitAt).getTime()) / 3600000;
|
||||
if (ageHours < staleHours) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data: prDetail } = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr.number,
|
||||
});
|
||||
|
||||
const isBehindBase = prDetail.mergeable_state === "behind";
|
||||
|
||||
const { data: checkRunsData } = await github.rest.checks.listForRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: pr.head.sha,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const ciGateRuns = (checkRunsData.check_runs || [])
|
||||
.filter((run) => run.name === "CI Required Gate")
|
||||
.sort((a, b) => {
|
||||
const aTime = new Date(a.started_at || a.completed_at || a.created_at).getTime();
|
||||
const bTime = new Date(b.started_at || b.completed_at || b.created_at).getTime();
|
||||
return bTime - aTime;
|
||||
});
|
||||
|
||||
let ciState = "missing";
|
||||
if (ciGateRuns.length > 0) {
|
||||
const latest = ciGateRuns[0];
|
||||
if (latest.status !== "completed") {
|
||||
ciState = "in_progress";
|
||||
} else if (["success", "neutral", "skipped"].includes(latest.conclusion || "")) {
|
||||
ciState = "success";
|
||||
} else {
|
||||
ciState = String(latest.conclusion || "failure");
|
||||
}
|
||||
}
|
||||
|
||||
const ciMissing = ciState === "missing";
|
||||
const ciFailing = !["success", "in_progress", "missing"].includes(ciState);
|
||||
|
||||
if (!isBehindBase && !ciMissing && !ciFailing) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const reasons = [];
|
||||
if (isBehindBase) {
|
||||
reasons.push("- Branch is behind `main` (please rebase or merge the latest base branch).");
|
||||
}
|
||||
if (ciMissing) {
|
||||
reasons.push("- No `CI Required Gate` run was found for the current head commit.");
|
||||
}
|
||||
if (ciFailing) {
|
||||
reasons.push(`- Latest \`CI Required Gate\` result is \`${ciState}\`.`);
|
||||
}
|
||||
|
||||
const shortSha = pr.head.sha.slice(0, 12);
|
||||
const body = [
|
||||
marker,
|
||||
`Hi @${pr.user.login}, friendly automation nudge from PR hygiene.`,
|
||||
"",
|
||||
`This PR has had no new commits for **${Math.floor(ageHours)}h** and still needs an update before merge:`,
|
||||
"",
|
||||
...reasons,
|
||||
"",
|
||||
"### Recommended next steps",
|
||||
"1. Rebase your branch on `main`.",
|
||||
"2. Push the updated branch and re-run checks (or use **Re-run failed jobs**).",
|
||||
"3. Post fresh validation output in this PR thread.",
|
||||
"",
|
||||
"Maintainers: apply `no-stale` to opt out for accepted-but-blocked work.",
|
||||
`Head SHA: \`${shortSha}\``,
|
||||
].join("\n");
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const existing = comments.find(
|
||||
(comment) => comment.user?.type === "Bot" && comment.body?.includes(marker),
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
if (existing.body === body) {
|
||||
skipped += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
|
||||
nudged += 1;
|
||||
core.info(`#${pr.number}: hygiene nudge posted/updated.`);
|
||||
}
|
||||
|
||||
core.info(`Done. Nudged=${nudged}, skipped=${skipped}`);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue