ci: guard against generated catalog files in contributor PRs

- Add generated-files-guard.yml: fails non-maintainer PRs that commit
  docs/components.json or dashboard/public catalog artifacts and posts
  revert instructions (pull_request_target, API-only, no PR code checkout)
- Welcome bot now warns up front not to commit generated files
- CLAUDE.md: document who regenerates the catalog (maintainers only) and
  why contributor PRs must not include it

Claude-Session: https://claude.ai/code/session_01JVP5nv7a7bMkwvy67LgtPF
This commit is contained in:
Dani
2026-09-14 12:05:41 -04:00
parent 284d66bf5c
commit 31f852d8e7
3 changed files with 141 additions and 0 deletions
@@ -73,6 +73,12 @@ jobs:
`4. 📦 **Catalog regeneration** — the component catalog is rebuilt automatically.`,
`5. 🚀 **Live on [aitmpl.com](https://www.aitmpl.com)** — your component appears on the website after deploy.`,
``,
`### ⚠️ Do not commit generated catalog files`,
`Please make sure your PR does **not** include \`docs/components.json\` or anything under \`dashboard/public/\` `,
`(\`components.json\`, \`counts.json\`, \`search-index.json\`, \`components/*.json\`, \`component-content/**\`). `,
`Those files are regenerated automatically by \`scripts/generate_components_json.py\` after merge, and committing them causes merge conflicts. `,
`If you already ran the script, revert them with: \`git checkout origin/main -- docs/components.json dashboard/public/\``,
``,
`### While you wait`,
`- Check the **Security Audit** comment below for any issues to fix.`,
`- Make sure your component follows the [contribution guide](https://github.com/${owner}/${repo}/blob/main/CLAUDE.md#component-system).`,
+111
View File
@@ -0,0 +1,111 @@
name: Generated Files Guard
# Blocks external PRs that commit generated catalog artifacts. Those files are
# produced by scripts/generate_components_json.py (run by maintainers and the
# daily update-json-data workflow) and committing them from a fork causes merge
# conflicts with every other catalog change.
#
# Uses pull_request_target so it can comment on fork PRs. It never checks out
# or executes PR code — it only reads the changed-file list via the API.
on:
pull_request_target:
types: [opened, reopened, synchronize]
paths:
- 'docs/components.json'
- 'dashboard/public/components.json'
- 'dashboard/public/counts.json'
- 'dashboard/public/search-index.json'
- 'dashboard/public/components/**'
- 'dashboard/public/component-content/**'
permissions:
pull-requests: write
jobs:
guard:
name: No generated catalog files
runs-on: ubuntu-latest
steps:
- name: Check for generated catalog files in the PR
uses: actions/github-script@v8
with:
script: |
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
const prNumber = pr.number;
const marker = '<!-- generated-files-guard:v1 -->';
// Maintainers regenerate the catalog on purpose (e.g. sync PRs).
const trusted = ['OWNER', 'MEMBER', 'COLLABORATOR'];
if (trusted.includes(pr.author_association)) {
core.info(`Author is ${pr.author_association} — skipping guard.`);
return;
}
const generatedPatterns = [
/^docs\/components\.json$/,
/^dashboard\/public\/components\.json$/,
/^dashboard\/public\/counts\.json$/,
/^dashboard\/public\/search-index\.json$/,
/^dashboard\/public\/components\//,
/^dashboard\/public\/component-content\//,
];
const files = await github.paginate(github.rest.pulls.listFiles, {
owner, repo, pull_number: prNumber, per_page: 100,
});
const offending = files
.map(f => f.filename)
.filter(name => generatedPatterns.some(re => re.test(name)));
const existing = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});
const previous = existing.find(c => c.body && c.body.includes(marker));
if (offending.length === 0) {
if (previous) {
await github.rest.issues.updateComment({
owner, repo, comment_id: previous.id,
body: `${marker}\n✅ Generated catalog files have been removed from this PR. Thanks!`,
});
}
core.info('No generated catalog files in this PR.');
return;
}
const list = offending.map(f => `- \`${f}\``).join('\n');
const body = [
marker,
`## ⚠️ Please remove generated catalog files from this PR`,
``,
`This PR commits files that are **generated automatically** by \`scripts/generate_components_json.py\`. `,
`Committing them causes merge conflicts with every other PR that touches the catalog, so contributor PRs must not include them:`,
``,
list,
``,
`### How to fix`,
``,
`From your branch, restore those files to their \`main\` version and push:`,
``,
'```bash',
`git fetch origin main`,
`git checkout origin/main -- docs/components.json dashboard/public/`,
`git commit -m "chore: drop generated catalog files"`,
`git push`,
'```',
``,
`Your PR should only contain changes under \`cli-tool/components/\` (plus any supporting files). `,
`The catalog and dashboard counts are regenerated automatically after merge — you don't need to run the script.`,
``,
`_This check will pass automatically once the files above are no longer part of the PR._`,
].join('\n');
if (previous) {
await github.rest.issues.updateComment({ owner, repo, comment_id: previous.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
}
core.setFailed(`PR includes ${offending.length} generated catalog file(s). See the PR comment for instructions.`);
+24
View File
@@ -105,6 +105,30 @@ Use the component-reviewer agent to review [component-path]
`generate_components_json.py`, commit, or publish until testing is confirmed
or explicitly skipped by the user.
7. Run `python scripts/generate_components_json.py` to update catalog
(**maintainers only** — see below)
#### Generated catalog files: who regenerates them
`scripts/generate_components_json.py` writes `docs/components.json` and the
split `dashboard/public/` artifacts (`components.json`, `counts.json`,
`search-index.json`, `components/*.json`, `component-content/**`). These files
are **generated output, never hand-edited**, and who commits them depends on
the workflow:
| Workflow | Regenerate + commit the catalog? |
|----------|----------------------------------|
| Maintainer working directly on this repo (local branch, sync PRs, agent-driven migrations) | ✅ Yes — run the script and commit the output with the component |
| **External contributor PR (fork)** | ❌ **No** — the PR must only contain files under `cli-tool/components/` (plus supporting files). The catalog is regenerated automatically after merge (`update-json-data.yml` daily cron, or a maintainer). |
Why: the generated JSON files are single-line blobs that change on every
component/download-count update, so two PRs that both commit them always
conflict. `.github/workflows/generated-files-guard.yml` fails any
non-maintainer PR that touches them and posts revert instructions; the
`component-pr-welcome.yml` bot also warns about it up front.
When reviewing a contributor PR that includes these files, ask them to revert
with `git checkout origin/main -- docs/components.json dashboard/public/` rather
than resolving the conflict by hand.
**The component-reviewer agent checks:**
- ✅ Valid YAML frontmatter and required fields