diff --git a/.github/workflows/docs-gh-pages.yml b/.github/workflows/docs-gh-pages.yml index 435af957..68767dd5 100644 --- a/.github/workflows/docs-gh-pages.yml +++ b/.github/workflows/docs-gh-pages.yml @@ -1,40 +1,89 @@ -name: Build and Deploy Documentation +name: Docs — Build & Preview +permissions: + contents: read on: push: - branches: - - main + branches: [ main ] # regular prod deploy + paths: + - 'mkdocs.yml' + - 'docs/**' + pull_request: # preview only when docs are touched + branches: [ '**' ] + paths: + - 'mkdocs.yml' + - 'docs/**' jobs: - build-and-deploy-docs: + build: runs-on: ubuntu-latest + permissions: + contents: read + actions: write steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Fetch all history for .git-restore-mtime to work correctly + with: { fetch-depth: 0 } - - name: Set up Python - uses: actions/setup-python@v5 + - name: Set up Python 3.13 + uses: actions/setup-python@v6 with: - python-version: '3.x' + python-version: "3.13" + allow-prereleases: true + cache: "pip" - - name: Install uv via GitHub Action - uses: astral-sh/setup-uv@v6 + - uses: astral-sh/setup-uv@v7 - - name: Install mesa-frames + docs dependencies + - name: Install mesa-frames + docs deps run: | uv pip install --system . uv pip install --group docs --system - - name: Build MkDocs site (general documentation) - run: mkdocs build --config-file mkdocs.yml --site-dir ./site + - name: Convert jupytext .py notebooks to .ipynb + run: | + set -euxo pipefail + # Convert any jupytext .py files to .ipynb without executing them. + # Enable nullglob so the pattern expands to empty when there are no matches + # and globstar so we recurse into subdirectories (e.g., user-guide/). + shopt -s nullglob globstar || true + files=(docs/general/**/*.py) + if [ ${#files[@]} -eq 0 ]; then + echo "No jupytext .py files found under docs/general" + else + for src in "${files[@]}"; do + [ -e "$src" ] || continue + dest="${src%.py}.ipynb" + echo "Converting $src -> $dest" + # jupytext will write the .ipynb alongside the source file + uv run jupytext --to notebook "$src" + done + fi + + - name: Build MkDocs site + run: uv run mkdocs build --config-file mkdocs.yml --site-dir ./site - - name: Build Sphinx docs (API documentation) - run: sphinx-build -b html docs/api site/api + - name: Build Sphinx docs (API) + run: uv run sphinx-build -b html docs/api site/api - - name: Deploy to GitHub Pages + - name: Upload site artifact + uses: actions/upload-artifact@v4 + with: + name: site + path: site + + deploy-main: + needs: build + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + permissions: + contents: write + pages: write + steps: + - uses: actions/download-artifact@v4 + with: { name: site, path: site } + - name: Deploy to GitHub Pages (main) uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages publish_dir: ./site - force_orphan: true \ No newline at end of file + force_orphan: true diff --git a/.github/workflows/docs-preview-dispatch.yml b/.github/workflows/docs-preview-dispatch.yml new file mode 100644 index 00000000..3cc22b92 --- /dev/null +++ b/.github/workflows/docs-preview-dispatch.yml @@ -0,0 +1,67 @@ +# Deploys preview artifacts produced by the Docs — Build & Preview workflow for PRs. +# The build workflow runs with read-only permissions on PRs (including forks) and +# uploads the built site as an artifact. This workflow runs in the base repo with +# the permissions needed to publish the preview to the gh-pages branch, but it +# never checks out or executes untrusted PR code. +name: Docs — Preview Deploy + +on: + workflow_run: + workflows: ["Docs — Build & Preview"] + types: [completed] + +permissions: + contents: write + actions: read + pages: write + +jobs: + deploy-preview: + if: > + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' && + (github.event.workflow_run.pull_requests || null) + runs-on: ubuntu-latest + steps: + - name: Download built site artifact + uses: actions/download-artifact@v4 + with: + name: site + path: site + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Derive preview metadata + id: meta + env: + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} + PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0].number }} + run: | + set -euo pipefail + short_sha="$(printf '%s' "$HEAD_SHA" | cut -c1-7)" + { + echo "short_sha=$short_sha" + echo "head_branch=$HEAD_BRANCH" + echo "pr_number=$PR_NUMBER" + } >> "$GITHUB_OUTPUT" + + - name: Deploy preview under subfolder + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_branch: gh-pages + publish_dir: ./site + destination_dir: preview/${{ steps.meta.outputs.head_branch }}/${{ steps.meta.outputs.short_sha }} + keep_files: true + + - name: Print preview URL + env: + PREVIEW_OWNER: ${{ github.repository_owner }} + PREVIEW_REPO: ${{ github.repository }} + HEAD_BRANCH: ${{ steps.meta.outputs.head_branch }} + SHORT_SHA: ${{ steps.meta.outputs.short_sha }} + PR_NUMBER: ${{ steps.meta.outputs.pr_number }} + run: | + echo "Preview for PR #${PR_NUMBER}:" + echo "https://${PREVIEW_OWNER}.github.io/$(basename "$PREVIEW_REPO")/preview/${HEAD_BRANCH}/${SHORT_SHA}/" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 105824de..a2070903 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,33 +17,27 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.VERSION_PUSH_TOKEN }} - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install hatch + - name: Setup uv + uses: astral-sh/setup-uv@v3 - name: Set release version run: | # Get the tag from the GitHub release TAG=${GITHUB_REF#refs/tags/} # Remove 'v' prefix if present VERSION=${TAG#v} - hatch version $VERSION + uvx hatch version $VERSION - name: Build package - run: hatch build + run: uvx hatch build - name: Run tests - run: hatch run test:pytest + run: uvx hatch run test:pytest - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@release/v1 - name: Verify PyPI Release run: | # Verify PyPI release PACKAGE_NAME="mesa_frames" - CURRENT_VERSION=$(hatch version) - pip install $PACKAGE_NAME==$CURRENT_VERSION + CURRENT_VERSION=$(uvx hatch version) + uv pip install --system $PACKAGE_NAME==$CURRENT_VERSION python -c "import mesa_frames; print(mesa_frames.__version__)" - name: Update GitHub Release uses: softprops/action-gh-release@v1 @@ -51,9 +45,52 @@ jobs: with: files: | dist/* + - name: Generate changelog from release notes + id: notes + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const tag = (context.payload.release && context.payload.release.tag_name) + ? context.payload.release.tag_name + : (process.env.GITHUB_REF || '').replace('refs/tags/', ''); + + const body = (context.payload.release && context.payload.release.body) ? context.payload.release.body : ''; + if (!body || body.trim().length === 0) { + core.setFailed('Release body is empty. Ensure the GitHub Release is created with auto-generated notes configured by .github/release.yml or supply a body.'); + } + + fs.writeFileSync('RELEASE_BODY.md', body, 'utf8'); + core.setOutput('tag', tag); + - name: Prepend notes to CHANGELOG.md + env: + TAG: ${{ steps.notes.outputs.tag }} + run: | + VERSION_NO_V=${TAG#v} + DATE_UTC=$(date -u +%Y-%m-%d) + echo "## Version ${VERSION_NO_V} — ${DATE_UTC}" > RELEASE_HEADER.md + echo "" >> RELEASE_HEADER.md + if [ -f CHANGELOG.md ]; then + cat RELEASE_HEADER.md RELEASE_BODY.md CHANGELOG.md > CHANGELOG.new + else + cat RELEASE_HEADER.md RELEASE_BODY.md > CHANGELOG.new + fi + mv CHANGELOG.new CHANGELOG.md + - name: Commit and push CHANGELOG update + env: + TAG: ${{ steps.notes.outputs.tag }} + run: | + git config user.name github-actions + git config user.email github-actions@github.com + # Ensure we are on the main branch before committing so the push lands on main. + git checkout main + git add CHANGELOG.md + # Avoid CI cycles + git commit -m "Changelog: add notes for ${TAG} [skip ci]" || echo "No changelog changes to commit" + git push origin main || true - name: Create or recreate version branch run: | - CURRENT_VERSION=$(hatch version) + CURRENT_VERSION=$(uvx hatch version) BRANCH_NAME="v$CURRENT_VERSION" git config user.name github-actions @@ -72,15 +109,15 @@ jobs: - name: Update to Next Version run: | # Bump to next development version - hatch version patch - hatch version dev + uvx hatch version patch + uvx hatch version dev # Get the new version - NEW_VERSION=$(hatch version) + NEW_VERSION=$(uvx hatch version) # Commit and push the version bump git config user.name github-actions git config user.email github-actions@github.com - git add mesa_frames/__init__.py + git add mesa_frames/__init__.py CHANGELOG.md git commit -m "Bump version to $NEW_VERSION [skip ci]" - git push origin main \ No newline at end of file + git push origin main