feat(mdbook): multi-version site, mise-pinned tools, justfile

Three intertwined changes — split conceptually but landed together
because they share the same workflow rewrite:

1. MULTI-VERSION SITE. The published site now keeps every prior spec
   version alongside the rolling latest build (docs.rs / readthedocs
   pattern):

   - .github/workflows/mdbook.yml builds latest from current HEAD and
     each v* tag in a git worktree, placing each into _site/<ver>/.
     Each per-version build gets MDBOOK_OUTPUT__HTML__SITE_URL set to
     /meshcore-spec/<ver>/ so the sitemap/canonical links resolve
     correctly per version.
   - Workflow emits _site/versions.json (newest first) and a root
     index.html that redirects to /latest/.
   - theme/version-picker.js is a small client-side dropdown that
     fetches /<base>/versions.json and lets the reader switch to the
     same chapter in any other version. Slots into mdbook's menu bar
     (.right-buttons); falls back to a fixed top-right widget if the
     menu isn't found.
   - theme/version-picker.css styles it to fit the navy theme.
   - book.toml wires the picker files via additional-js /
     additional-css; site-url defaults to /meshcore-spec/latest/ for
     the main build (CI overrides per tag).
   - Old tags built without these files render without a picker, which
     is fine — users won't typically need to navigate AWAY from an
     older version. v0.1.0 onward will all carry the picker.

   Spec versions use independent semver and do NOT mirror MeshCore
   firmware versions; each spec release records the upstream commit
   hash it was validated against in CHANGELOG.md.

2. MISE-PINNED TOOLS. mise.toml pins cargo:mdbook so local dev and
   CI use the same version. The workflow installs everything via
   jdx/mise-action instead of peaceiris/actions-mdbook, making
   mise.toml the single source of truth for tool versions.

3. JUSTFILE. justfile gives short names (build / serve / clean /
   validate / setup) for the common tasks. Recipes run under
   `mise exec -- sh -c …` so they always pick up the mise-pinned
   tools (sh is intentional, keeping recipes POSIX-compatible).

Documentation: README's spec link now mentions the versioning model;
src/00-overview.md gets a "Spec Versions" subsection explaining the
URL layout and version-picker.

Verified locally: `just build` produces a clean book/ with the
version-picker JS/CSS injected (hashed filenames, slotted into
theme/). JS parses clean under `node --check`.

Note for first-time deploy: GitHub Pages source must be set to
"GitHub Actions" in repo settings (one-time, manual). After that,
push to main builds + deploys latest, and pushing a v* tag adds
the snapshot to the site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.7 committed 2026-05-12 09:51:07 -06:00
1 parent 4d6f31d152
commit 9ad31f7470
8 files changed
+294 -9

No files matched your search

+85 -8
View File
@@ -3,6 +3,7 @@ name: mdBook
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
@@ -16,26 +17,102 @@ concurrency:
group: pages
cancel-in-progress: false
env:
# Repo path on GitHub Pages — change here if the repo is renamed.
SITE_BASE: /meshcore-spec
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install mdBook
uses: peaceiris/actions-mdbook@v2
- name: Checkout (with tags)
uses: actions/checkout@v4
with:
mdbook-version: latest
fetch-depth: 0
- name: Install mise + pinned tools (mdbook, etc.)
uses: jdx/mise-action@v2
with:
install: true
cache: true
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Build book
run: mdbook build
- name: Build all versions
run: |
set -euo pipefail
mkdir -p _site
WORKSPACE="$PWD"
# ---- latest (current ref) ----
echo "::group::build latest"
MDBOOK_OUTPUT__HTML__SITE_URL="${SITE_BASE}/latest/" \
mdbook build "$WORKSPACE" -d "$WORKSPACE/_site/latest"
echo "::endgroup::"
# ---- each v* tag ----
# Tags are built in worktrees so the workflow's own files (this
# file, the picker JS/CSS) don't bleed into older builds.
mapfile -t TAGS < <(git tag --list 'v*' --sort=-v:refname)
for tag in "${TAGS[@]:-}"; do
[ -z "$tag" ] && continue
echo "::group::build $tag"
wt="$(mktemp -d -t "mdbook-${tag//\//-}-XXXXXX")"
git worktree add --detach "$wt" "$tag"
# If a very old tag lacks book.toml, skip it but don't fail.
if [ -f "$wt/book.toml" ]; then
MDBOOK_OUTPUT__HTML__SITE_URL="${SITE_BASE}/${tag}/" \
mdbook build "$wt" -d "$WORKSPACE/_site/${tag}" || {
echo "::warning::mdbook build failed for ${tag}; skipping"
rm -rf "$WORKSPACE/_site/${tag}"
}
else
echo "::warning::${tag} has no book.toml; skipping"
fi
git worktree remove --force "$wt"
echo "::endgroup::"
done
# ---- versions.json (consumed by theme/version-picker.js) ----
# Newest first; "latest" always at the top.
{
printf '['
printf '"latest"'
for tag in "${TAGS[@]:-}"; do
[ -z "$tag" ] && continue
[ -d "_site/${tag}" ] || continue
printf ',"%s"' "$tag"
done
printf ']\n'
} > _site/versions.json
echo "versions.json:"
cat _site/versions.json
# ---- root redirect to /latest/ ----
cat > _site/index.html <<'HTML'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=./latest/">
<link rel="canonical" href="./latest/">
<title>MeshCore Protocol Specification</title>
</head>
<body>
<p>Redirecting to the <a href="./latest/">latest version</a>&hellip;</p>
</body>
</html>
HTML
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./book
path: ./_site
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
needs: build
runs-on: ubuntu-latest
environment: