1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
name: Refresh site stats
# site/src/data/stats.json holds the numbers the homepage and the install page
# render: crates.io download counts, GitHub release asset downloads, the latest
# release tag, and the per-platform archive sizes. It is a generated file — the
# site deliberately renders committed JSON rather than live API responses, so
# builds stay hermetic and reproducible.
#
# Before this workflow it had no automation at all: the only way to refresh it
# was to run `npm run update-stats` in site/ by hand, which is how the download
# counts drifted away from reality.
#
# Why a pull request and not a direct commit, matching changelog.yml /
# discovery.yml / benchmark.yml: generated content still deserves a reviewable
# diff, and the GITHUB_TOKEN cannot push to a protected main. The script is
# idempotent (a run where only `updatedAt` would move writes nothing), so a
# quiet day opens no PR.
on:
push:
branches:
paths:
# The version tile and the install page read `version` from this file,
# which the script reads out of Cargo.toml. Refresh on the bump itself,
# not only once the release is published.
- Cargo.toml
schedule:
# 05:07 UTC daily. Off the hour, and clear of every other scheduled
# workflow here (03:23 benchmark, 03:41 demo, 04:00 CI, 04:17 discovery,
# 04:31 auto-release) so they do not contend for the runner queue.
- cron: "7 5 * * *"
workflow_dispatch:
release:
# A new release changes the tag, the date, and the archive sizes at once.
types:
permissions:
contents: read
concurrency:
group: stats-refresh
cancel-in-progress: true
jobs:
refresh:
name: refresh
runs-on: ubuntu-24.04-arm
timeout-minutes: 10
permissions:
contents: write
pull-requests: write
# gh workflow run below — waking the merge poller.
actions: write
steps:
- uses: actions/checkout@v7
timeout-minutes: 2
- uses: actions/setup-node@v7
timeout-minutes: 5
with:
node-version: "26"
- name: Refresh stats.json
id: gen
timeout-minutes: 5
run: |
set -euo pipefail
# The script needs no packages — it is a bare Node script using fetch.
node site/scripts/update-stats.ts | tee stats-gen.log
# `git status --porcelain` rather than `git diff --quiet`, so a
# first-time generated file (untracked) counts as a change too.
if [ -z "$(git status --porcelain -- site/src/data/stats.json)" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "stats.json is already current."
else
echo "changed=true" >> "$GITHUB_OUTPUT"
git --no-pager diff --stat -- site/src/data/stats.json
fi
- name: Summarise
if: always()
run: |
{
echo "### Site stats refresh"
echo
echo '```'
cat stats-gen.log 2>/dev/null || echo "(generator produced no log)"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Open a pull request
id: pr
if: steps.gen.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v8
timeout-minutes: 5
with:
author: "debanjanbasu <10209115+debanjanbasu@users.noreply.github.com>"
committer: "debanjanbasu <10209115+debanjanbasu@users.noreply.github.com>"
branch: chore/stats-refresh
title: "chore(site): refresh download counts and archive sizes"
commit-message: "chore(site): refresh download counts and archive sizes"
labels: dependencies
add-paths: site/src/data/stats.json
body: |
Automated refresh of `site/src/data/stats.json` by
`.github/workflows/stats.yml`, run after `${{ github.event_name }}`.
The file is read by the homepage stat tiles and the install page's
download table, so it is committed rather than fetched at build
time — the site stays reproducible and builds offline.
Only the numbers change here: crates.io downloads, GitHub release
asset downloads, the latest release tag and date, and the measured
archive size per platform. Nothing in the copy depends on these
values, so review is a sanity check that the counters are plausible
and no size has collapsed.
The script skips the write when nothing but `updatedAt` would
move, so a quiet day produces no diff and no PR.
- name: Wake the merge poller
# CI on a token-authored PR waits in action_required until the poller
# approves it (GitHub's 2026-06 bot-event gate). Waking the poller now
# instead of waiting for the next cron tick removes the measured 5-7
# minutes of approval latency before any CI compute starts. The poller
# is idempotent, and the cron remains the safety net.
if: steps.pr.outputs.pull-request-number != ''
timeout-minutes: 2
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: gh workflow run automation-merge.yml --ref main || echo "::warning::poller dispatch failed"