Repository navigation
137 lines (126 loc) · 6.51 KB
/
Copy pathupdate-badges.yml
File metadata and controls
137 lines (126 loc) · 6.51 KB
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
134
135
136
137
# SPDX-License-Identifier: MIT
# Refresh the shields endpoint payloads for the toolchain badges.
#
# `.shields/vale.json` and `.shields/sg.json` are the only badge values this
# repository computes for itself. Shields cannot answer "is there a newer
# upstream we have not pinned", so the comparison runs here and the color rule
# lives in `.github/scripts/update-badges.cjs`, under code review like anything
# else. Everything else in the README's badge row is a shields built-in or a
# static badge and needs nothing from this file.
#
# WHY THIS IS ITS OWN WORKFLOW AND NOT A JOB IN A PUBLISHING ONE.
#
# It needs `contents: write`, because writing a badge means committing to
# `main`. `release-cli.yml`, `release-cli-nightly.yml`, and `release-vale.yml`
# each hold `id-token: write` and an npm OIDC publishing identity. Keeping the
# two apart in SEPARATE FILES makes the separation structural: there is no
# arrangement of jobs here that a later edit could get wrong, because the
# credentialed workflows are not in this file to be edited. A job added to a
# release workflow would put a write token and a publishing identity one
# `permissions:` block away from each other, and the only thing keeping them
# apart would be whoever reviews the next change to that file. Do not merge
# this into a release workflow to save a file.
#
# WHY IT IS NOT FOLDED INTO release-vale.yml SPECIFICALLY. That workflow's
# detect phase opens a pull request when upstream Vale is ahead. The badge has
# to be YELLOW while that pull request is open and green once it merges, so the
# payload cannot live in that pull request's own diff — it would go yellow at
# exactly the moment it should go green. It has to reach `main` independently
# of the change it is warning about.
#
# WHY THE SCHEDULE, AND WHY A BADGE COMMIT DOES NOT LOOP.
#
# `validate.yml` triggers on every push to `main` with NO `paths` filter, and
# since #132 the nightly publishes on `Validate` completing. So a commit here
# costs one Validate run and, if changesets are pending, one nightly publish.
# That chain terminates only because what is committed depends on UPSTREAM
# moving, not on us pushing:
#
# badge commit → Validate → nightly workflow_run → nightly publishes
# → next scheduled badge run finds the same upstream versions
# → writes nothing → no commit → chain over.
#
# A badge derived from each nightly would NOT terminate: publish → commit →
# Validate → publish, forever. The nightly gate cannot stop it, because it
# dedupes per-sha and every badge commit is a new sha. That is why the nightly
# badge in the README is a plain static one with no version in it, and why
# nothing in this workflow ever reads a nightly version.
#
# The same reasoning bounds the cost of this workflow. The schedule is daily so
# an upstream release is noticed within a day, but `update-badges.cjs` rewrites
# a payload only when the rendered badge actually changes — and the date it
# renders advances only once a week — so a quiet week produces at most one
# commit per badge, not seven.
#
# THE DATE IN THE MESSAGE IS THE POINT, NOT DECORATION. A badge that stopped
# being updated looks identical to one whose value has not changed. Rendering
# `vale | 3.17.1 · 2026-08-21` means a reader can tell "upstream has been
# quiet" from "this job died in April" without opening anything.
#
# Action refs are pinned to commit SHAs; the trailing comment records the tag.
name: Update badges
on:
# Upstream's release cadence, not ours. This answers "what is upstream
# doing", which has nothing to do with our own pushes or releases — hence a
# schedule rather than a trigger on any repository event.
schedule:
- cron: "17 6 * * *"
# For "upstream just released, do not wait until tomorrow".
workflow_dispatch:
# No workflow-wide grants; the one job asks for exactly what it needs.
permissions: {}
# One at a time, so two runs cannot both decide to commit the same payload.
concurrency: update-badges
jobs:
update:
name: Refresh toolchain badges
runs-on: ubuntu-latest
permissions:
contents: write # commit the refreshed payloads to main
steps:
# Credentials persist because this job pushes. It holds no npm identity,
# no id-token, and runs nothing it downloaded.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
# No install step: the scripts are zero-dependency CommonJS. GITHUB_TOKEN
# is passed only to raise the GitHub API rate limit on the Vale release
# lookup; both endpoints (GitHub releases, the npm registry) are public.
- id: badges
run: node .github/scripts/update-badges.cjs --write
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Guarded on the script's own answer rather than on `git diff`, so the
# decision is made in the tested code path rather than restated in shell.
# Nothing from a workflow expression is interpolated into this body.
#
# RETRIED, because losing a race here is the ordinary case rather than an
# exceptional one: any pull request merging between the checkout and this
# push makes `main` a non-fast-forward, and a bare push would fail the job
# and leave the badge stale until tomorrow's schedule. Rebasing our single
# commit onto the new tip and pushing again is exactly what a human would
# do, and `.shields/` is touched by nothing else, so the rebase cannot
# meet a conflict from an ordinary merge.
#
# Bounded at three attempts. A push that keeps losing is not a race any
# more, and a job that retries forever hides whatever is actually wrong.
- name: Commit the refreshed payloads
if: steps.badges.outputs.changed == 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add .shields
git commit -m "chore(badges): refresh upstream toolchain badges"
for attempt in 1 2 3; do
if git push origin HEAD:main; then
echo "Pushed on attempt ${attempt}."
exit 0
fi
echo "Push rejected on attempt ${attempt}; rebasing onto the new main."
git fetch origin main
git rebase origin/main
done
echo "::error::could not push the badge commit after 3 attempts"
exit 1