Single source of truth for my GitHub profile — the README on
github.com/axisrow and the Projects section on
axisrow.github.io are generated from here.
projects.json ──▶ sync/generate.py ──▶ out/axisrow/README.md ──▶ axisrow/axisrow
(live stars out/site/projects.html ──▶ axisrow.github.io
from GitHub API)
- Edit
projects.json(descriptions, grouping, contributions, stats) — commit tomain. - A GitHub Actions workflow (
.github/workflows/sync.yml) runs on every push toprojects.json/sync/**, daily, and on manual dispatch. - It mints a short-lived GitHub App installation token via
pat(CI mode — PEM from theAPP_PRIVATE_KEYsecret, no Bitwarden in CI), pulls live star counts, renders both outputs, and cross-pushes them to the two output repos asaxisrow-ci[bot]. - Portfolio fragments are replaced only inside the stable
PROFILE:PROJECTSandPROFILE:STARSmarker blocks. The sync never patches the site's JavaScript or unrelated presentation markup.
Change projects.json only. The generated files live in out/ (gitignored) and
in the output repos — never edit them there directly.
To preview locally:
GH_TOKEN="$(bash /path/to/pat/scripts/pat.sh token)" python3 sync/generate.py
diff out/axisrow/README.md <(gh api repos/axisrow/axisrow/contents/README.md -q .content | base64 -d)APP_PRIVATE_KEY— theaxisrow-ciGitHub App private key (PEM). One-time, never expires;patmints a fresh 1h token from it on every run.PAT_APP_ID—4278593(the App ID; not secret).
projects.json tracks a fixed set of repos (the starred ones). New repos are not
added automatically — only the ones listed. Per-repo star counts are pulled live
from the GitHub API on each run; the aggregate stats (stars_earned,
merged_upstream_prs) are verified snapshots. The site also keeps a daily star
history from 2026-03-01: it uses an opening balance plus dated GitHub star
events so the restored series ends at 99 original-repository stars (104 total
minus 5 fork stars).