Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 42 additions & 45 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
// For format details, see https://containers.dev/implementors/json_reference/
{
"name": "myst-version-switcher-plugin Developer Container",
"build": {
"dockerfile": "../Dockerfile",
"target": "developer"
},
"remoteEnv": {
// Allow X11 apps to run inside the container
"DISPLAY": "${localEnv:DISPLAY}",
// Put things that allow it in the persistent cache
"PRE_COMMIT_HOME": "/cache/pre-commit",
"npm_config_cache": "/cache/npm-cache"
},
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"ms-azuretools.vscode-docker"
]
}
},
// Create the config folder for the bash-config feature
"initializeCommand": "mkdir -p ${localEnv:HOME}/.config/terminal-config",
"postCreateCommand": "npm install && npx prek install",
"runArgs": [
// Allow the container to access the host X11 display and EPICS CA
"--net=host",
// Make sure SELinux does not disable with access to host filesystems like tmp
"--security-opt=label=disable"
],
"mounts": [
// Mount in the user terminal config folder so it can be edited
{
"source": "${localEnv:HOME}/.config/terminal-config",
"target": "/user-terminal-config",
"type": "bind"
},
// Keep a persistent cross-container cache for pre-commit and npm
{
"source": "devcontainer-shared-cache",
"target": "/cache",
"type": "volume"
}
],
// Mount the parent as /workspaces so sibling repos are accessible
"workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind",
"name": "myst-version-switcher-plugin Developer Container",
"build": {
"dockerfile": "../Dockerfile",
"target": "developer"
},
"remoteEnv": {
// Allow X11 apps to run inside the container
"DISPLAY": "${localEnv:DISPLAY}",
// Put things that allow it in the persistent cache
"PRE_COMMIT_HOME": "/cache/pre-commit",
"npm_config_cache": "/cache/npm-cache"
},
"customizations": {
"vscode": {
"extensions": ["biomejs.biome", "ms-azuretools.vscode-docker"]
}
},
// Create the config folder for the bash-config feature
"initializeCommand": "mkdir -p ${localEnv:HOME}/.config/terminal-config",
"postCreateCommand": "npm install && npx prek install",
"runArgs": [
// Allow the container to access the host X11 display and EPICS CA
"--net=host",
// Make sure SELinux does not disable with access to host filesystems like tmp
"--security-opt=label=disable"
],
"mounts": [
// Mount in the user terminal config folder so it can be edited
{
"source": "${localEnv:HOME}/.config/terminal-config",
"target": "/user-terminal-config",
"type": "bind"
},
// Keep a persistent cross-container cache for pre-commit and npm
{
"source": "devcontainer-shared-cache",
"target": "/cache",
"type": "volume"
}
],
// Mount the parent as /workspaces so sibling repos are accessible
"workspaceMount": "source=${localWorkspaceFolder}/..,target=/workspaces,type=bind"
}
11 changes: 0 additions & 11 deletions .github/pages/index.html

This file was deleted.

22 changes: 12 additions & 10 deletions .github/workflows/_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,28 @@ jobs:
BASE_URL: /${{ github.event.repository.name }}/${{ env.DOCS_VERSION }}
run: npm run docs

- name: Prepare html pages for artifact upload
run: cp -r docs/_build/html $RUNNER_TEMP/artifact/html
# Two layouts from the one build: `artifact/html` for the uploaded artifact
# (downstream relies on the `html` dir name), and `pages/<version>` for
# the gh-pages publish tree, into which the action also writes switcher.json
# and the root redirect.
- name: Stage built docs
run: |
mkdir -p $RUNNER_TEMP/artifact $RUNNER_TEMP/pages
cp -r docs/_build/html $RUNNER_TEMP/artifact/html
cp -r docs/_build/html $RUNNER_TEMP/pages/${{ env.DOCS_VERSION }}

- name: Upload built docs artifact
uses: actions/upload-artifact@v4
with:
name: docs
path: ${{ runner.temp }}/artifact

- name: Copy committed pages into staging
run: |
cp -r .github/pages/. $RUNNER_TEMP/_staging/
cp -r docs/_build/html $RUNNER_TEMP/_staging/${{ env.DOCS_VERSION }}

- name: Write switcher.json
- name: Write switcher.json + redirect
uses: ./switcher
with:
version: ${{ env.DOCS_VERSION }}
repo: ${{ github.repository }}
output: ${{ runner.temp }}/_staging/switcher.json
output-dir: ${{ runner.temp }}/pages

