Skip to content

Commit bc050fb

Browse files
committed
Add GitHub Actions workflow to automate website version updates on Lab releases
1 parent 92fc20b commit bc050fb

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
name: Update website Lab version
2+
3+
on:
4+
release:
5+
types: [published]
6+
7+
permissions:
8+
contents: read
9+
10+
concurrency:
11+
group: update-website-lab-version-${{ github.event.release.tag_name }}
12+
cancel-in-progress: false
13+
14+
jobs:
15+
update:
16+
if: startsWith(github.event.release.tag_name, 'v2.')
17+
runs-on: ubuntu-latest
18+
env:
19+
LAB_RELEASE_TAG: ${{ github.event.release.tag_name }}
20+
TARGET_BRANCH: dev
21+
TARGET_REPOSITORY: MontFerret/montferret.github.io
22+
steps:
23+
- name: Validate release metadata
24+
id: metadata
25+
run: |
26+
set -euo pipefail
27+
28+
prerelease_identifier='(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)'
29+
semver_pattern="^v2\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-${prerelease_identifier}(\\.${prerelease_identifier})*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$"
30+
if [[ ! "$LAB_RELEASE_TAG" =~ $semver_pattern ]]; then
31+
echo "::error title=Invalid Lab release version::Expected a v2 SemVer tag, got '$LAB_RELEASE_TAG'."
32+
exit 1
33+
fi
34+
35+
lab_version="${LAB_RELEASE_TAG#v}"
36+
update_branch="chore/lab-version-${lab_version}"
37+
git check-ref-format --branch "$update_branch"
38+
39+
echo "branch=$update_branch" >> "$GITHUB_OUTPUT"
40+
echo "version=$lab_version" >> "$GITHUB_OUTPUT"
41+
42+
- name: Create repository-scoped GitHub App token
43+
id: app-token
44+
uses: actions/create-github-app-token@v3
45+
with:
46+
client-id: ${{ secrets.FERRET_RELEASE_APP_CLIENT_ID }}
47+
private-key: ${{ secrets.FERRET_RELEASE_APP_PRIVATE_KEY }}
48+
owner: MontFerret
49+
repositories: montferret.github.io
50+
permission-contents: write
51+
permission-pull-requests: write
52+
53+
- name: Checkout website development branch
54+
uses: actions/checkout@v6
55+
with:
56+
repository: ${{ env.TARGET_REPOSITORY }}
57+
ref: ${{ env.TARGET_BRANCH }}
58+
token: ${{ steps.app-token.outputs.token }}
59+
fetch-depth: 0
60+
persist-credentials: true
61+
62+
- name: Update Lab version
63+
id: version-file
64+
env:
65+
LAB_VERSION: ${{ steps.metadata.outputs.version }}
66+
run: |
67+
set -euo pipefail
68+
69+
version_file='data/versions.yaml'
70+
temporary_file="$(mktemp)"
71+
trap 'rm -f "$temporary_file"' EXIT
72+
73+
awk -v version="$LAB_VERSION" '
74+
/^lab:[[:space:]]*$/ {
75+
lab_blocks++
76+
in_lab = 1
77+
print
78+
next
79+
}
80+
81+
in_lab && /^[^[:space:]]/ {
82+
in_lab = 0
83+
}
84+
85+
in_lab && /^ v2:[[:space:]]*/ {
86+
lab_v2_keys++
87+
if ($0 !~ /^ v2:[[:space:]]*"[^"]*"[[:space:]]*(#.*)?$/) {
88+
invalid_lab_v2 = 1
89+
} else {
90+
sub(/"[^"]*"/, "\"" version "\"")
91+
}
92+
}
93+
94+
{ print }
95+
96+
END {
97+
failed = 0
98+
if (lab_blocks != 1) {
99+
printf "::error title=Invalid website version file::Expected exactly one top-level lab mapping, found %d.\n", lab_blocks > "/dev/stderr"
100+
failed = 1
101+
}
102+
if (lab_v2_keys != 1) {
103+
printf "::error title=Invalid website version file::Expected exactly one lab.v2 key, found %d.\n", lab_v2_keys > "/dev/stderr"
104+
failed = 1
105+
}
106+
if (invalid_lab_v2) {
107+
print "::error title=Invalid website version file::Expected lab.v2 to be a double-quoted scalar." > "/dev/stderr"
108+
failed = 1
109+
}
110+
exit failed
111+
}
112+
' "$version_file" > "$temporary_file"
113+
114+
mv "$temporary_file" "$version_file"
115+
trap - EXIT
116+
117+
git add -- "$version_file"
118+
staged_files="$(git diff --cached --name-only)"
119+
if [[ -z "$staged_files" ]]; then
120+
echo "Website already references Lab ${LAB_RELEASE_TAG}."
121+
echo "changed=false" >> "$GITHUB_OUTPUT"
122+
exit 0
123+
fi
124+
125+
if [[ "$staged_files" != "$version_file" ]]; then
126+
echo "::error title=Unexpected website changes::Expected only $version_file to be staged."
127+
git status --short
128+
exit 1
129+
fi
130+
131+
git diff --cached --check
132+
echo "changed=true" >> "$GITHUB_OUTPUT"
133+
134+
- name: Commit and push update branch
135+
if: steps.version-file.outputs.changed == 'true'
136+
env:
137+
UPDATE_BRANCH: ${{ steps.metadata.outputs.branch }}
138+
run: |
139+
set -euo pipefail
140+
141+
if ! git diff --quiet; then
142+
echo "::error title=Unexpected website changes::Found unstaged changes outside the Lab version update."
143+
git status --short
144+
exit 1
145+
fi
146+
147+
git config user.name "ferret-release[bot]"
148+
git config user.email "ferret-release[bot]@users.noreply.github.com"
149+
git switch -c "$UPDATE_BRANCH"
150+
git commit -m "chore: update Lab website version to ${LAB_RELEASE_TAG}"
151+
152+
remote_ref="refs/heads/${UPDATE_BRANCH}"
153+
remote_sha="$(git ls-remote --heads origin "$remote_ref" | awk '{ print $1 }')"
154+
if [[ -n "$remote_sha" ]]; then
155+
git push --force-with-lease="${remote_ref}:${remote_sha}" --set-upstream origin "HEAD:${remote_ref}"
156+
else
157+
git push --set-upstream origin "HEAD:${remote_ref}"
158+
fi
159+
160+
- name: Create or reuse pull request
161+
if: steps.version-file.outputs.changed == 'true'
162+
env:
163+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
164+
RELEASE_URL: ${{ github.event.release.html_url }}
165+
UPDATE_BRANCH: ${{ steps.metadata.outputs.branch }}
166+
run: |
167+
set -euo pipefail
168+
169+
existing_pr="$(gh pr list --repo "$TARGET_REPOSITORY" --state open --head "$UPDATE_BRANCH" --json url --jq '.[0].url // empty')"
170+
if [[ -n "$existing_pr" ]]; then
171+
echo "Updated existing pull request: $existing_pr"
172+
exit 0
173+
fi
174+
175+
gh pr create \
176+
--repo "$TARGET_REPOSITORY" \
177+
--base "$TARGET_BRANCH" \
178+
--head "$UPDATE_BRANCH" \
179+
--title "chore: update Lab website version to ${LAB_RELEASE_TAG}" \
180+
--body "Automated website version update triggered by Lab release [${LAB_RELEASE_TAG}](${RELEASE_URL})."

0 commit comments

Comments
 (0)