-
Notifications
You must be signed in to change notification settings - Fork 13
601 lines (557 loc) · 26.8 KB
/
Copy pathrelease.yml
File metadata and controls
601 lines (557 loc) · 26.8 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
name: Release
on:
push:
tags:
- 'v*'
# Manual fallback — added after v0.8.4's tag push (via auto-tag.yml, using
# RELEASE_TAG_PAT) never triggered this workflow at all, confirmed by
# polling the Actions API directly for several minutes (not just a webhook
# delay), with no clear root cause found from the outside. A `tag` input
# rather than relying on `--ref vX.Y.Z` alone: `workflow_dispatch` can only
# be dispatched against a ref whose *own* copy of this file already has the
# trigger, which an already-pushed tag's copy never will the first time
# this is needed — the whole reason a manual fallback exists at all. The
# `resolve-tag` job below is the one place `github.event.inputs.tag` vs.
# `github.ref_name` gets decided; every other job reads its output instead
# of branching on that itself.
workflow_dispatch:
inputs:
tag:
description: "Tag to release, e.g. v0.8.4 (only needed when dispatching manually instead of via an actual tag push)"
required: true
env:
CARGO_TERM_COLOR: always
BIN_NAME: shiki
permissions:
contents: read
jobs:
resolve-tag:
name: Resolve release tag
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
tag: ${{ steps.resolve.outputs.tag }}
steps:
- id: resolve
run: echo "tag=${{ github.event.inputs.tag || github.ref_name }}" >> "$GITHUB_OUTPUT"
build:
name: build (${{ matrix.target }})
needs: resolve-tag
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
ext: tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
ext: zip
- os: macos-latest
target: x86_64-apple-darwin
ext: tar.gz
- os: macos-latest
target: aarch64-apple-darwin
ext: tar.gz
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Build
run: cargo build --release -p shiki-cli --target ${{ matrix.target }}
- name: Package (tar.gz)
if: matrix.ext == 'tar.gz'
shell: bash
run: |
set -euo pipefail
stage=dist/shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}
mkdir -p "$stage"
cp "target/${{ matrix.target }}/release/${{ env.BIN_NAME }}" "$stage/"
cp README.md LICENSE CHANGELOG.md "$stage/"
tar -C dist -czf "dist/shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}.tar.gz" \
"shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}"
- name: Package (zip)
if: matrix.ext == 'zip'
shell: bash
run: |
set -euo pipefail
stage=dist/shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}
mkdir -p "$stage"
cp "target/${{ matrix.target }}/release/${{ env.BIN_NAME }}.exe" "$stage/"
cp README.md LICENSE CHANGELOG.md "$stage/"
cd dist
7z a "shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}.zip" \
"shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}"
- uses: actions/upload-artifact@v7
with:
name: shiki-${{ matrix.target }}
path: dist/shiki-${{ needs.resolve-tag.outputs.tag }}-${{ matrix.target }}.${{ matrix.ext }}
if-no-files-found: error
release:
name: Create GitHub Release
needs: [build, resolve-tag]
runs-on: ubuntu-latest
timeout-minutes: 10
# The only job that needs write access to repo contents — creating a
# GitHub Release (`softprops/action-gh-release`) via the default
# `GITHUB_TOKEN`. Every other job either only reads (`build`,
# `resolve-tag`, `publish-crates`) or pushes with `RELEASE_TAG_PAT`
# instead of `GITHUB_TOKEN` (`update-packaging-manifests`,
# `update-screenshots` — see their own steps for why), so they don't
# need this permission at all.
permissions:
contents: write
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/download-artifact@v8
with:
path: artifacts
- name: Flatten artifacts and generate checksums
id: version
env:
TAG: ${{ needs.resolve-tag.outputs.tag }}
run: |
set -euo pipefail
mkdir -p dist
find artifacts -type f -exec cp {} dist/ \;
cd dist
sha256sum * > SHA256SUMS.txt
echo "version=${TAG#v}" >> "$GITHUB_OUTPUT"
- uses: softprops/action-gh-release@v3
with:
# Explicit, not left to default to `github.ref` — that's not a
# tag ref at all when this runs via `workflow_dispatch` (it's
# whichever branch/tag `--ref` checked out), which would either
# create a release against the wrong tag or fail outright.
tag_name: ${{ needs.resolve-tag.outputs.tag }}
files: dist/*
# GitHub's release API prepends `body` before the auto-generated
# notes when `generate_release_notes: true` is also set (not
# replaced by it) — this is what actually gets pasted around when
# someone shares a GitHub Release link, so it's worth pointing at
# the nicer, OG-carded page instead of leaving that link only on
# the site's own changelog. Both URLs are 404 for a few minutes
# after this step runs — the actual page/image are generated by
# update-release-pages further down this same workflow, and then
# need pages.yml's own deploy on top of that — but they're
# predictable from the version alone, and self-heal with no edit
# needed once those catch up.
body: |
📝 **[Full release notes with screenshot](https://sazardev.github.io/shiki/changelog/${{ steps.version.outputs.version }}.html)**

generate_release_notes: true
update-packaging-manifests:
name: Update AUR/Scoop/Homebrew manifests from release checksums
needs: [release, resolve-tag]
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v7
with:
ref: main
# main requires a pull request for everything pushed with the
# default GITHUB_TOKEN (branch protection added after this job
# already existed) — RELEASE_TAG_PAT belongs to a real repo admin,
# so pushing with it bypasses that requirement the same legitimate
# way a human admin's own push would. Falls back to the default
# token when the secret isn't set yet, so checkout itself never
# fails; the "Commit updated manifests" step below fails loudly
# instead, right when it actually needs to push.
token: ${{ secrets.RELEASE_TAG_PAT || github.token }}
- name: Download release checksums
run: |
set -euo pipefail
curl -sSfL -o SHA256SUMS.txt \
"https://github.com/${{ github.repository }}/releases/download/${{ needs.resolve-tag.outputs.tag }}/SHA256SUMS.txt"
- name: Update Scoop manifest
run: |
set -euo pipefail
version="${{ needs.release.outputs.version }}"
hash=$(grep 'x86_64-pc-windows-msvc.zip' SHA256SUMS.txt | awk '{print $1}')
# Written to two places, kept byte-identical on purpose:
# `packaging/scoop/shiki.json` is what README's direct-manifest-URL
# install method (`scoop install https://raw.githubusercontent...`)
# points at — that works with zero bucket setup. `bucket/shiki.json`
# (repo root) is what makes `scoop bucket add sazardev
# https://github.com/sazardev/shiki` + `scoop install shiki` (the
# method already documented on the marketing site) actually work —
# Scoop only resolves manifests from a bucket's `bucket/` folder (or
# its root, but not an arbitrary nested path), so leaving it only
# under `packaging/scoop/` meant that second, already-documented
# install method could never have found it.
python3 - "$version" "$hash" <<'EOF'
import json, sys
version, sha256 = sys.argv[1], sys.argv[2]
for path in ("packaging/scoop/shiki.json", "bucket/shiki.json"):
with open(path) as f:
data = json.load(f)
data["version"] = version
data["architecture"]["64bit"]["url"] = (
f"https://github.com/sazardev/shiki/releases/download/v{version}/"
f"shiki-v{version}-x86_64-pc-windows-msvc.zip"
)
data["architecture"]["64bit"]["hash"] = f"sha256:{sha256}"
# The zip's own top-level entry is this same
# shiki-v{version}-x86_64-pc-windows-msvc/ folder (see the
# release job's "Package (zip)" step) — Scoop does not
# auto-strip a single nested top-level folder on extract, so
# without extract_dir it looks for shiki.exe directly under
# the app's version dir and fails to shim it (issue #8).
data["architecture"]["64bit"]["extract_dir"] = (
f"shiki-v{version}-x86_64-pc-windows-msvc"
)
with open(path, "w") as f:
json.dump(data, f, indent=4)
f.write("\n")
EOF
- name: Update Homebrew formula
run: |
set -euo pipefail
version="${{ needs.release.outputs.version }}"
arm_hash=$(grep 'aarch64-apple-darwin.tar.gz' SHA256SUMS.txt | awk '{print $1}')
intel_hash=$(grep 'x86_64-apple-darwin.tar.gz' SHA256SUMS.txt | awk '{print $1}')
# Rewritten line-by-line rather than JSON-style key edits (unlike
# the Scoop manifest above) since a Ruby formula has no structured
# format to parse — tracking which arch block we're in as we walk
# the file is simpler than a multiline regex, and doesn't care
# about incidental formatting changes to the file around it.
python3 - "$version" "$arm_hash" "$intel_hash" <<'EOF'
import sys
version, arm_hash, intel_hash = sys.argv[1], sys.argv[2], sys.argv[3]
path = "packaging/homebrew/shiki.rb"
with open(path) as f:
lines = f.readlines()
out = []
current_arch = None
for line in lines:
stripped = line.strip()
if stripped.startswith("version "):
line = f' version "{version}"\n'
elif "aarch64-apple-darwin.tar.gz" in line:
current_arch = "arm"
line = (
f' url "https://github.com/sazardev/shiki/releases/download/'
f'v{version}/shiki-v{version}-aarch64-apple-darwin.tar.gz"\n'
)
elif "x86_64-apple-darwin.tar.gz" in line:
current_arch = "intel"
line = (
f' url "https://github.com/sazardev/shiki/releases/download/'
f'v{version}/shiki-v{version}-x86_64-apple-darwin.tar.gz"\n'
)
elif stripped.startswith("sha256 "):
hash_value = arm_hash if current_arch == "arm" else intel_hash
line = f' sha256 "{hash_value}"\n'
out.append(line)
with open(path, "w") as f:
f.writelines(out)
EOF
- name: Update PKGBUILD (shiki-bin)
run: |
set -euo pipefail
version="${{ needs.release.outputs.version }}"
hash=$(grep 'x86_64-unknown-linux-gnu.tar.gz' SHA256SUMS.txt | awk '{print $1}')
sed -i "s/^pkgver=.*/pkgver=${version}/" packaging/aur/PKGBUILD
sed -i "s/^pkgrel=.*/pkgrel=1/" packaging/aur/PKGBUILD
sed -i "s/^sha256sums=.*/sha256sums=('${hash}')/" packaging/aur/PKGBUILD
- name: Regenerate .SRCINFO (shiki-bin)
run: |
set -euo pipefail
docker run --rm -v "$PWD/packaging/aur:/pkg" -w /pkg archlinux:base-devel \
bash -c "useradd -m builder && chown -R builder /pkg && su builder -c 'makepkg --printsrcinfo' > .SRCINFO"
- name: Update PKGBUILD (shiki, builds from source)
run: |
set -euo pipefail
version="${{ needs.release.outputs.version }}"
# No prebuilt asset to hash here — this package builds from the
# plain GitHub source tarball for the tag, which isn't one of the
# release's own uploaded assets (it's generated on the fly by
# GitHub's codeload), so it has to be downloaded and hashed here.
curl -sSfL -o source.tar.gz \
"https://github.com/${{ github.repository }}/archive/refs/tags/${{ needs.resolve-tag.outputs.tag }}.tar.gz"
hash=$(sha256sum source.tar.gz | awk '{print $1}')
sed -i "s/^pkgver=.*/pkgver=${version}/" packaging/aur-src/PKGBUILD
sed -i "s/^pkgrel=.*/pkgrel=1/" packaging/aur-src/PKGBUILD
sed -i "s/^sha256sums=.*/sha256sums=('${hash}')/" packaging/aur-src/PKGBUILD
rm source.tar.gz
- name: Regenerate .SRCINFO (shiki)
run: |
set -euo pipefail
docker run --rm -v "$PWD/packaging/aur-src:/pkg" -w /pkg archlinux:base-devel \
bash -c "useradd -m builder && chown -R builder /pkg && su builder -c 'makepkg --printsrcinfo' > .SRCINFO"
- name: Commit updated manifests
env:
HAS_PAT: ${{ secrets.RELEASE_TAG_PAT }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add packaging/scoop/shiki.json bucket/shiki.json packaging/aur/PKGBUILD packaging/aur/.SRCINFO \
packaging/aur-src/PKGBUILD packaging/aur-src/.SRCINFO packaging/homebrew/shiki.rb
if git diff --cached --quiet; then
echo "No packaging manifest changes to commit"
elif [ -z "$HAS_PAT" ]; then
echo "::error::RELEASE_TAG_PAT secret is not set — main requires a pull" \
"request, so pushing with the default GITHUB_TOKEN would be rejected." \
"Add a repo-scoped personal access token as RELEASE_TAG_PAT."
exit 1
else
git commit -m "packaging: update manifests for ${{ needs.resolve-tag.outputs.tag }}"
git push origin HEAD:main
fi
- name: Push to AUR
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
if: ${{ env.AUR_SSH_PRIVATE_KEY != '' }}
run: |
set -euo pipefail
mkdir -p ~/.ssh
echo "$AUR_SSH_PRIVATE_KEY" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts
export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o IdentitiesOnly=yes"
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Two AUR packages, same maintenance shape: shiki-bin (this repo's
# own prebuilt release asset) and shiki (builds from source) — see
# packaging/aur/ and packaging/aur-src/ respectively.
for pair in "shiki-bin:packaging/aur" "shiki:packaging/aur-src"; do
aur_repo="${pair%%:*}"
local_dir="${pair#*:}"
clone_dir="/tmp/aur-${aur_repo}"
rm -rf "$clone_dir"
git clone "ssh://aur@aur.archlinux.org/${aur_repo}.git" "$clone_dir"
cp "$local_dir/PKGBUILD" "$local_dir/.SRCINFO" "$clone_dir/"
(
cd "$clone_dir"
git add PKGBUILD .SRCINFO
if git diff --cached --quiet; then
echo "No AUR changes to push for ${aur_repo}"
else
git commit -m "Update to ${{ needs.resolve-tag.outputs.tag }}"
git push origin master
fi
)
done
- name: Push Homebrew tap
# No separate secret/guard needed here — the "Commit updated
# manifests" step above already fails the job (exit 1) if
# RELEASE_TAG_PAT isn't set, so reaching this step means it's
# already present. Uses the same PAT over HTTPS rather than a
# dedicated SSH deploy key, unlike the AUR push above, since
# sazardev/homebrew-shiki is a normal GitHub repo the PAT already
# has push access to — no separate credential to provision.
env:
RELEASE_TAG_PAT: ${{ secrets.RELEASE_TAG_PAT }}
run: |
set -euo pipefail
clone_dir="/tmp/homebrew-shiki"
rm -rf "$clone_dir"
git clone "https://x-access-token:${RELEASE_TAG_PAT}@github.com/sazardev/homebrew-shiki.git" "$clone_dir"
mkdir -p "$clone_dir/Formula"
cp packaging/homebrew/shiki.rb "$clone_dir/Formula/shiki.rb"
(
cd "$clone_dir"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/shiki.rb
if git diff --cached --quiet; then
echo "No Homebrew tap changes to push"
else
git commit -m "Update to ${{ needs.resolve-tag.outputs.tag }}"
git push origin HEAD:main
fi
)
update-screenshots:
name: Regenerate marketing screenshots and demo GIF for the docs site
# Also needs update-packaging-manifests (not just release/resolve-tag)
# so it never checks out `main` and pushes in parallel with that job's
# own push to the same branch — hit for real on v0.8.4's release:
# update-packaging-manifests pushed its manifest-update commit first,
# and this job's checkout (taken before that push landed) was then
# rejected as a non-fast-forward when it tried to push its own
# screenshot-update commit. Running after it entirely removes the race
# instead of adding retry/rebase logic to paper over it.
needs: [release, resolve-tag, update-packaging-manifests]
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
with:
ref: main
token: ${{ secrets.RELEASE_TAG_PAT || github.token }}
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
key: screenshots
- name: Install screenshot tooling (xterm, ImageMagick, xdotool, Xvfb, pngquant)
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install -y xterm imagemagick xdotool xvfb pngquant
- name: Install demo-gif tooling (VHS, ttyd, ffmpeg)
run: |
set -euo pipefail
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" | sudo tee /etc/apt/sources.list.d/charm.list
sudo apt-get update
sudo apt-get install -y vhs ffmpeg
# ttyd has no apt package — vhs's own README points at its GitHub
# release binaries instead.
curl -fsSL -o /tmp/ttyd https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.x86_64
chmod +x /tmp/ttyd
sudo mv /tmp/ttyd /usr/local/bin/ttyd
- name: Install a Nerd Font (for the TUI's icon glyphs)
run: |
set -euo pipefail
mkdir -p ~/.local/share/fonts
curl -sSfL -o /tmp/JetBrainsMono.zip \
https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip
unzip -q /tmp/JetBrainsMono.zip -d ~/.local/share/fonts
fc-cache -f
- name: Generate screenshots
run: bash scripts/screenshots.sh
- name: Generate demo GIF
run: bash scripts/demo-gif.sh
- name: Update docs site screenshots and demo GIF
id: update
env:
HAS_PAT: ${{ secrets.RELEASE_TAG_PAT }}
run: |
set -euo pipefail
for theme in \
catppuccin-mocha catppuccin-macchiato catppuccin-frappe catppuccin-latte \
tokyo-night-storm tokyo-night tokyo-night-moon \
gruvbox-dark gruvbox-light \
nord \
solarized-dark solarized-light; do
cp "screenshots/$theme/wide-01-notebooks.png" "docs/assets/screenshots/$theme.png"
done
# documentation.html's per-feature screenshot gallery — every
# capture, for every theme scripts/screenshots.sh generates
# (including Dracula/One Dark/Monokai/default, which the homepage
# hero loop above doesn't cover yet). A plain glob over whatever
# screenshots.sh actually produced, not a hardcoded file list, so a
# future new capture (or theme) shows up here automatically with no
# matching edit needed in this workflow.
for theme_dir in screenshots/*/; do
theme="$(basename "$theme_dir")"
mkdir -p "docs/assets/screenshots/gallery/$theme"
cp "$theme_dir"*.png "docs/assets/screenshots/gallery/$theme/"
done
# `import` (ImageMagick) writes full 24-bit-per-pixel PNGs, but
# these are terminal screenshots — a few dozen distinct colors,
# sharp flat-color edges, no photographic gradients — exactly what
# pngquant's palette quantization is for. 272 images at ~17MB
# became ~5MB at quality=80-95 with no visible difference
# (verified by eye locally); `--skip-if-larger` means a pathological
# input just keeps its original bytes instead of growing.
# Regenerating this gallery on every single release forever makes
# the per-release size here matter far more than a one-off image
# would, hence bothering to compress it at all.
pngquant --quality=80-95 --skip-if-larger --ext .png --force \
docs/assets/screenshots/gallery/*/*.png
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add docs/assets/screenshots docs/assets/demo.gif
if git diff --cached --quiet; then
echo "No screenshot/demo-gif changes to commit"
elif [ -z "$HAS_PAT" ]; then
echo "::error::RELEASE_TAG_PAT secret is not set — main requires a pull" \
"request, so pushing with the default GITHUB_TOKEN would be rejected." \
"Add a repo-scoped personal access token as RELEASE_TAG_PAT."
exit 1
else
git commit -m "docs: refresh screenshots and demo gif for ${{ needs.resolve-tag.outputs.tag }}"
git push origin HEAD:main
fi
update-release-pages:
name: Generate the per-version release page, OG share card, and RSS feed
# Needs update-screenshots specifically (not just release/resolve-tag),
# for two reasons: the OG card picks a theme's screenshot from
# docs/assets/screenshots/*.png, which that job just refreshed for this
# exact release, and running after it (rather than in parallel) avoids
# the same push race update-screenshots itself avoids by depending on
# update-packaging-manifests — two jobs racing to push their own commit
# to `main` at the same time, one of them rejected as a non-fast-forward.
needs: [release, resolve-tag, update-screenshots]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
with:
ref: main
token: ${{ secrets.RELEASE_TAG_PAT || github.token }}
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# ubuntu-latest's own `chromium-browser` apt package is a snap-only
# stub with no snapd available in this container, so it silently fails
# to launch — browser-actions/setup-chrome downloads a real, working
# Chromium binary instead, the same class of fix documented for the
# sandbox's own capped-viewport headless-Chromium render CLAUDE.md
# already describes for the original, hand-built og-image.png.
- uses: browser-actions/setup-chrome@v1
id: setup-chrome
- name: Generate release page + OG card + feed.xml for this version
id: generate
run: |
set -euo pipefail
tag="${{ needs.resolve-tag.outputs.tag }}"
version="${tag#v}"
echo "version=$version" >> "$GITHUB_OUTPUT"
python3 scripts/generate_release_pages.py --version "$version" \
--chromium "${{ steps.setup-chrome.outputs.chrome-path }}"
- name: Commit and push the generated release page + feed
env:
HAS_PAT: ${{ secrets.RELEASE_TAG_PAT }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add "docs/changelog/${{ steps.generate.outputs.version }}.html" \
"docs/assets/og/${{ steps.generate.outputs.version }}.png" \
docs/sitemap.xml docs/feed.xml
if git diff --cached --quiet; then
echo "No release-page changes to commit"
elif [ -z "$HAS_PAT" ]; then
echo "::error::RELEASE_TAG_PAT secret is not set — main requires a pull" \
"request, so pushing with the default GITHUB_TOKEN would be rejected." \
"Add a repo-scoped personal access token as RELEASE_TAG_PAT."
exit 1
else
git commit -m "docs: add release page and OG card for ${{ needs.resolve-tag.outputs.tag }}"
git push origin HEAD:main
fi
publish-crates:
name: Publish to crates.io
needs: release
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Check registry token is configured
id: check
env:
TOKEN_SET: ${{ secrets.CARGO_REGISTRY_TOKEN != '' }}
run: echo "has_token=${TOKEN_SET}" >> "$GITHUB_OUTPUT"
- name: Publish crates in dependency order
if: steps.check.outputs.has_token == 'true'
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
set -euo pipefail
for crate in shiki-core shiki-config shiki-tui shiki-cli; do
cargo publish -p "$crate"
echo "Waiting for $crate to become available on the index..."
sleep 30
done