mirror of
https://github.com/davila7/claude-code-templates.git
synced 2026-09-19 01:30:23 +08:00
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:
@@ -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).`,
|
||||
|
||||
@@ -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.`);
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user