Skip to content

Commit c465c93

Browse files
authored
ci: cut a docs version when a release changes the docs (#821)
Docusaurus versioning here is a manual commit and it has drifted: 8.0.0, 8.2.0 and 8.3.0 were cut while 8.1.0 and every patch release were not, so anyone reading the docs for a released version can be reading a snapshot several releases stale. Cuts a version if, and only if, website/docs differs from the most recent snapshot. The version number decides nothing: a patch release that changed documentation gets a snapshot, and a major that changed none does not, because a snapshot identical to the one before it is noise in the version picker. The comparison is against the previous snapshot directory rather than the previous release's tag, and the distinction matters. The 8.3.0 snapshot was committed in 0335865, which also carried doc edits, after v8.3.0 was tagged; diffing against the tag reports three files as changed that the snapshot already contains. The snapshot is what readers see, so the snapshot is what to compare against. Opens a pull request rather than pushing to master. A cut is ~41 files and 6,000 lines, which deserves review, and a PR avoids both the protected-branch question and re-triggering the deploy workflow. The release tag and the dispatch input reach the shell through env rather than expression interpolation, so a tag carrying shell syntax is data rather than script.
1 parent 0b32f86 commit c465c93

1 file changed

Lines changed: 170 additions & 0 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Snapshots website/docs into a new versioned_docs entry when a release is published AND the docs
2+
# have actually changed since the last snapshot.
3+
#
4+
# Docusaurus versioning here is a manual `docusaurus docs:version <version>` commit, and it has
5+
# drifted: 8.0.0, 8.2.0 and 8.3.0 were cut while 8.1.0 and every patch release were not. So anyone
6+
# reading the docs for a released version can be reading a snapshot several releases stale.
7+
#
8+
# The rule is exactly one condition: cut a version if, and only if, website/docs differs from the
9+
# most recent snapshot. A release that changed no documentation gets no new version, whatever its
10+
# version number, because a snapshot identical to the one before it is noise in the version picker.
11+
#
12+
# The comparison is against the previous snapshot directory rather than against the previous
13+
# release's tag, and that distinction is load-bearing. The 8.3.0 snapshot was committed in
14+
# 0335865b56, which also carried doc edits, after v8.3.0 had been tagged. Diffing against the tag
15+
# therefore reports three files as changed that the snapshot already contains. The snapshot is what
16+
# readers actually see, so the snapshot is the thing to compare against.
17+
#
18+
# The result is opened as a pull request rather than pushed to master. A cut is roughly 41 files and
19+
# 6,000 lines, which deserves a human glance, and a PR avoids both the protected-branch question and
20+
# re-triggering the deploy workflow from a push this workflow made.
21+
22+
name: "Docs version cut"
23+
24+
on:
25+
release:
26+
types:
27+
- published
28+
workflow_dispatch:
29+
inputs:
30+
version:
31+
description: "Version to cut, without a leading v (e.g. 8.4.0). Defaults to the release tag."
32+
required: false
33+
type: string
34+
force:
35+
description: "Cut even when website/docs is unchanged since the last snapshot."
36+
required: false
37+
type: boolean
38+
default: false
39+
40+
permissions:
41+
contents: write
42+
pull-requests: write
43+
44+
jobs:
45+
46+
docs-version-cut:
47+
48+
name: "Docs version cut"
49+
runs-on: "ubuntu-latest"
50+
51+
steps:
52+
53+
- name: "Checkout"
54+
uses: "actions/checkout@v6"
55+
with:
56+
ref: "master"
57+
58+
- name: "Resolve the version to cut"
59+
id: "resolve"
60+
# The tag name and the dispatch input are both attacker-shaped strings: they reach the shell
61+
# through env rather than through ${{ }} interpolation, so a value carrying shell syntax is
62+
# data here instead of script. The regex below is what makes everything downstream safe.
63+
#
64+
# Requiring a plain X.Y.Z also keeps prereleases out. A v9.0.0-beta1 snapshot would sit in
65+
# the version picker forever, and the release it describes is meant to be temporary.
66+
env:
67+
INPUT_VERSION: "${{ inputs.version || '' }}"
68+
RELEASE_TAG: "${{ github.event.release.tag_name || '' }}"
69+
run: |
70+
set -euo pipefail
71+
72+
if [ -n "$INPUT_VERSION" ]; then
73+
version="$INPUT_VERSION"
74+
else
75+
version="$RELEASE_TAG"
76+
fi
77+
version="${version#v}"
78+
79+
if ! printf '%s' "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
80+
echo "::notice::'$version' is not a plain X.Y.Z version, skipping."
81+
echo "skip=true" >> "$GITHUB_OUTPUT"
82+
exit 0
83+
fi
84+
85+
echo "version=$version" >> "$GITHUB_OUTPUT"
86+
echo "previous=$(jq -r '.[0]' website/versions.json)" >> "$GITHUB_OUTPUT"
87+
88+
- name: "Decide whether the docs changed"
89+
id: "decide"
90+
if: "steps.resolve.outputs.skip != 'true'"
91+
# VERSION is already known to match X.Y.Z, but it and the rest come through env anyway: one
92+
# rule for reaching the shell is easier to keep than a per-value judgement about which
93+
# interpolation happens to be safe today.
94+
env:
95+
VERSION: "${{ steps.resolve.outputs.version }}"
96+
PREVIOUS: "${{ steps.resolve.outputs.previous }}"
97+
FORCE: "${{ inputs.force || 'false' }}"
98+
run: |
99+
set -euo pipefail
100+
101+
skip() { echo "::notice::$1"; echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
102+
103+
if jq -e --arg v "$VERSION" 'index($v)' website/versions.json > /dev/null; then
104+
skip "$VERSION is already in versions.json, nothing to cut."
105+
fi
106+
107+
if [ "$FORCE" != "true" ]; then
108+
snapshot="website/versioned_docs/version-${PREVIOUS}"
109+
110+
# No previous snapshot to compare against means there is nothing to be identical to.
111+
if [ -d "$snapshot" ]; then
112+
if diff -rq "website/docs" "$snapshot" > /dev/null 2>&1; then
113+
skip "website/docs is identical to the version-${PREVIOUS} snapshot, so $VERSION needs no cut."
114+
fi
115+
116+
echo "Changed since the version-${PREVIOUS} snapshot:"
117+
diff -rq "website/docs" "$snapshot" 2>&1 | sed 's/^/ /'
118+
fi
119+
fi
120+
121+
echo "skip=false" >> "$GITHUB_OUTPUT"
122+
123+
- name: "Setup NodeJS"
124+
if: "steps.decide.outputs.skip == 'false'"
125+
uses: "actions/setup-node@v7"
126+
with:
127+
node-version: "20.x"
128+
129+
- name: "Yarn install"
130+
if: "steps.decide.outputs.skip == 'false'"
131+
run: "yarn install"
132+
working-directory: "website"
133+
134+
- name: "Cut the version"
135+
if: "steps.decide.outputs.skip == 'false'"
136+
env:
137+
VERSION: "${{ steps.resolve.outputs.version }}"
138+
run: 'yarn docusaurus docs:version "$VERSION"'
139+
working-directory: "website"
140+
141+
- name: "Build the site to prove the snapshot is valid"
142+
if: "steps.decide.outputs.skip == 'false'"
143+
run: "yarn run build"
144+
working-directory: "website"
145+
146+
- name: "Open the pull request"
147+
if: "steps.decide.outputs.skip == 'false'"
148+
uses: "peter-evans/create-pull-request@v7"
149+
with:
150+
token: "${{ secrets.GITHUB_TOKEN }}"
151+
branch: "docs/version-${{ steps.resolve.outputs.version }}"
152+
base: "master"
153+
commit-message: "docs: cut the ${{ steps.resolve.outputs.version }} docs version"
154+
title: "docs: cut the ${{ steps.resolve.outputs.version }} docs version"
155+
add-paths: |
156+
website/versions.json
157+
website/versioned_docs/**
158+
website/versioned_sidebars/**
159+
body: |
160+
Snapshots `website/docs` into `versioned_docs/version-${{ steps.resolve.outputs.version }}`,
161+
opened automatically because ${{ steps.resolve.outputs.version }} was released and
162+
`website/docs` differs from the version-${{ steps.resolve.outputs.previous }} snapshot.
163+
The changed files are listed in the "Decide whether the docs changed" step of the run
164+
that opened this.
165+
166+
Generated by `docusaurus docs:version`; the site was built from the result before this was
167+
opened, so the snapshot is known to compile. Nothing outside `website/versions.json`,
168+
`versioned_docs/` and `versioned_sidebars/` is touched.
169+
170+
Close this if ${{ steps.resolve.outputs.version }} was not meant to carry a docs snapshot.

0 commit comments

Comments
 (0)