Skip to content

Commit 676f876

Browse files
Add 'Request a Repo' button and issue template (#3)
* Add "Request a Repo" feature to the repos homepage Add a GitHub issue template with a structured form for anyone to request a public repository be added to the architecture docs. Add a styled "Request a Repo" button in the site header nav that links directly to the issue template. Closes supermodeltools/arch-docs#13 * Add fully automated repo onboarding pipeline - Simplify issue template to just a URL field (low friction) - Add auto-add-repo.yml workflow that triggers on issue creation: - Validates repo (public, not archived, not empty, not duplicate) - Auto-detects description, language, pill from GitHub API - Forks repo into org, sets up arch-docs workflow, enables Pages - Appends to repos.yaml and commits (triggers homepage rebuild) - Comments on issue with result and closes it Required secrets: BOT_TOKEN (PAT with repo + admin:org scope), SUPERMODEL_API_KEY (for arch-docs on forked repos) * Add inline submit form and org-wide repo sync Homepage changes: - Add inline submit form below search: paste a GitHub URL, see a preview of where docs will live, one click to submit - Accepts full URLs or owner/repo shorthand - "No results" area now links to the submit form - Mobile-responsive layout for the submit row Sync workflow (sync-repos.yml): - Runs every 6 hours + manual trigger - Scans all org repos for arch-docs workflow - Auto-adds any missing repos to repos.yaml - Forks → Community category, org-native → Supermodel Open Source - Auto-detects description, language, pill from GitHub API - Commits and pushes to trigger homepage rebuild This ensures repos like volt that are set up internally but never added to repos.yaml will auto-appear on the homepage.
1 parent 0b24b41 commit 676f876

File tree

4 files changed

+603
-3
lines changed

4 files changed

+603
-3
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Request a Repository
2+
description: Request a public repository to be added to the architecture docs
3+
title: "[Repo Request] "
4+
labels: ["repo-request"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Paste the GitHub URL of a public repository below. Everything else is automatic!
10+
11+
- type: input
12+
id: repo_url
13+
attributes:
14+
label: Repository URL
15+
description: "GitHub URL (e.g. https://github.com/facebook/react)"
16+
placeholder: "https://github.com/owner/repo"
17+
validations:
18+
required: true
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
name: Auto-add Requested Repo
2+
3+
on:
4+
issues:
5+
types: [opened, labeled]
6+
7+
concurrency:
8+
group: add-repo
9+
cancel-in-progress: false
10+
11+
jobs:
12+
process-request:
13+
if: contains(github.event.issue.labels.*.name, 'repo-request')
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: write
17+
issues: write
18+
env:
19+
ISSUE_NUMBER: ${{ github.event.issue.number }}
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Parse repo URL from issue
24+
id: parse
25+
env:
26+
ISSUE_BODY: ${{ github.event.issue.body }}
27+
run: |
28+
REPO_URL=$(echo "$ISSUE_BODY" | grep -oE 'https://github\.com/[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+' | head -1)
29+
30+
if [ -z "$REPO_URL" ]; then
31+
echo "error=true" >> "$GITHUB_OUTPUT"
32+
echo "error_msg=No valid GitHub repository URL found in the issue." >> "$GITHUB_OUTPUT"
33+
exit 0
34+
fi
35+
36+
# Normalize: strip trailing slashes or .git
37+
REPO_URL=$(echo "$REPO_URL" | sed 's|\.git$||; s|/$||')
38+
UPSTREAM=$(echo "$REPO_URL" | sed 's|https://github.com/||')
39+
REPO_NAME=$(echo "$UPSTREAM" | cut -d'/' -f2)
40+
41+
echo "repo_url=$REPO_URL" >> "$GITHUB_OUTPUT"
42+
echo "upstream=$UPSTREAM" >> "$GITHUB_OUTPUT"
43+
echo "repo_name=$REPO_NAME" >> "$GITHUB_OUTPUT"
44+
45+
- name: Validate repository
46+
if: steps.parse.outputs.error != 'true'
47+
id: validate
48+
env:
49+
GH_TOKEN: ${{ secrets.BOT_TOKEN }}
50+
UPSTREAM: ${{ steps.parse.outputs.upstream }}
51+
REPO_NAME: ${{ steps.parse.outputs.repo_name }}
52+
run: |
53+
# 1. Check repo exists and is accessible
54+
if ! REPO_JSON=$(gh api "repos/$UPSTREAM" 2>/dev/null); then
55+
echo "error=true" >> "$GITHUB_OUTPUT"
56+
echo "error_msg=Repository \`$UPSTREAM\` not found or not accessible." >> "$GITHUB_OUTPUT"
57+
exit 0
58+
fi
59+
60+
# 2. Must be public
61+
IS_PRIVATE=$(echo "$REPO_JSON" | jq -r '.private')
62+
if [ "$IS_PRIVATE" = "true" ]; then
63+
echo "error=true" >> "$GITHUB_OUTPUT"
64+
echo "error_msg=Repository is private. Only public repositories can be added." >> "$GITHUB_OUTPUT"
65+
exit 0
66+
fi
67+
68+
# 3. Must not be archived
69+
IS_ARCHIVED=$(echo "$REPO_JSON" | jq -r '.archived')
70+
if [ "$IS_ARCHIVED" = "true" ]; then
71+
echo "error=true" >> "$GITHUB_OUTPUT"
72+
echo "error_msg=Repository is archived." >> "$GITHUB_OUTPUT"
73+
exit 0
74+
fi
75+
76+
# 4. Must not be empty
77+
SIZE=$(echo "$REPO_JSON" | jq -r '.size')
78+
if [ "$SIZE" = "0" ]; then
79+
echo "error=true" >> "$GITHUB_OUTPUT"
80+
echo "error_msg=Repository appears to be empty." >> "$GITHUB_OUTPUT"
81+
exit 0
82+
fi
83+
84+
# 5. Must not already be listed
85+
if grep -q "^ - name: ${REPO_NAME}$" repos.yaml; then
86+
echo "error=true" >> "$GITHUB_OUTPUT"
87+
echo "error_msg=Repository \`$REPO_NAME\` is already listed on the homepage." >> "$GITHUB_OUTPUT"
88+
exit 0
89+
fi
90+
91+
# 6. Must not conflict with an existing org repo that isn't a fork of this upstream
92+
if gh api "repos/supermodeltools/$REPO_NAME" &>/dev/null; then
93+
EXISTING_PARENT=$(gh api "repos/supermodeltools/$REPO_NAME" --jq '.parent.full_name // ""')
94+
if [ -n "$EXISTING_PARENT" ] && [ "$EXISTING_PARENT" != "$UPSTREAM" ]; then
95+
echo "error=true" >> "$GITHUB_OUTPUT"
96+
echo "error_msg=A repo named \`$REPO_NAME\` already exists in the org as a fork of \`$EXISTING_PARENT\`, not \`$UPSTREAM\`." >> "$GITHUB_OUTPUT"
97+
exit 0
98+
fi
99+
fi
100+
101+
# Extract metadata
102+
DESCRIPTION=$(echo "$REPO_JSON" | jq -r '.description // "No description provided"' | head -c 200)
103+
LANGUAGE=$(echo "$REPO_JSON" | jq -r '.language // "Unknown"')
104+
STARS=$(echo "$REPO_JSON" | jq -r '.stargazers_count')
105+
106+
echo "valid=true" >> "$GITHUB_OUTPUT"
107+
echo "stars=$STARS" >> "$GITHUB_OUTPUT"
108+
echo "language=$LANGUAGE" >> "$GITHUB_OUTPUT"
109+
{
110+
echo "description<<DESCEOF"
111+
echo "$DESCRIPTION"
112+
echo "DESCEOF"
113+
} >> "$GITHUB_OUTPUT"
114+
115+
- name: Map language to pill
116+
if: steps.validate.outputs.valid == 'true'
117+
id: pill
118+
env:
119+
LANGUAGE: ${{ steps.validate.outputs.language }}
120+
run: |
121+
case "$LANGUAGE" in
122+
JavaScript) PILL="JavaScript"; PILL_CLASS="pill-blue" ;;
123+
TypeScript) PILL="TypeScript"; PILL_CLASS="pill-blue" ;;
124+
Python) PILL="Python"; PILL_CLASS="pill-green" ;;
125+
Go) PILL="Go"; PILL_CLASS="pill-accent" ;;
126+
Rust) PILL="Rust"; PILL_CLASS="pill-accent" ;;
127+
Java) PILL="Java"; PILL_CLASS="pill-orange" ;;
128+
Kotlin) PILL="Kotlin"; PILL_CLASS="pill-orange" ;;
129+
Scala) PILL="Scala"; PILL_CLASS="pill-orange" ;;
130+
Ruby) PILL="Ruby"; PILL_CLASS="pill-orange" ;;
131+
C) PILL="C"; PILL_CLASS="pill-accent" ;;
132+
C++) PILL="C++"; PILL_CLASS="pill-accent" ;;
133+
C#) PILL="C#"; PILL_CLASS="pill-green" ;;
134+
Swift) PILL="Swift"; PILL_CLASS="pill-orange" ;;
135+
PHP) PILL="PHP"; PILL_CLASS="pill-blue" ;;
136+
CSS) PILL="CSS"; PILL_CLASS="pill-blue" ;;
137+
Shell|Bash) PILL="DevOps"; PILL_CLASS="pill-green" ;;
138+
HCL) PILL="DevOps"; PILL_CLASS="pill-green" ;;
139+
Jupyter*) PILL="AI/ML"; PILL_CLASS="pill-orange" ;;
140+
*) PILL="$LANGUAGE"; PILL_CLASS="pill-blue" ;;
141+
esac
142+
echo "pill=$PILL" >> "$GITHUB_OUTPUT"
143+
echo "pill_class=$PILL_CLASS" >> "$GITHUB_OUTPUT"
144+
145+
- name: Fork and setup arch-docs
146+
if: steps.validate.outputs.valid == 'true'
147+
env:
148+
GH_TOKEN: ${{ secrets.BOT_TOKEN }}
149+
SUPERMODEL_API_KEY: ${{ secrets.SUPERMODEL_API_KEY }}
150+
UPSTREAM: ${{ steps.parse.outputs.upstream }}
151+
REPO_NAME: ${{ steps.parse.outputs.repo_name }}
152+
run: |
153+
ORG="supermodeltools"
154+
FORK="${ORG}/${REPO_NAME}"
155+
156+
# 1. Fork (skip if already exists)
157+
if gh repo view "$FORK" &>/dev/null; then
158+
echo "Fork $FORK already exists, skipping fork"
159+
else
160+
echo "Forking $UPSTREAM into $ORG..."
161+
gh repo fork "$UPSTREAM" --org "$ORG" --clone=false
162+
echo "Waiting for fork to be ready..."
163+
sleep 5
164+
fi
165+
166+
# 2. Set the SUPERMODEL_API_KEY secret
167+
echo "Setting SUPERMODEL_API_KEY secret..."
168+
gh secret set SUPERMODEL_API_KEY --repo "$FORK" --body "$SUPERMODEL_API_KEY"
169+
170+
# 3. Detect default branch
171+
DEFAULT_BRANCH=$(gh api "repos/$FORK" --jq '.default_branch')
172+
echo "Default branch: $DEFAULT_BRANCH"
173+
174+
# 4. Push the arch-docs workflow
175+
WORKFLOW_CONTENT='name: Architecture Docs
176+
177+
on:
178+
push:
179+
branches: [main, master]
180+
workflow_dispatch:
181+
182+
permissions:
183+
contents: write
184+
pages: write
185+
id-token: write
186+
187+
concurrency:
188+
group: pages
189+
cancel-in-progress: true
190+
191+
jobs:
192+
build-and-deploy:
193+
runs-on: ubuntu-latest
194+
environment:
195+
name: github-pages
196+
url: ${{ steps.deploy.outputs.page_url }}
197+
steps:
198+
- uses: actions/checkout@v4
199+
200+
- uses: supermodeltools/arch-docs@main
201+
id: docs
202+
with:
203+
supermodel-api-key: ${{ secrets.SUPERMODEL_API_KEY }}
204+
205+
- uses: actions/configure-pages@v5
206+
207+
- uses: actions/upload-pages-artifact@v3
208+
with:
209+
path: ./arch-docs-output
210+
211+
- uses: actions/deploy-pages@v4
212+
id: deploy'
213+
214+
# Dedent the workflow content (remove leading spaces from heredoc)
215+
WORKFLOW_CONTENT=$(echo "$WORKFLOW_CONTENT" | sed 's/^ //')
216+
ENCODED=$(echo -n "$WORKFLOW_CONTENT" | base64 -w0)
217+
218+
EXISTING_SHA=$(gh api "repos/$FORK/contents/.github/workflows/arch-docs.yml" --jq '.sha' 2>/dev/null || echo "")
219+
220+
if [ -n "$EXISTING_SHA" ]; then
221+
gh api --method PUT "repos/$FORK/contents/.github/workflows/arch-docs.yml" \
222+
-f message="Add arch-docs workflow" \
223+
-f content="$ENCODED" \
224+
-f branch="$DEFAULT_BRANCH" \
225+
-f sha="$EXISTING_SHA" \
226+
--silent
227+
else
228+
gh api --method PUT "repos/$FORK/contents/.github/workflows/arch-docs.yml" \
229+
-f message="Add arch-docs workflow" \
230+
-f content="$ENCODED" \
231+
-f branch="$DEFAULT_BRANCH" \
232+
--silent
233+
fi
234+
235+
# 5. Enable GitHub Pages
236+
echo "Enabling GitHub Pages..."
237+
gh api --method POST "repos/$FORK/pages" \
238+
-f build_type="workflow" \
239+
--silent 2>/dev/null || \
240+
gh api --method PUT "repos/$FORK/pages" \
241+
-f build_type="workflow" \
242+
--silent 2>/dev/null || \
243+
echo "Pages may already be configured"
244+
245+
# 6. Trigger the arch-docs workflow
246+
echo "Triggering arch-docs workflow..."
247+
gh workflow run arch-docs.yml --repo "$FORK" --ref "$DEFAULT_BRANCH" 2>/dev/null || \
248+
echo "Workflow trigger pending — will run on next push"
249+
250+
echo "Setup complete for $FORK"
251+
252+
- name: Add repo to repos.yaml
253+
if: steps.validate.outputs.valid == 'true'
254+
env:
255+
REPO_NAME: ${{ steps.parse.outputs.repo_name }}
256+
UPSTREAM: ${{ steps.parse.outputs.upstream }}
257+
DESCRIPTION: ${{ steps.validate.outputs.description }}
258+
PILL: ${{ steps.pill.outputs.pill }}
259+
PILL_CLASS: ${{ steps.pill.outputs.pill_class }}
260+
run: |
261+
# Use yq to append to the Community category (index 1)
262+
yq -i '.categories[1].repos += [{
263+
"name": strenv(REPO_NAME),
264+
"upstream": strenv(UPSTREAM),
265+
"description": strenv(DESCRIPTION),
266+
"pill": strenv(PILL),
267+
"pill_class": strenv(PILL_CLASS)
268+
}]' repos.yaml
269+
270+
echo "Added $REPO_NAME to repos.yaml"
271+
tail -8 repos.yaml
272+
273+
- name: Commit and push
274+
if: steps.validate.outputs.valid == 'true'
275+
env:
276+
GH_TOKEN: ${{ secrets.BOT_TOKEN }}
277+
REPO_NAME: ${{ steps.parse.outputs.repo_name }}
278+
UPSTREAM: ${{ steps.parse.outputs.upstream }}
279+
run: |
280+
# Pull latest in case another request was processed concurrently
281+
git pull --rebase origin main
282+
283+
git config user.name "supermodel-bot"
284+
git config user.email "bot@supermodeltools.com"
285+
git add repos.yaml
286+
git commit -m "Add ${REPO_NAME} to community repos (closes #${ISSUE_NUMBER})"
287+
288+
# Push with BOT_TOKEN so it triggers the build-index workflow
289+
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
290+
git push origin main
291+
292+
- name: Comment and close issue (success)
293+
if: steps.validate.outputs.valid == 'true'
294+
env:
295+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
296+
REPO_NAME: ${{ steps.parse.outputs.repo_name }}
297+
UPSTREAM: ${{ steps.parse.outputs.upstream }}
298+
STARS: ${{ steps.validate.outputs.stars }}
299+
run: |
300+
gh issue comment "$ISSUE_NUMBER" --body "$(cat <<EOF
301+
✅ **Done!** \`$UPSTREAM\` has been added automatically.
302+
303+
| | |
304+
|---|---|
305+
| **Repo** | [$UPSTREAM](https://github.com/$UPSTREAM) |
306+
| **Stars** | $STARS |
307+
| **Arch docs** | [repos.supermodeltools.com/$REPO_NAME](https://repos.supermodeltools.com/$REPO_NAME/) |
308+
| **Homepage** | [repos.supermodeltools.com](https://repos.supermodeltools.com/) |
309+
310+
The architecture docs are being generated now. They'll be live at the link above shortly.
311+
EOF
312+
)"
313+
gh issue close "$ISSUE_NUMBER" --reason completed
314+
315+
- name: Comment on issue (validation failed)
316+
if: steps.parse.outputs.error == 'true' || steps.validate.outputs.error == 'true'
317+
env:
318+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
319+
PARSE_ERROR: ${{ steps.parse.outputs.error_msg }}
320+
VALIDATE_ERROR: ${{ steps.validate.outputs.error_msg }}
321+
run: |
322+
ERROR_MSG="${PARSE_ERROR:-$VALIDATE_ERROR}"
323+
gh issue comment "$ISSUE_NUMBER" --body "❌ **Could not add this repository.**
324+
325+
$ERROR_MSG
326+
327+
Please fix the issue and open a new request."
328+
gh issue close "$ISSUE_NUMBER" --reason "not_planned"

0 commit comments

Comments
 (0)