docs(readme): replace star-history.com with self-generated chart (#11171)

* docs(readme): replace star-history.com with self-generated chart

The star-history.com SVG is a third-party dependency that can rate
limit or go down. Replace it with a GitHub Action that fetches
stargazers via the REST API and renders an SVG with matplotlib,
committing note/star_history.svg weekly. The README references the
committed file directly, so the chart has no runtime dependency on
any external service.

* ci(star-history): run daily instead of weekly
This commit is contained in:
Chris Lu
2026-09-04 23:45:50 -07:00
committed by GitHub
parent 070174726d
commit 567578f08d
4 changed files with 1827 additions and 1 deletions
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Render the repository's star history to note/star_history.svg.
Uses the GitHub REST stargazers endpoint with the starred-at accept header,
which caps at 40,000 entries (400 pages of 100). The chart is regenerated on
a schedule; if the repo grows past that cap the script stops at 40,000 and
logs a warning rather than under-reporting.
"""
import json
import os
import sys
import urllib.request
from datetime import datetime
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt # noqa: E402
from matplotlib.dates import AutoDateLocator, DateFormatter # noqa: E402
REPO = "seaweedfs/seaweedfs"
TOKEN = os.environ["GITHUB_TOKEN"]
OUT = os.environ.get("OUT", "note/star_history.svg")
PAGE_CAP = 400 # GitHub's hard limit on stargazer pagination
def fetch_stargazers():
stars = []
page = 1
while page <= PAGE_CAP:
url = f"https://api.github.com/repos/{REPO}/stargazers?per_page=100&page={page}"
req = urllib.request.Request(
url,
headers={
"Accept": "application/vnd.github.star+json",
"Authorization": f"Bearer {TOKEN}",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "seaweedfs-star-history",
},
)
with urllib.request.urlopen(req) as resp:
batch = json.load(resp)
if not batch:
break
for u in batch:
sa = u.get("starred_at")
if sa:
stars.append(datetime.fromisoformat(sa.replace("Z", "+00:00")))
if len(batch) < 100:
break
page += 1
if page > PAGE_CAP:
print(
f"::warning::Hit the {PAGE_CAP}-page stargazer pagination cap; "
"chart reflects the first 40,000 stars only."
)
return stars
def render(stars, out):
stars.sort()
counts = list(range(1, len(stars) + 1))
fig, ax = plt.subplots(figsize=(10, 4), dpi=130)
ax.plot(stars, counts, color="#0969da", linewidth=1.6)
ax.set_xlabel("Date")
ax.set_ylabel("Stars")
ax.set_title(f"{REPO} star history")
ax.grid(True, linestyle="--", alpha=0.3)
ax.xaxis.set_major_locator(AutoDateLocator())
ax.xaxis.set_major_formatter(DateFormatter("%Y-%m"))
fig.autofmt_xdate()
fig.tight_layout()
fig.savefig(out, format="svg", transparent=False)
plt.close(fig)
def main():
stars = fetch_stargazers()
if not stars:
print("::error::No stargazers fetched; not updating the chart.")
sys.exit(1)
render(stars, OUT)
print(f"Rendered {len(stars)} stars to {OUT}")
if __name__ == "__main__":
main()
+46
View File
@@ -0,0 +1,46 @@
---
name: Star History
on:
schedule:
- cron: "0 0 * * *" # daily, 00:00 UTC
workflow_dispatch:
permissions:
contents: write
jobs:
render:
name: Regenerate star history chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v8
with:
python-version: "3.x"
cache: pip
cache-dependency-path: .github/scripts/star_history.py
- name: Install matplotlib
run: pip install matplotlib
- name: Render chart
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: python .github/scripts/star_history.py
- name: Commit if changed
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git diff --quiet -- note/star_history.svg; then
echo "No changes to the chart."
exit 0
fi
git add note/star_history.svg
git commit -m "docs: regenerate star history chart"
git push