- name: Publish Docs to gh-pages
if: github.ref_type == 'tag' || github.ref_name == 'main'
Expand All @@ -61,5 +63,5 @@ jobs:
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ${{ runner.temp }}/_staging
publish_dir: ${{ runner.temp }}/pages
keep_files: true
45 changes: 25 additions & 20 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,18 @@

A pydata-style version-switcher for [MyST](https://mystmd.org) docs, delivered as
a single `anywidget` plugin **plus** a CI composite action that generates the
`switcher.json` the widget reads.
`switcher.json` the widget reads and a root `index.html` redirect to the newest
stable release.

## Repo layout

```
plugins/version-switcher/version-switcher.mjs # MyST directive + anywidget runtime (single file, no README — docs are in docs/)
switcher/action.yml # composite action: writes switcher.json ONLY
switcher/make-switcher.mjs # dependency-free Node switcher generator
switcher/action.yml # composite action: writes switcher.json + index.html
switcher/make-switcher.mjs # dependency-free Node switcher + redirect generator
test/ # npm test suite (node, no framework)
docs/ # this repo's own docs (dogfoods the plugin)
.github/workflows/ci.yml # orchestrator → _test / _docs / _release
.github/pages/index.html # MUST stay committed — bootstraps .github/pages/
# dir (so mv step works) + redirects root to main/
```

## Two halves, different lifecycles
Expand All @@ -29,11 +28,15 @@ the action is consumed from the repo tree at the same tag.

## Key design decisions

### `switcher` action is write-only
The action writes `.github/pages/switcher.json` and nothing else. It does NOT `mv`
the built docs, does NOT `git fetch`. Staging the versioned dir (`mv`) and
`fetch-depth: 0` (for tags + `origin/gh-pages`) are the caller's responsibility
(pattern lifted from `python-copier-template-example`).
### `switcher` action only writes the two derived files
The action writes `switcher.json` and a root `index.html` (a redirect to the
newest stable release) into the caller-supplied `output-dir` — the gh-pages
publish root — and nothing else. It does NOT `mv` the built docs, does NOT
`git fetch`. Staging the versioned dir (`mv`) and `fetch-depth: 0` (for tags +
`origin/gh-pages`) are the caller's responsibility (pattern lifted from
`python-copier-template-example`). Both files are derived purely from the git
version ordering, so regenerating them every deploy is intentional — with
`keep_files: true` each deploy refreshes the root redirect to the latest release.

### BASE_URL must be set before `myst build`
```yaml
Expand All @@ -43,14 +46,13 @@ run: cd docs && myst build --html
```
Without this, assets and links break under the versioned GitHub Pages sub-path.

### `.github/pages/index.html` must stay committed
- Redirects the Pages root to `./main/index.html`.
- CI copies it into `_staging/` before publishing, so it always lands on gh-pages.
- `.github/pages/` is source-only; CI writes nothing there — versioned builds stage in `_staging/`.

### `make-switcher.mjs` degrades gracefully on first deploy
When `origin/gh-pages` does not yet exist, it produces a single-entry `switcher.json`
for just the current version rather than failing.
When `origin/gh-pages` does not yet exist (no deployed builds, no tags), it
produces a single-entry `switcher.json` for just the current version and an
`index.html` redirecting to it, rather than failing. The "preferred" version (the
`index.html` target, flagged `preferred: true` in switcher.json) is the newest
non-prerelease tag with a deployed build, falling back to `main`/`master`.
Prerelease detection mirrors `_release.yml` (an `a`/`b`/`rc` marker).

## CI structure

Expand All @@ -64,7 +66,7 @@ Mirrors `python-copier-template-example` as closely as possible:
### `_docs.yml` deviations from template
1. `npm install -g mystmd@1.10.1` instead of `uv run tox -e docs` (no Python here)
2. `BASE_URL` env var set before `myst build`
3. `uses: ./switcher` writes `switcher.json` (instead of `make_switcher.py`)
3. `uses: ./switcher` writes `switcher.json` + `index.html` (instead of `make_switcher.py`)

`mystmd` is pinned at `1.10.1` (not `latest`).

Expand Down Expand Up @@ -123,14 +125,17 @@ site:
- run: cd docs && myst build --html
env:
BASE_URL: /<repo>/${{ env.DOCS_VERSION }}
- run: mv docs/_build/html .github/pages/$DOCS_VERSION
- run: |
mkdir -p _site
mv docs/_build/html _site/$DOCS_VERSION
- uses: DiamondLightSource/myst-version-switcher-plugin/switcher@<tag>
with:
version: ${{ env.DOCS_VERSION }}
repo: ${{ github.repository }}
output-dir: _site # required: writes switcher.json + index.html into the publish root
- uses: peaceiris/actions-gh-pages@v4
with:
publish_dir: .github/pages
publish_dir: _site
keep_files: true
```

Expand Down
6 changes: 6 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"formatter": { "indentStyle": "tab" },
"javascript": { "formatter": { "quoteStyle": "double" } }
}
28 changes: 17 additions & 11 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,18 @@ stranding users at the root.
path, the widget synthesises a `local (dev)` entry rooted at `/` so the switcher
is usable during `myst start`.

## Generating switcher.json
## Generating switcher.json + the root redirect

The `switcher` composite action reads your repo's tags and `origin/gh-pages` to
produce a `switcher.json` in the standard pydata format:
produce two files in your publish root: a `switcher.json` in the standard pydata
format, and an `index.html` that redirects the site root to your newest stable
release. The newest non-prerelease tag is flagged `preferred` (rendered with a ★)
and is the redirect target; before any release exists it falls back to `main`.

```json
[
{ "version": "main", "name": "main (dev)", "url": "https://ORG.github.io/REPO/main/" },
{ "version": "2.1", "name": "2.1 (stable)", "url": "https://ORG.github.io/REPO/2.1/", "preferred": true },
{ "version": "main", "url": "https://ORG.github.io/REPO/main/" },
{ "version": "2.1", "url": "https://ORG.github.io/REPO/2.1/", "preferred": true },
{ "version": "2.0", "url": "https://ORG.github.io/REPO/2.0/" }
]
```
Expand All @@ -86,19 +89,22 @@ Wire it into your docs workflow after staging the built HTML and before publishi
- run: cd docs && myst build --html
env:
BASE_URL: /<repo>/${{ env.DOCS_VERSION }} # required for versioned sub-path
- run: mv docs/_build/html .github/pages/$DOCS_VERSION
- run: |
mkdir -p _site
mv docs/_build/html _site/$DOCS_VERSION
- uses: DiamondLightSource/myst-version-switcher-plugin/switcher@<tag>
with:
version: ${{ env.DOCS_VERSION }}
repo: ${{ github.repository }}
# output defaults to .github/pages/switcher.json
output-dir: _site # writes switcher.json + index.html into the publish root
- uses: peaceiris/actions-gh-pages@v4
with:
publish_dir: .github/pages
publish_dir: _site
keep_files: true
```

The action **only writes `switcher.json`** — staging (`mv`) and publishing stay in
the workflow. `fetch-depth: 0` is the consumer's responsibility. On the first
deploy, when `origin/gh-pages` does not yet exist, the action produces a
single-entry `switcher.json` for the current version rather than failing.
The action **only writes `switcher.json` and `index.html`** — staging (`mv`) and
publishing stay in the workflow. `fetch-depth: 0` is the consumer's
responsibility. On the first deploy, when `origin/gh-pages` does not yet exist,
the action produces a single-entry `switcher.json` for the current version and an
`index.html` redirecting to it, rather than failing.
48 changes: 24 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
{
"name": "myst-version-switcher-plugin",
"private": true,
"description": "A pydata-style version switcher for MyST, as a single anywidget plugin, plus a CI action to generate switcher.json.",
"type": "module",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/DiamondLightSource/myst-version-switcher-plugin.git"
},
"scripts": {
"test": "node test/test-url-logic.mjs && node test/test-make-switcher.mjs",
"docs": "cd docs && myst build --html",
"docs-dev": "cd docs && myst start"
},
"files": [
"plugins/version-switcher/version-switcher.mjs",
"switcher/make-switcher.mjs",
"switcher/action.yml"
],
"devDependencies": {
"@biomejs/biome": "^1.9.0",
"@j178/prek": "^0.3.0",
"mystmd": "1.10.1"
}
"name": "myst-version-switcher-plugin",
"private": true,
"description": "A pydata-style version switcher for MyST, as a single anywidget plugin, plus a CI action to generate switcher.json.",
"type": "module",
"license": "Apache-2.0",
"repository": {
"type": "git",
"url": "https://github.com/DiamondLightSource/myst-version-switcher-plugin.git"
},
"scripts": {
"test": "node test/test-url-logic.mjs && node test/test-make-switcher.mjs",
"docs": "cd docs && myst build --html",
"docs-dev": "cd docs && myst start"
},
"files": [
"plugins/version-switcher/version-switcher.mjs",
"switcher/make-switcher.mjs",
"switcher/action.yml"
],
"devDependencies": {
"@biomejs/biome": "^1.9.0",
"@j178/prek": "^0.3.0",
"mystmd": "1.10.1"
}
}
Loading
Loading