diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c919c78..2ad69e9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -46,7 +46,14 @@ jobs: outputs: version-name: ${{ steps.ver.outputs.version-name }} steps: + # fetch-depth: 0 brings the full history *and tags* — a build-command may + # derive a version from `git describe` (this repo's docs do, to pin the + # tutorial's snippets at the latest release), and the default shallow clone + # has no tags. Tags are cheap; fetch them unconditionally so any consumer's + # build-command can rely on them. - uses: actions/checkout@v7 + with: + fetch-depth: 0 # uv is installed unconditionally (Node/npx is already on the runner) so every # docs build has the same baseline env regardless of build-command. With no diff --git a/docs/_ext/inject-latest-tag.mjs b/docs/_ext/inject-latest-tag.mjs new file mode 100644 index 0000000..44ef4fa --- /dev/null +++ b/docs/_ext/inject-latest-tag.mjs @@ -0,0 +1,66 @@ +/** + * inject-latest-tag — a build-only MyST transform for *this repo's own docs*. + * + * The tutorial's copy-paste snippets pin this project at a release tag. Rather + * than hard-coding (and forever bumping) a version, the source carries the + * sentinel `__LATEST_TAG__` and this transform swaps it for the repo's latest + * tag at build time, so a reader copying the blocks gets a real, current ref. + * + * Unlike `plugins/version-switcher.mjs` (which is isomorphic — it also runs in + * the browser), this file only ever runs under MyST (Node) at build time, so it + * is free to touch git. It is loaded from `docs/myst.yml`, not shipped. + * + * The tag is read straight from `git describe` (CI checks out with fetch-depth 0 + * in docs.yml, so the tags are present). When no tag can be resolved the sentinel + * is left in place and a warning is emitted, so a missing tag is loud rather than + * silently shipping `__LATEST_TAG__` to readers. + */ +import { execSync } from "node:child_process"; + +const SENTINEL = "__LATEST_TAG__"; + +/** The latest release tag (`git describe --tags --abbrev=0`), or null. */ +function resolveLatestTag() { + try { + return execSync("git describe --tags --abbrev=0", { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + } catch { + return null; + } +} + +/** Replace the sentinel in every string `value` in the tree (text + code nodes). */ +function replaceInTree(node, tag) { + if (!node || typeof node !== "object") return; + if (typeof node.value === "string" && node.value.includes(SENTINEL)) { + node.value = node.value.split(SENTINEL).join(tag); + } + if (Array.isArray(node.children)) { + for (const child of node.children) replaceInTree(child, tag); + } +} + +const injectLatestTag = { + name: "inject-latest-tag", + doc: "Replace __LATEST_TAG__ with this repo's latest git tag at build time.", + stage: "document", + plugin: () => (tree, vfile) => { + const tag = resolveLatestTag(); + if (!tag) { + vfile?.message?.( + `inject-latest-tag: no tag resolved from git describe; leaving ${SENTINEL} as-is.`, + ); + return; + } + replaceInTree(tree, tag); + }, +}; + +const plugin = { + name: "inject-latest-tag", + transforms: [injectLatestTag], +}; + +export default plugin; diff --git a/docs/myst.yml b/docs/myst.yml index daa1895..0efe995 100644 --- a/docs/myst.yml +++ b/docs/myst.yml @@ -6,6 +6,8 @@ project: # makes MyST localize this same file as the anywidget runtime. plugins: - ../plugins/version-switcher.mjs + # Build-only: swaps __LATEST_TAG__ in the tutorial for this repo's latest tag. + - ./_ext/inject-latest-tag.mjs toc: - file: index.md - file: tutorials.md diff --git a/docs/tutorials/adding-to-a-fresh-repo.md b/docs/tutorials/adding-to-a-fresh-repo.md index 3e6503f..3f3586a 100644 --- a/docs/tutorials/adding-to-a-fresh-repo.md +++ b/docs/tutorials/adding-to-a-fresh-repo.md @@ -8,8 +8,10 @@ version switcher and a versioned GitHub Pages deployment. By the end you will ha - a `stable/` alias pointing at your latest release. It assumes a repo that already builds docs with `myst build --html` from a `docs/` -directory. Replace `ORG/REPO` throughout, and pin a real `` from this project's -[releases](https://github.com/DiamondLightSource/myst-version-switcher-plugin/releases). +directory. Replace `ORG/REPO` throughout. The snippets below are pinned to the +latest release (`__LATEST_TAG__`); bump that pin to any version from this project's +[releases](https://github.com/DiamondLightSource/myst-version-switcher-plugin/releases) +when you need a different one. ## 1. Add the plugin to your MyST project @@ -19,7 +21,7 @@ In `docs/myst.yml`, load the plugin from its release asset and route a navbar pa # docs/myst.yml project: plugins: - - https://github.com/DiamondLightSource/myst-version-switcher-plugin/releases/download//version-switcher.mjs + - https://github.com/DiamondLightSource/myst-version-switcher-plugin/releases/download/__LATEST_TAG__/version-switcher.mjs site: template: book-theme parts: @@ -79,7 +81,7 @@ on: jobs: docs: # Call the docs building workflow directly - uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/docs.yml@ + uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/docs.yml@__LATEST_TAG__ with: # Whatever turns your sources into docs/_build/html at $BASE_URL. # uv and Node are preinstalled, so this can be make / tox / npx / npm. @@ -88,7 +90,7 @@ jobs: release: needs: [docs] if: github.ref_type == 'tag' # tag pushes only - uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/release.yml@ + uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/release.yml@__LATEST_TAG__ permissions: contents: write # create the Release + attach assets @@ -109,10 +111,11 @@ jobs: ### `publish-dispatch.yml` `publish.yml` is the engine and owns all the branching; this is a thin **shim** — the -only thing that calls it, and the one place you pin `@`. It has to exist as a file +only thing that calls it, and the one place you pin `@__LATEST_TAG__`. It has to exist as a file in your repo (not just be `uses:`'d) because the tag trampoline re-dispatches it as a `workflow_dispatch`, and a reusable workflow can't be dispatched cross-repo. Copy it -verbatim, changing only ``: +verbatim; the only thing to keep current is the `publish.yml` pin, already set to +`__LATEST_TAG__`: ```yaml # .github/workflows/publish-dispatch.yml @@ -126,7 +129,7 @@ on: pr: { description: "Fork PR to approve + preview (empty = re-deploy)", required: false, default: "" } jobs: publish: - uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/publish.yml@ + uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/publish.yml@__LATEST_TAG__ with: version-name: ${{ inputs.version-name }} # "" on dispatch → pure durable gather pr: ${{ inputs.pr }} # set (dispatch) → pin that fork head SHA