Repository navigation
ci: let workflow_dispatch actually deploy docs, rename to "Docs Publish" #236
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Docs Publish | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - docs/** | |
| - src/** | |
| - packages/mvtb-data/** | |
| - pyproject.toml | |
| - .github/workflows/docs.yml | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - docs/** | |
| - src/** | |
| - packages/mvtb-data/** | |
| - pyproject.toml | |
| - .github/workflows/docs.yml | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| # Only one deployment at a time; don't cancel an in-progress deploy | |
| concurrency: | |
| group: pages | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v7 | |
| with: | |
| python-version: "3.12" | |
| - name: Check notebook bootstrap cells and outputs are in sync | |
| run: | | |
| python docs/notebooks/sync_bootstrap.py --check | |
| python docs/notebooks/clear_outputs.py --check | |
| - name: Install system dependencies | |
| run: sudo apt-get install -y graphviz | |
| - name: Install Python dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install -r docs/requirements.txt | |
| cd packages/mvtb-data && pip install . | |
| - name: Build docs | |
| run: sphinx-build -b html docs/source docs/build/html | |
| - name: Build wheel for JupyterLite | |
| run: | | |
| pip install build | |
| python -m build --wheel --outdir docs/lite/pypi | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: docs/lite/wasm_check/package-lock.json | |
| - name: Check the JupyterLite install path actually works (real Pyodide, real WASM) | |
| run: | | |
| npm --prefix docs/lite/wasm_check ci | |
| node docs/lite/wasm_check/check_pyodide_install.mjs docs/lite/pypi/*.whl | |
| - name: Sync notebooks into JupyterLite content | |
| run: | | |
| mkdir -p docs/lite/files | |
| find docs/notebooks \ | |
| -type f \ | |
| -name '*.ipynb' \ | |
| ! -path '*/.ipynb_checkpoints/*' \ | |
| ! -path '*/old/*' \ | |
| -print0 | while IFS= read -r -d '' nb; do | |
| rel="${nb#docs/notebooks/}" | |
| mkdir -p "docs/lite/files/$(dirname "$rel")" | |
| cp -p "$nb" "docs/lite/files/$rel" | |
| done | |
| - name: Adapt staged notebooks for JupyterLite | |
| run: | | |
| python - <<'PY' | |
| import json | |
| from pathlib import Path | |
| root = Path("docs/lite/files") | |
| for nb_path in root.rglob("*.ipynb"): | |
| with nb_path.open("r", encoding="utf-8") as f: | |
| nb = json.load(f) | |
| meta = nb.setdefault("metadata", {}) | |
| meta["kernelspec"] = { | |
| "name": "python", | |
| "display_name": "Python (Pyodide)", | |
| "language": "python", | |
| } | |
| meta.setdefault("language_info", {}) | |
| meta["language_info"]["name"] = "python" | |
| for cell in nb.get("cells", []): | |
| if cell.get("cell_type") != "code": | |
| continue | |
| source = cell.get("source", []) | |
| if isinstance(source, str): | |
| lines = source.splitlines(keepends=True) | |
| else: | |
| lines = source | |
| new_lines = [] | |
| changed = False | |
| for line in lines: | |
| if line.strip() in ("%matplotlib ipympl", "%matplotlib widget"): | |
| newline = "\n" if line.endswith("\n") else "" | |
| new_lines.append(f"%matplotlib inline{newline}") | |
| changed = True | |
| else: | |
| new_lines.append(line) | |
| if changed: | |
| cell["source"] = ( | |
| new_lines | |
| if not isinstance(source, str) | |
| else "".join(new_lines) | |
| ) | |
| with nb_path.open("w", encoding="utf-8") as f: | |
| json.dump(nb, f, indent=1) | |
| f.write("\n") | |
| PY | |
| - name: Build JupyterLite site | |
| # jupyterlite-pyodide-kernel is pinned deliberately -- NOT for wasm-ABI | |
| # matching (MVTB is pure Python, unlike RTB's compiled-extension pin of | |
| # the same version, for a different reason). Newer kernel releases bundle | |
| # Pyodide >=0.27.7, which turned WebAssembly JSPI ("stack switching") on | |
| # by default for run_until_complete(). Browsers without JSPI support | |
| # (Safari has none as of 2026-08; Firefox only behind a flag) either | |
| # crash the kernel outright or, with the enableRunUntilComplete: false | |
| # workaround, turn run_until_complete into a fire-and-forget no-op -- | |
| # letting a cell's package/import machinery run before an install has | |
| # actually finished landing (confirmed live on bdsim: intermittent | |
| # ModuleNotFoundError despite a successful-looking install cell). | |
| # jupyterlite-pyodide-kernel==0.6.1 bundles Pyodide 0.27.6, genuinely | |
| # predating this whole mechanism -- matches RTB's and bdsim's existing | |
| # pins. Installing jupyterlite-core directly rather than the jupyterlite | |
| # metapackage: the metapackage requires jupyterlite-core>=0.8.1, which | |
| # conflicts with this pin's jupyterlite-core<0.7.0 requirement. | |
| # Once Safari (and Firefox out-of-flag) ship JSPI, revisit this pin -- | |
| # and docs/lite/wasm_check/package.json's matching pyodide npm pin. | |
| run: | | |
| pip install jupyterlite-core "jupyterlite-pyodide-kernel==0.6.1" jupyter-server | |
| cd docs/lite && jupyter lite build --output-dir ../build/html/lite | |
| - name: Package notebooks zip | |
| run: | | |
| rm -f docs/build/html/mvtb_notebooks.zip | |
| zip -r docs/build/html/mvtb_notebooks.zip docs/notebooks | |
| - name: Upload notebooks zip artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: mvtb_notebooks | |
| path: docs/build/html/mvtb_notebooks.zip | |
| if-no-files-found: error | |
| # push and workflow_dispatch both publish; pull_request only builds | |
| # (to catch breakage in review), it never gets an artifact to deploy. | |
| - uses: actions/upload-pages-artifact@v5 | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' | |
| with: | |
| path: docs/build/html | |
| deploy: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| # ref check (not just event_name) so a manual dispatch from a | |
| # non-main branch builds but doesn't publish -- push is already | |
| # restricted to main via the `branches: [main]` filter above, but | |
| # workflow_dispatch can be run against any branch. | |
| if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v5 |