Files

114 lines
4.7 KiB
YAML

name: Refresh GitHub data
# Keep the deployed GitHub snapshot (stats / releases / contributors / commits)
# fresh without pushing code. Every 30 minutes the snapshot is regenerated from
# the GitHub API and compared against what is currently deployed; only when the
# numbers actually moved (stars, release, commits) is the Pages deploy
# dispatched, which rebuilds the site and ships the fresh data. No change means
# no rebuild — the site isn't rebuilt 48 times a day for nothing.
#
# Notes:
# - The snapshot file (public/data/github.json) is gitignored and regenerated
# on every build by the `prebuild` script, so nothing needs committing — the
# deploy build produces the fresh data by itself.
# - The site also live-fetches GitHub data in the browser, so this workflow
# mostly improves the first paint and the no-JS / rate-limited fallback.
# - `gh workflow run deploy.yml` is used because pushes made with the default
# GITHUB_TOKEN do not trigger other workflows.
on:
schedule:
- cron: "*/30 * * * *" # every 30 minutes (may be delayed under load)
workflow_dispatch:
inputs:
force:
description: "Rebuild even if the data did not change"
type: boolean
default: false
permissions:
contents: read
actions: write # needed for `gh workflow run deploy.yml`
jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Regenerate the GitHub snapshot
run: npm run fetch:data
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy only when the deployed data is stale
id: changed
# GITHUB_REPOSITORY (always "owner/name") is used to build the Pages
# URL — github.event.repository can be unavailable on schedule events.
env:
FORCE: ${{ inputs.force }}
run: |
node - <<'NODE'
(async () => {
const fs = require("fs");
const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/");
const rootRepo = `${owner}.github.io`.toLowerCase();
const site = repo.toLowerCase() === rootRepo
? `https://${owner}.github.io`
: `https://${owner}.github.io/${repo}`;
const base = `${site}/data/github.json`;
const cur = JSON.parse(fs.readFileSync("public/data/github.json", "utf8"));
const live = await (async () => {
if (process.env.FORCE === "true") return null;
try {
const res = await fetch(base, { cache: "no-store" });
if (!res.ok) return null; // not deployed yet → establish the baseline
return await res.json();
} catch {
return null; // unreachable / first deploy → rebuild
}
})();
if (live === null) {
fs.appendFileSync(process.env.GITHUB_OUTPUT, "changed=true\n");
process.exit(0);
}
// Compare real data only. generatedAt always differs; and the repo
// object's temp_clone_token rotates on every authenticated call
// (permissions is token-shaped too) — without stripping those the
// JSON never matches and the site rebuilds every 30 minutes. Strip
// defensively: the deployed file may predate the fetch script's own
// sanitization.
for (const obj of [cur, live]) {
delete obj.generatedAt;
if (obj.repo) {
delete obj.repo.temp_clone_token;
delete obj.repo.permissions;
}
if (obj.latestRelease && Array.isArray(obj.latestRelease.assets)) {
// Download counts move on every download — compare releases by
// their identity (tag, date, notes), not by popularity noise.
for (const asset of obj.latestRelease.assets) delete asset.download_count;
}
}
const didChange = JSON.stringify(cur) !== JSON.stringify(live);
fs.appendFileSync(process.env.GITHUB_OUTPUT, `changed=${didChange}\n`);
})();
NODE
- name: Dispatch the deploy
if: steps.changed.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh workflow run deploy.yml --ref main -f force_full=true
echo "Deployed data was stale — dispatched a rebuild."
- name: Nothing to do
if: steps.changed.outputs.changed != 'true'
run: echo "Deployed data is current — no rebuild needed."