diff --git a/.agents/skills/bump-version-and-release/SKILL.md b/.agents/skills/bump-version-and-release/SKILL.md index 61aa4d309..6471c065d 100644 --- a/.agents/skills/bump-version-and-release/SKILL.md +++ b/.agents/skills/bump-version-and-release/SKILL.md @@ -75,8 +75,8 @@ Three workflows in `.github/workflows/` interact with `VERSION`: ### 3. `deploy_docs_from_release.yaml` — versioned docs - **Trigger:** GitHub `release` event with `types: [published]`. -- **Behavior:** runs `mike deploy --push --update-aliases latest`, publishing the docs site under the release tag and pointing the `latest` alias at it. -- Companion workflows publish unversioned docs from `main` (default alias `main`) and `develop` (alias `develop`). +- **Behavior:** publishes the docs under the release's **MAJOR.MINOR slug** (tag `0.21.0` → `/0.21/`) titled with the full tag, points the `latest` alias at it, and makes `latest` the site default. A later hotfix republishes the same slug in place (retitled to the new patch version), so URLs never break within a minor line. +- Companion workflows publish `develop` (slug `develop`, titled ` (unstable)`, pinned to the top of the version selector) and `main` (same MAJOR.MINOR slug mechanism as releases, so docs hotfixes go live without a release). There is no separate `main` docs version. So the full release path is: bump `VERSION` → PR → merge to `main`/`develop` (retag unchanged images and/or rebuild changed ones + push + sign) → cut a GitHub Release matching that VERSION (versioned docs go live). @@ -253,7 +253,7 @@ For a true release (dropping the pre-release suffix): 3. [ ] In the same PR, retitle the Release Notes section to `## X.Y.Z — YYYY-MM-DD` and open a fresh `## (Unreleased)` above it. 4. [ ] Merge to `main`. 5. [ ] Wait for `docker-build.yml` to push and sign all images. -6. [ ] Create a GitHub Release with tag `X.Y.Z` (matching `VERSION` exactly). Publishing the release fires `deploy_docs_from_release.yaml`, which runs `mike deploy --push --update-aliases X.Y.Z latest` and updates the versioned docs site. +6. [ ] Create a GitHub Release with tag `X.Y.Z` (matching `VERSION` exactly). Publishing the release fires `deploy_docs_from_release.yaml`, which publishes the docs under the `X.Y` slug (titled `X.Y.Z`) and points the `latest` alias and site default at it. 7. [ ] Verify the docs site shows the new version under the version selector and that `latest` resolves to it. ## References @@ -263,7 +263,7 @@ For a true release (dropping the pre-release suffix): - [`/.github/workflows/check-version-increment.yml`](../../../.github/workflows/check-version-increment.yml) — the PR gate (semver regex lives here) - [`/.github/workflows/docker-build.yml`](../../../.github/workflows/docker-build.yml) — build/push/sign on tag change - [`/.github/workflows/deploy_docs_from_release.yaml`](../../../.github/workflows/deploy_docs_from_release.yaml) — versioned docs on release -- [`/.github/workflows/deploy_docs_from_main.yaml`](../../../.github/workflows/deploy_docs_from_main.yaml) and [`deploy_docs_from_develop.yaml`](../../../.github/workflows/deploy_docs_from_develop.yaml) — branch-tracking docs aliases +- [`/.github/workflows/deploy_docs_from_main.yaml`](../../../.github/workflows/deploy_docs_from_main.yaml) and [`deploy_docs_from_develop.yaml`](../../../.github/workflows/deploy_docs_from_develop.yaml) — branch-tracking docs deploys (main republishes the current MAJOR.MINOR slug; develop publishes the `develop` preview) - [`/airstack.sh`](../../../airstack.sh) — defines `airstack version` and `get_VERSION` (used everywhere image tags are built) - [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) diff --git a/.env b/.env index b1ad7e2b3..a3e991a5e 100644 --- a/.env +++ b/.env @@ -12,7 +12,7 @@ PROJECT_NAME="airstack" # If you've run ./airstack.sh setup, then this will auto-generate from the git commit hash every time a change is made # to a Dockerfile or docker-compose.yaml file. Otherwise this can also be set explicitly to make a release version. # auto-generated from git commit hash -VERSION="0.20.1" +VERSION="0.20.2" # Image-tag discriminator ONLY (appears in the image tag suffix, e.g. ..._robot-x86-64_dev). # No Dockerfile consumes it: "prebuilt" does NOT bake the built ros_ws into the image today — # a real prebuilt (workspace-baked) stage is future work. Keep "dev" (mounted code, built live). diff --git a/.github/workflows/deploy_docs_from_develop.yaml b/.github/workflows/deploy_docs_from_develop.yaml index 686132fc0..2b170424d 100644 --- a/.github/workflows/deploy_docs_from_develop.yaml +++ b/.github/workflows/deploy_docs_from_develop.yaml @@ -105,10 +105,10 @@ jobs: run: | VERSION=$(grep -m1 '^VERSION=' .env | cut -d= -f2- | tr -d '"') mike deploy --push --title "${VERSION} (unstable)" develop - # mike re-sorts versions.json on every deploy ("main" sorts above - # "develop"), so pin develop — the higher, unreleased version — back - # to the top of the version selector after each deploy. - - name: Reorder version selector (develop above main) + # mike re-sorts versions.json on every deploy, so pin develop — the + # higher, unreleased version — back to the top of the version selector + # after each deploy. + - name: Reorder version selector (develop first) run: | git worktree add ../ghp-reorder gh-pages python3 .github/workflows/scripts/docs_reorder_versions.py ../ghp-reorder/versions.json diff --git a/.github/workflows/deploy_docs_from_main.yaml b/.github/workflows/deploy_docs_from_main.yaml index 75abb1e92..eea1f4d0b 100644 --- a/.github/workflows/deploy_docs_from_main.yaml +++ b/.github/workflows/deploy_docs_from_main.yaml @@ -55,15 +55,22 @@ jobs: run: | git config --global user.name "Docs Deploy" git config --global user.email "docs.deploy@example.co.uk" + # Stable docs live under a MAJOR.MINOR slug (e.g. /0.20/) — there is + # no separate "main" docs version (it duplicated the release entry in + # the version selector). A hotfix merged to main republishes the same + # slug in place and retitles the selector entry to the new patch + # version, so URLs never break within a minor line. - name: Build Docs Website run: | VERSION=$(grep -m1 '^VERSION=' .env | cut -d= -f2- | tr -d '"') - mike deploy --push --title "${VERSION} (stable)" main - mike set-default main --push - # mike re-sorts versions.json on every deploy ("main" sorts above - # "develop"), so pin develop — the higher, unreleased version — back - # to the top of the version selector after each deploy. - - name: Reorder version selector (develop above main) + SLUG=$(echo "${VERSION}" | cut -d. -f1-2) + echo "Deploying main's docs: slug ${SLUG}, title ${VERSION}" + mike deploy --push --update-aliases --title "${VERSION}" "${SLUG}" latest + mike set-default latest --push + # mike re-sorts versions.json on every deploy, so pin develop — the + # higher, unreleased version — back to the top of the version selector + # after each deploy. + - name: Reorder version selector (develop first) run: | git worktree add ../ghp-reorder gh-pages python3 .github/workflows/scripts/docs_reorder_versions.py ../ghp-reorder/versions.json diff --git a/.github/workflows/deploy_docs_from_release.yaml b/.github/workflows/deploy_docs_from_release.yaml index 618cb6aeb..c1c1adc34 100644 --- a/.github/workflows/deploy_docs_from_release.yaml +++ b/.github/workflows/deploy_docs_from_release.yaml @@ -49,13 +49,22 @@ jobs: run: | git config --global user.name "Docs Deploy" git config --global user.email "docs.deploy@example.co.uk" + # Docs versions use MAJOR.MINOR slugs (e.g. tag 0.21.0 → /0.21/) so a + # later hotfix can republish the same slug in place without breaking + # URLs; the selector entry's title carries the full patch version. - name: Build Docs Website - run: mike deploy --push --update-aliases ${{ github.event.release.tag_name }} latest - # mike re-sorts versions.json on every deploy ("main" sorts above - # "develop"), so pin develop — the higher, unreleased version — back - # to the top of the version selector after each deploy. Guarded: - # the script may be absent on release tags cut before it existed. - - name: Reorder version selector (develop above main) + env: + TAG: ${{ github.event.release.tag_name }} + run: | + SLUG=$(echo "${TAG}" | cut -d. -f1-2) + echo "Deploying release docs: slug ${SLUG}, title ${TAG}" + mike deploy --push --update-aliases --title "${TAG}" "${SLUG}" latest + mike set-default latest --push + # mike re-sorts versions.json on every deploy, so pin develop — the + # higher, unreleased version — back to the top of the version selector + # after each deploy. Guarded: the script may be absent on release tags + # cut before it existed. + - name: Reorder version selector (develop first) run: | if [ ! -f .github/workflows/scripts/docs_reorder_versions.py ]; then echo "skipped: docs_reorder_versions.py not on this tag" diff --git a/.github/workflows/scripts/docs_reorder_versions.py b/.github/workflows/scripts/docs_reorder_versions.py index fe0db4183..77c02b75d 100644 --- a/.github/workflows/scripts/docs_reorder_versions.py +++ b/.github/workflows/scripts/docs_reorder_versions.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 -"""Pin the docs version-selector order: develop above main. +"""Pin the docs version-selector order: develop first. -mike re-sorts versions.json on every deploy, and among non-numeric -("dev") versions it sorts reverse-lexically, so "main" always lands -above "develop". We want develop (which carries the higher, unreleased -version number) listed first while main stays the set-default landing -version. Every workflow that runs `mike deploy` must re-run this script -afterwards, since any mike write restores mike's own ordering. +mike re-sorts versions.json on every deploy, and the non-numeric +"develop" version does not reliably sort above the MAJOR.MINOR release +slugs. We want develop (which carries the higher, unreleased version +number) listed first, while the `latest` alias stays the set-default +landing version. Every workflow that runs `mike deploy` must re-run +this script afterwards, since any mike write restores mike's own +ordering. Usage: docs_reorder_versions.py @@ -20,7 +21,7 @@ def reorder(entries): - pinned_names = ('develop', 'main') + pinned_names = ('develop',) pinned = [e for name in pinned_names for e in entries if e['version'] == name] rest = [e for e in entries if e['version'] not in pinned_names] diff --git a/.github/workflows/sync-develop-from-main.yaml b/.github/workflows/sync-develop-from-main.yaml index 135a91863..56cb8f5f2 100644 --- a/.github/workflows/sync-develop-from-main.yaml +++ b/.github/workflows/sync-develop-from-main.yaml @@ -116,6 +116,9 @@ jobs: f.write(f"NEW_VERSION={new_ver}\n") PYEOF + # Read the version from .env: GITHUB_ENV exports only apply to + # later steps, so ${NEW_VERSION} would be empty here. + NEW_VERSION=$(grep -m1 '^VERSION=' .env | cut -d= -f2- | tr -d '"') git add .env git commit -m "Bump VERSION to ${NEW_VERSION} after sync from main" diff --git a/.openhands/microagents/repo.md b/.openhands/microagents/repo.md index 606b803c8..29fe29283 100644 --- a/.openhands/microagents/repo.md +++ b/.openhands/microagents/repo.md @@ -82,7 +82,7 @@ AirStack/ - Uses AirLab Docker registry credentials **Documentation Deployment**: -- **Main Branch** (`deploy_docs_from_main.yaml`): Deploys docs to main version on pushes to `main` +- **Main Branch** (`deploy_docs_from_main.yaml`): Republishes the current MAJOR.MINOR docs slug (e.g. `/0.20/`) from pushes to `main` and points `latest` at it - **Develop Branch** (`deploy_docs_from_develop.yaml`): Deploys docs to develop version on pushes to `develop` - **Release** (`deploy_docs_from_release.yaml`): Deploys docs to versioned release on GitHub releases - Uses MkDocs Material with mike for versioning diff --git a/docs/hooks/release_notes_current_version.py b/docs/hooks/release_notes_current_version.py index abd12db77..ade79db58 100644 --- a/docs/hooks/release_notes_current_version.py +++ b/docs/hooks/release_notes_current_version.py @@ -5,13 +5,15 @@ mike-deployed docs version should only render its own notes — the site's version selector (and the GitHub releases page) is the archive for the rest. -At build time this hook reads the ``VERSION=`` line from the repo-root -``.env`` and drops every ``## X.Y.Z ...`` section whose base semver does not -match (pre-release suffixes like ``-dev.N`` are ignored for matching, so -``VERSION="0.21.0-dev.13"`` keeps the ``## 0.21.0 (Unreleased)`` section). +Docs versions are published under MAJOR.MINOR slugs (e.g. ``/0.20/``), with +hotfix patches republished in place. At build time this hook reads the +``VERSION=`` line from the repo-root ``.env`` and keeps every ``## X.Y.Z ...`` +section sharing its MAJOR.MINOR — so a ``0.20`` build shows the ``## 0.20.0`` +section plus any ``## 0.20.1`` hotfix sections, and pre-release suffixes are +ignored (``VERSION="0.21.0-dev.13"`` keeps ``## 0.21.0 (Unreleased)``). -If no section matches the current VERSION, the page is left unfiltered and a -warning is logged rather than publishing an empty page. +If no section matches the current VERSION's MAJOR.MINOR, the page is left +unfiltered and a warning is logged rather than publishing an empty page. """ import logging @@ -47,19 +49,24 @@ def on_page_markdown(markdown, page, config, files): ) return markdown + minor = ".".join(current.split(".")[:2]) # "0.20.2" -> "0.20" + + def matches(version: str) -> bool: + return ".".join(version.split(".")[:2]) == minor + sections = list(SECTION_RE.finditer(markdown)) - if not any(m.group(1) == current for m in sections): + if not any(matches(m.group(1)) for m in sections): log.warning( - "release_notes hook: no '## %s' section in %s; " + "release_notes hook: no '## %s.x' section in %s; " "publishing the page unfiltered", - current, + minor, PAGE_SRC, ) return markdown pieces = [markdown[: sections[0].start()]] for i, match in enumerate(sections): - if match.group(1) != current: + if not matches(match.group(1)): continue end = sections[i + 1].start() if i + 1 < len(sections) else len(markdown) pieces.append(markdown[match.start() : end])