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
name: Deploy docs
on:
# `latest` tracks releases. A docs change merged today describes the crate that will ship, not
# the one a reader has installed, so it reaches the numbered versions with the release that
# ships it.
release:
types:
# `main` is published as `next`, marked as unreleased on every page, so a reader of the
# upcoming version finds its documentation before the release.
push:
branches:
paths:
- "docs/**"
- "website/**"
- "examples/**"
# A manual run either republishes a release from its tag (a typo fix or a broken link worth
# publishing between releases), or, with `archive_ref`, rebuilds an older line from its archive
# branch with the site code of the branch the run starts from.
workflow_dispatch:
inputs:
ref:
description: "Tag or ref to deploy the site from (defaults to the current branch)"
required: false
type: string
version:
description: "Minor line of an archive, such as 0.6 (with archive_ref)"
required: false
type: string
archive_ref:
description: "Archive branch holding that line's pages, such as docs/archive/v0.6"
required: false
type: string
permissions:
contents: write
# Every deploy rewrites the whole gh-pages branch, so deploys run one at a time, in order. The
# queue keeps every waiting run: without it a newer run would cancel a waiting one, and a push to
# main could drop a release deploy.
concurrency:
group: docs-deploy
cancel-in-progress: false
queue: max
jobs:
deploy:
name: Deploy versioned docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# On a release this is the tag; a manual run may name one, and falls back to the
# branch it was started from. An archive run builds with the site code of that branch.
ref: ${{ inputs.ref || github.ref }}
fetch-depth: 0
# The archive branch holds the old line's pages, the sources they include and its sidebar;
# it carries no site code.
- name: Check out the archive
if: inputs.archive_ref
uses: actions/checkout@v7
with:
ref: ${{ inputs.archive_ref }}
path: archive
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: npm
cache-dependency-path: website/package-lock.json
- name: Install site toolchain
working-directory: website
run: npm ci
# The API links on the pages resolve against the rustdoc of the code deployed. An archive's
# pages carry none.
- name: Install Rust toolchain
if: "!inputs.archive_ref"
uses: dtolnay/rust-toolchain@stable
- name: Build the rustdoc the API links resolve against
if: "!inputs.archive_ref"
run: cargo doc --locked --all-features --no-deps -p ruststream
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# The site is versioned by the crate's minor line, read from the manifest at the deployed
# ref. On a release that ref is the tag, so the two must agree: a tag deployed against a
# manifest still on the previous version would publish the new docs under the old number,
# and `latest` would point at a version nobody released.
#
# Every version lives side by side in the gh-pages tree, so a deploy rebuilds only its own
# directory. `latest` follows the newest release: `next`, an archive and a fix to a past
# release leave it where it is. A release of a new minor line also rebuilds the line it
# takes `latest` from as an older version, from that line's newest tag (fetched above with
# the full history) and its rustdoc.
- name: Build and place the site
env:
TAG: ${{ github.event_name == 'release' && github.ref_name || '' }}
EVENT: ${{ github.event_name }}
ARCHIVE_VERSION: ${{ inputs.version }}
ARCHIVE_REF: ${{ inputs.archive_ref }}
# Ownership tokens of Google Search Console and Bing Webmaster Tools, written into the
# site's root page and every page of each build, and the name of the verification file
# Google fetches from the root; repository variables, empty until the
# maintainer sets them.
DOCS_GOOGLE_SITE_VERIFICATION: ${{ vars.DOCS_GOOGLE_SITE_VERIFICATION }}
DOCS_BING_SITE_VERIFICATION: ${{ vars.DOCS_BING_SITE_VERIFICATION }}
DOCS_GOOGLE_SITE_VERIFICATION_FILE: ${{ vars.DOCS_GOOGLE_SITE_VERIFICATION_FILE }}
run: |
git fetch origin gh-pages
git worktree add -B gh-pages gh-pages origin/gh-pages
if [ "$EVENT" = "push" ]; then
GH_PAGES_DIR=gh-pages scripts/deploy_docs.sh next
exit 0
fi
if [ -n "$ARCHIVE_REF" ]; then
if ! [[ "$ARCHIVE_VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then
echo "An archive run needs the version of its line, such as 0.6; got '${ARCHIVE_VERSION}'"
exit 1
fi
GH_PAGES_DIR=gh-pages scripts/deploy_docs.sh "$ARCHIVE_VERSION" \
--content archive --source-ref "$ARCHIVE_REF"
exit 0
fi
if [ -n "$ARCHIVE_VERSION" ]; then
echo "version names the line of an archive and needs archive_ref"
exit 1
fi
version="$(sed -nE 's/^version = "([0-9]+)\.([0-9]+).*/\1.\2/p' Cargo.toml | head -1)"
if [ -n "$TAG" ] && [ "${TAG#v}" != "$(sed -nE 's/^version = "([^"]+)".*/\1/p' Cargo.toml | head -1)" ]; then
echo "Tag '${TAG}' does not match the manifest version; refusing to publish docs"
exit 1
fi
newest="$(node -e '
const versions = require("./gh-pages/versions.json");
const release = versions.find((entry) => /^v?\d/.test(entry.version));
console.log(release ? release.version : "");
')"
alias_args=()
if [ -z "$newest" ] || [ "$(printf '%s\n%s\n' "$version" "$newest" | sort -V | tail -1)" = "$version" ]; then
alias_args=(--alias latest)
fi
GH_PAGES_DIR=gh-pages scripts/deploy_docs.sh "$version" "${alias_args[@]}"
# Each deploy would otherwise add a commit carrying a full copy of the rendered site, and
# the branch would grow without bound (GitHub keeps the whole history). Serving only needs
# the latest tree: rewrite the branch to a single commit holding the current site and
# force-push, the same fix faststream applies to its docs pipeline. The deploy reads only
# the branch tree (all versions live side by side in it), never the history.
- name: Squash gh-pages history and push
working-directory: gh-pages
run: |
git add -A
git reset --hard "$(git commit-tree -m 'docs: deploy' "$(git write-tree)")"
git push --force origin gh-pages