Skip to content

[AAASM-3523] 📝 (docs): Standardize reference links to latest stable channel #410

[AAASM-3523] 📝 (docs): Standardize reference links to latest stable channel

[AAASM-3523] 📝 (docs): Standardize reference links to latest stable channel #410

Workflow file for this run

name: Docs
permissions:
contents: read
pages: write
id-token: write
on:
pull_request:
branches: [master]
paths:
- 'docs/**'
- 'README.md'
- 'CONTRIBUTING.md'
- '.github/workflows/docs.yml'
push:
branches: [master]
paths:
- 'docs/**'
- 'README.md'
- 'CONTRIBUTING.md'
- '.github/workflows/docs.yml'
# Release-driven frozen-version cut (AAASM-2752). Instead of reacting to a
# raw tag push, the frozen snapshot is cut only after the Release workflow
# has *succeeded* for that tag. This guarantees we never publish docs for a
# release whose binaries/crates failed to build. The triggering run's
# head_branch is the tag name (Release fires on `push: tags`).
workflow_run:
workflows: ["Release"]
types: [completed]
jobs:
build:
name: Build mdBook
runs-on: ubuntu-latest
# PR + master-push always build. A workflow_run only proceeds when the
# upstream Release succeeded AND it was a release tag (vX.Y.Z[-pre]).
if: >-
github.event_name != 'workflow_run' ||
(github.event.workflow_run.conclusion == 'success' &&
startsWith(github.event.workflow_run.head_branch, 'v'))
steps:
- uses: actions/checkout@v7.0.0
with:
# Full history so the last-changed preprocessor can read per-page
# git dates; a shallow clone would collapse every page to one commit.
fetch-depth: 0
# For a release cut, build the docs from the tagged commit, not master.
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache mdBook binaries
uses: actions/cache@v5
with:
path: |
~/.cargo/bin/mdbook
~/.cargo/bin/mdbook-mermaid
key: ${{ runner.os }}-mdbook-0.5.2-mermaid-0.17.0
- name: Install mdBook
run: |
if ! command -v mdbook >/dev/null; then
cargo install --locked --version 0.5.2 mdbook
fi
- name: Install mdbook-mermaid
run: |
if ! command -v mdbook-mermaid >/dev/null; then
cargo install --locked --version 0.17.0 mdbook-mermaid
fi
# Guard the pre-release semver gate (docs/ci/channels.py): a logic
# regression here would silently mis-classify release channels, so run
# the channel-gate unit tests on every PR/push before building.
- name: Run channel-gate unit tests
run: python3 docs/ci/test_channels.py
- name: Build book
run: mdbook build docs
# --- Doc versioning (AAASM-2752) ---
# The Pages site is laid out as per-version subpaths under `_site/`:
# _site/index.html -> root redirect (stable -> pre-release -> latest)
# _site/versions.json -> manifest read by the selector + warning banner
# _site/latest/ -> live docs tracking master
# _site/<vX.Y.Z[-pre]>/ -> frozen, immutable release snapshots
#
# versions.json describes moving CHANNELS that point at a concrete
# subpath, plus immutable ARCHIVED concrete versions:
# { "channels": [ {id,title,target}, ... ],
# "archived": [ {id,title}, ... ] }
# Channels: latest (master), pre-release (newest vX.Y.Z-pre tag),
# stable (newest vX.Y.Z tag). Channel id classification:
# ^v\d+\.\d+\.\d+$ -> stable
# ^v\d+\.\d+\.\d+-.+ -> pre-release
#
# On master push we publish into latest/. On a *successful* Release
# (workflow_run) we publish into <tag>/, archive it, and repoint the
# stable or pre-release channel to it.
- name: Resolve target version subpath
if: github.event_name != 'pull_request'
id: docver
env:
EVENT_NAME: ${{ github.event_name }}
RELEASE_TAG: ${{ github.event.workflow_run.head_branch }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_run" ]; then
tag="$RELEASE_TAG"
# Classify the channel from the tag shape.
if printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
channel="stable"
elif printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+-.+'; then
channel="pre-release"
else
echo "::error::Tag '$tag' is not a release version; skipping docs cut."
exit 1
fi
echo "version=$tag" >> "$GITHUB_OUTPUT"
echo "channel=$channel" >> "$GITHUB_OUTPUT"
else
echo "version=latest" >> "$GITHUB_OUTPUT"
echo "channel=latest" >> "$GITHUB_OUTPUT"
fi
- name: Fetch prior live versions.json
# AAASM-2752 + AAASM-2827: the live manifest is the only place that
# carries the moving channel targets (pre-release -> vX.Y.Z-pre).
# archived[] is rebuilt from git tags in the next step so a missing
# or truncated prior manifest is no longer a permanent loss.
if: github.event_name != 'pull_request'
id: restore
continue-on-error: true
run: |
base_url="https://${GITHUB_REPOSITORY_OWNER}.github.io/${GITHUB_REPOSITORY##*/}"
echo "Fetching existing versions.json from ${base_url}/versions.json"
if curl -fsSL "${base_url}/versions.json" -o prior-versions.json; then
echo "prior_versions=true" >> "$GITHUB_OUTPUT"
else
echo "No existing versions.json (first deploy)."
echo "prior_versions=false" >> "$GITHUB_OUTPUT"
fi
- name: Rebuild every release tag's docs subpath from git
# AAASM-2827: the previous design mirrored prior subpaths from the
# live Pages site with `wget -r`, which silently lost versions when
# the mirror failed and only worked if the prior deploy was itself
# whole. Rebuild every archived release directly from its git tag
# instead — git is the source of truth, and this loop is idempotent
# and self-healing: if a prior release never published docs (e.g.
# because its Release workflow failed), it is recovered on the next
# successful deploy.
if: github.event_name != 'pull_request'
env:
VERSION: ${{ steps.docver.outputs.version }}
run: |
set -euo pipefail
mkdir -p _site
# Enumerate every release tag (vX.Y.Z[-pre]) reachable in the
# repository. The workflow's checkout uses fetch-depth: 0 so all
# tags are local.
mapfile -t TAGS < <(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' | sort)
: > extra-archived.txt
# Workspace for tag worktrees. We use git worktree (cheaper than
# a fresh clone per tag) and tear each one down after use.
BUILD_ROOT="$(mktemp -d)"
trap 'rm -rf "$BUILD_ROOT" || true' EXIT
for tag in "${TAGS[@]}"; do
# Skip the tag being cut this run; the freshly built docs/book
# is placed in _site/$VERSION below by the assemble step.
if [ "$tag" = "$VERSION" ]; then
echo "$tag" >> extra-archived.txt
continue
fi
# Already restored from the artifact upload of a prior step?
# (Defensive — no current step pre-populates _site, but this
# keeps the loop idempotent if one is added later.)
if [ -f "_site/$tag/index.html" ]; then
echo "Skipping $tag (already present in _site)"
echo "$tag" >> extra-archived.txt
continue
fi
workdir="$BUILD_ROOT/$tag"
echo "::group::Rebuild $tag"
# `git worktree add --detach` checks out the tag's tree without
# touching the current branch state. A tag whose docs/ subtree
# is missing or whose mdBook config has drifted past this
# workflow's mdbook version is logged and skipped — its subpath
# will fall back to the prior live mirror (if any) or 404.
if ! git worktree add --detach "$workdir" "$tag" >/dev/null 2>&1; then
echo "::warning::Could not check out $tag; skipping."
echo "::endgroup::"
continue
fi
if [ ! -f "$workdir/docs/book.toml" ]; then
echo "::warning::$tag has no docs/book.toml; skipping."
git worktree remove --force "$workdir" >/dev/null 2>&1 || true
echo "::endgroup::"
continue
fi
if ! mdbook build "$workdir/docs"; then
echo "::warning::mdbook build failed for $tag; skipping."
git worktree remove --force "$workdir" >/dev/null 2>&1 || true
echo "::endgroup::"
continue
fi
mkdir -p "_site/$tag"
cp -R "$workdir/docs/book/." "_site/$tag/"
git worktree remove --force "$workdir" >/dev/null 2>&1 || true
echo "$tag" >> extra-archived.txt
echo "::endgroup::"
done
echo "Rebuilt archive subpaths:"
find _site -maxdepth 1 -mindepth 1 -type d | sort
echo "extra-archived.txt:"
cat extra-archived.txt
- name: Assemble versioned Pages site
if: github.event_name != 'pull_request'
env:
VERSION: ${{ steps.docver.outputs.version }}
CHANNEL: ${{ steps.docver.outputs.channel }}
run: |
set -euo pipefail
mkdir -p _site
# 1. Place the freshly built book at the target version subpath.
# Archive subpaths for all *other* release tags were already
# materialised by the rebuild step above.
rm -rf "_site/$VERSION"
mkdir -p "_site/$VERSION"
cp -R docs/book/. "_site/$VERSION/"
# 2. Recompute versions.json via the testable channel module
# (docs/ci/channels.py), which applies the pre-release semver
# gate and unions extra-archived.txt (written by the rebuild
# step) into archived[]. AAASM-2827: this makes archived[]
# self-healing against a missing/truncated prior manifest.
python3 docs/ci/build_versions.py "$VERSION" "$CHANNEL" _site/versions.json
# 3. Root redirect (stable -> pre-release -> latest, resolved client-side).
cp docs/site-root-index.html _site/index.html
echo "Assembled _site:"; find _site -maxdepth 2 -type d | sort
- name: Upload Pages artifact
if: github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v5
with:
path: _site
deploy:
name: Deploy to GitHub Pages
if: github.event_name != 'pull_request'
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
verify-commands:
name: Verify documented commands (Linux)
# Only on PR / master push; a release cut re-verifies nothing new.
if: github.event_name != 'workflow_run'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7.0.0
- name: Install system dependencies (protoc, pkg-config, libssl-dev)
run: |
sudo apt-get update
sudo apt-get install -y protobuf-compiler pkg-config libssl-dev
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo build
uses: Swatinem/rust-cache@v2
- name: Verify README "Getting Started" build command
run: cargo build --workspace --exclude aa-ebpf
- name: Verify api-reference.md rustdoc command
run: cargo doc --workspace --no-deps --exclude aa-ebpf
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}