Skip to content

Version the published documentation with mike - #44

Merged
rpmcginty merged 1 commit into
mainfrom
chore/docs-versioning
Sep 15, 2026
Merged

rpmcginty merged 1 commit into
mainfrom
chore/docs-versioning

Conversation

@rpmcginty

@rpmcginty rpmcginty commented Aug 19, 2026 •

Copy link
Copy Markdown
Collaborator

Why

publish_docs.yml deployed to gh-pages with force_orphan: true, which recreated the branch on every build — so only the newest docs ever existed and there was no way to look back at a released version. extra.version.provider was also set to mkdocs, which isn't a value mkdocs-material recognizes (its JS only acts on "mike" plus a versions.json), so no version selector was ever rendered.

What

Publish through mike, which keeps each version in its own subdirectory on gh-pages:

URL Contents
/ Redirect to latest/
latest/ Newest release (alias for the highest X.Y)
X.Y/ Built from that minor version's most recent release tag
dev/ Built from the tip of main
  • publish_docs.yml — pushes to main redeploy dev.
  • release.yml — new publish-docs job deploys the release's X.Y version and moves the latest alias. It builds from the release tag, so published docs match the released code; patch releases refresh their minor version rather than adding an entry.
  • Makefile — docs-versions, docs-serve-versions, docs-deploy-dev, docs-deploy-release. DOCS_PUSH=true opts into pushing, so a local deploy commits to gh-pages for review first.
  • docs/developer/index.md — documents the layout, the workflows, and the local commands.

Two deliberate details:

  • --alias-type=copy for latest instead of mike's default symlink, so serving doesn't depend on the host resolving git symlinks. It costs nothing: the copied files are byte-identical, so git stores one tree object for both (verified — latest and 1.0 hashed the same).
  • A shared concurrency: publish-docs group across both workflows. A release bumps _version.py on main, which triggers the dev deploy at the same time as the release deploy — two concurrent gh-pages pushes, and mike refuses a deploy when its local view of the branch is behind the remote.

mike is added to the docs dependency group and uv.lock — make install runs uv sync --frozen, so a lockfile-only omission would have left CI without mike.

Backfilling old versions (not in this PR)

Per review, the run-once backfill script and its instructions are not merged here. It lives outside the repos and will be run by hand once this merges. Copilot's finding on it — the throwaway worktree path was keyed by the derived X.Y rather than the tag, so two tags sharing a minor collided — is fixed in that copy.

Required one-time migration

Merging this is safe on its own: the first dev deploy adds dev/ and versions.json while the old flat site keeps serving from the branch root, so there's no breakage window. But the pre-mike files stay at that root and the root index.html remains the old site, so the cutover needs one manual run after merge:

cp ../.docs-migration/backfill-docs-versions.sh .
S=docs-versioned-migration

# Build each released X.Y from its own tag, and dev from main.
DOCS_BRANCH=$S ./backfill-docs-versions.sh
make docs-deploy-dev DOCS_BRANCH=$S

# Point latest at 1.0 without rebuilding it, so latest keeps the docs that
# shipped with 1.0 rather than main's current content.
uv run mike alias --branch $S --alias-type=copy -u 1.0 latest
uv run mike set-default --branch $S latest

git ls-tree --name-only $S          # review
git push --force origin $S:gh-pages
rm backfill-docs-versions.sh

make docs-deploy-release is deliberately not used here: it builds the working tree, which is right in release.yml (that job checks out the release tag) but wrong during migration, where main is ahead of the newest tag. Verified — using it rebuilt 1.0 from main and put content in it that was never in v1.0.0.

This rebuilds the branch rather than deleting leftovers — the old workflow force-pushed an orphan commit on every deploy, so there's no history to keep. GitHub Pages settings need no change (still gh-pages, root).

Testing

Run locally against throwaway branches, since these workflows can only be exercised for real on main:

  • make docs-build — clean.
  • make docs-deploy-dev + make docs-deploy-release DOCS_VERSION=1.0 — produced the expected root (.nojekyll, 1.0, dev, index.html, latest, versions.json), a versions.json listing dev and 1.0 [latest], and a root index.html redirecting to latest/.
  • Deploying onto a copy of the current gh-pages — confirmed it succeeds and leaves the old site intact, which is what the migration section above addresses.
  • The full migration sequence — produced a clean root with no legacy leftovers.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.81%. Comparing base (359e824) to head (2c0669f).
⚠️ Report is 11 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main      #44   +/-   ##
=======================================
  Coverage   90.80%   90.81%           
=======================================
  Files          26       27    +1     
  Lines        1446     1491   +45     
  Branches      137      145    +8     
=======================================
+ Hits         1313     1354   +41     
- Misses         96       98    +2     
- Partials       37       39    +2     

see 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rpmcginty
rpmcginty force-pushed the chore/docs-versioning branch from cc9a02c to dcfb8e8 Compare August 20, 2026 03:56
The gh-pages deploy force-pushed one flat site per build, so only the
newest docs ever existed. Publish through mike instead: pushes to main
deploy a `dev` version, and a release deploys its X.Y version built from
the release tag and moves the `latest` alias that the site root now
redirects to. Patch releases refresh their minor version rather than
adding an entry.

- add mike to the docs dependency group and the lockfile
- point Material's version selector at mike (extra.version.provider)
- add docs-versions / docs-serve-versions / docs-deploy-{dev,release}
  targets; aliases are created as copies so serving does not depend on
  the host resolving git symlinks, and git de-duplicates the blobs
- serialize the two gh-pages deploys with a shared concurrency group

Backfilling the versions released before now is a run-once operation, so
the script that does it is kept outside the repo rather than merged into
each one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rpmcginty
rpmcginty force-pushed the chore/docs-versioning branch from dcfb8e8 to 2c0669f Compare September 14, 2026 20:50

@kasbaker kasbaker left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@rpmcginty
rpmcginty merged commit 6becbeb into main Sep 15, 2026
9 checks passed
@rpmcginty
rpmcginty deleted the chore/docs-versioning branch September 15, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants