ci: publish the README through the required validate check

Deploy keys are disabled for this org and GitHub Actions cannot be a ruleset bypass actor, so main is protected by requiring a passing validate check on every commit. The README workflow stages its regenerated commit on bot/readme-<sha>, dispatches validate there (allowed to touch generated files only for that bot branch), waits for success and fast-forwards main. Adds a catalog policy link to the README header and documents the branch protection.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
John SmithandClaude Opus 5.5 committed 2026-10-17 13:18:59 -04:00
1 parent 6279aab5e1
commit dcbe583a4b
5 files changed
+54 -11

No files matched your search

+31 -5
View File
@@ -10,8 +10,12 @@ on:
- 'scripts/**'
workflow_dispatch:
# main requires a passing "validate" check on every commit, so the regenerated README is first pushed
# to a bot/readme-* branch, validated there, and then fast-forwarded onto main.
permissions:
contents: write
actions: write
checks: read
concurrency:
group: auto-generate-readme-main
@@ -39,7 +43,9 @@ jobs:
- name: Install dependencies
run: npm ci --ignore-scripts
- name: Generate and push README/registry
- name: Generate, validate and publish README/registry
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
@@ -62,14 +68,34 @@ jobs:
git add README.md dist/registry.json
git commit -m "docs: auto-regenerate README and registry data"
sha="$(git rev-parse HEAD)"
branch="bot/readme-${sha:0:12}"
if git push origin HEAD:main; then
# Stage the commit and run the required "validate" check on it
git push --force origin "HEAD:refs/heads/${branch}"
gh workflow run validate-pr.yml --ref "${branch}"
conclusion=""
for i in $(seq 1 60); do
sleep 10
conclusion="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${sha}/check-runs?check_name=validate" \
--jq '[.check_runs[] | select(.status == "completed") | .conclusion][0] // ""')"
[ -n "${conclusion}" ] && break
done
if [ "${conclusion}" != "success" ]; then
git push origin --delete "${branch}" || true
echo "validate on ${sha} finished with '${conclusion:-timeout}'; not publishing" >&2
exit 1
fi
if git push origin "${sha}:refs/heads/main"; then
git push origin --delete "${branch}" || true
exit 0
fi
echo "Push raced with another main update; retrying..."
sleep 2
git push origin --delete "${branch}" || true
echo "main moved while validating; retrying..."
done
echo "Failed to push regenerated README/registry after retries" >&2
echo "Failed to publish regenerated README/registry after retries" >&2
exit 1
+8 -1
View File
@@ -41,7 +41,14 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
BASE_REF: ${{ github.event.pull_request.base.sha || 'origin/main' }}
run: node scripts/check-pr.js --base "$BASE_REF"
# The README bot stages its regenerated README on bot/readme-* and dispatches this check
ALLOW_GENERATED: ${{ github.event_name == 'workflow_dispatch' && github.actor == 'github-actions[bot]' && startsWith(github.ref_name, 'bot/readme-') }}
run: |
if [ "$ALLOW_GENERATED" = "true" ]; then
node scripts/check-pr.js --base "$BASE_REF" --allow-generated
else
node scripts/check-pr.js --base "$BASE_REF"
fi
- name: README and registry still generate
run: |
+5 -1
View File
@@ -49,13 +49,17 @@ Removal PRs are reviewed like any other PR; closing one keeps the entries until
A weekly scout searches npm for packages published since V2 shipped that depend on `@opencode/*`, keeps public, licensed, non-archived, non-fork repositories that are not already listed or in an open PR, and ranks them by weekly downloads, then stars. It posts a **Scout** issue; a maintainer ticking a candidate's box makes the bot open a PR for it. Add a package or repo to [`discovery/declined.txt`](../discovery/declined.txt) to stop suggesting it.
## Branch protection
`main` has a ruleset: every commit that lands on it must have a passing `validate` check, and force-pushes and deletion are blocked. Contributors and maintainers get the check through pull requests; bots get it by dispatching `validate` on their branch. Repository admins can merge a PR whose check is failing or pending (for a broken CI), but cannot push unvalidated commits directly.
## Automation map
| Workflow | When | What |
| :--- | :--- | :--- |
| `validate-pr.yml` | every PR | Required `validate` check |
| `triage.yml` | PR opened/updated | Labels and a results comment (base-branch code only) |
| `generate-readme.yml` | push to main | README.md and dist/registry.json; fails without writing on any invalid entry |
| `generate-readme.yml` | push to main | README.md and dist/registry.json; fails without writing on any invalid entry. The regenerated commit is staged on `bot/readme-*`, validated, then fast-forwarded onto main |
| `site.yml` | push to main, daily | Search site on GitHub Pages |
| `health.yml` | daily | Health report issue, rename and removal PRs |
| `reprobe.yml` | daily | Re-checks `needs-v2` PRs |
+8 -4
View File
@@ -1,7 +1,9 @@
#!/usr/bin/env node
/**
* The required "validate" check for pull requests.
* Usage: node scripts/check-pr.js --base <git-ref> [--no-probe]
* Usage: node scripts/check-pr.js --base <git-ref> [--no-probe] [--allow-generated]
*
* --allow-generated is passed only for the README bot's own staged commits (see generate-readme.yml).
*
* Compares HEAD against the merge base with <git-ref>, analyzes the changed files, writes a report to
* $GITHUB_STEP_SUMMARY (when set) and exits 1 on any blocking problem.
@@ -11,7 +13,7 @@ const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { loadCatalog } = require('./utils/catalog');
const { DATA_FILE_RE, analyzePullRequest, formatReport } = require('./utils/pr-checks');
const { DATA_FILE_RE, GENERATED_FILES, analyzePullRequest, formatReport } = require('./utils/pr-checks');
const ROOT = path.join(__dirname, '..');
const git = (...args) => execFileSync('git', args, { cwd: ROOT, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
@@ -20,7 +22,8 @@ function parseArgs(argv) {
const baseIndex = argv.indexOf('--base');
return {
base: baseIndex !== -1 ? argv[baseIndex + 1] : 'origin/main',
probe: !argv.includes('--no-probe')
probe: !argv.includes('--no-probe'),
allowGenerated: argv.includes('--allow-generated')
};
}
@@ -42,7 +45,7 @@ function changedFiles(mergeBase) {
}
async function main() {
const { base, probe } = parseArgs(process.argv.slice(2));
const { base, probe, allowGenerated } = parseArgs(process.argv.slice(2));
const mergeBase = git('merge-base', base, 'HEAD').trim();
const changes = changedFiles(mergeBase);
@@ -61,6 +64,7 @@ async function main() {
const baseEntries = entries.filter(e => !changedPaths.has(e.file));
const result = await analyzePullRequest({ changes, baseEntries, probe });
if (allowGenerated) result.errors = result.errors.filter(e => !GENERATED_FILES.includes(e.file));
// Unparseable files anywhere in the catalog break README generation, so they always block
for (const err of loadErrors) {
if (!result.errors.some(e => e.file === err.file)) result.errors.push({ ...err, kind: 'metadata' });
+2
View File
@@ -45,6 +45,8 @@
<a href="https://heretek-ai.github.io/awesome-opencode/"><b>🔎 Search &amp; filter the catalog</b></a>
&nbsp;•&nbsp;
<a href="https://github.com/Heretek-AI/awesome-opencode/issues/new?template=add-entry.yml"><b>➕ Submit an entry</b></a>
&nbsp;•&nbsp;
<a href="https://github.com/Heretek-AI/awesome-opencode/blob/main/docs/policy.md"><b>📋 Catalog policy</b></a>
</p>
<br>