diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dfa8d14f..ae0a4d3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,13 +43,13 @@ jobs: contents: read steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.13" - - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true cache-dependency-glob: | @@ -71,13 +71,13 @@ jobs: contents: read steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.13" - - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true cache-dependency-glob: | @@ -99,13 +99,13 @@ jobs: contents: read steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.13" - - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: enable-cache: true cache-dependency-glob: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 275b7e0c..8a8bc024 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Initialize CodeQL uses: github/codeql-action/init@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 660c1684..622c5afd 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,66 +2,30 @@ # .github/workflows/docs.yml — Manual Documentation Rebuild # ============================================================================= # Use workflow_dispatch to manually rebuild docs without a release. -# Normal doc deployment happens automatically inside release.yml. +# Normal doc deployment happens automatically via release.yml; both delegate +# the actual build to the reusable quarto-build.yml workflow. +# Set deploy=false for a render-only dry run (e.g. to verify doc changes on +# a branch without touching the production gh-pages site). # ============================================================================= name: Documentation on: workflow_dispatch: + inputs: + deploy: + description: "Publish the rendered site to gh-pages" + type: boolean + default: true permissions: read-all -concurrency: - group: docs-deploy - cancel-in-progress: false - -env: - QUARTO_VERSION: "1.8.27" - PYTHON_VERSION: "3.13" - jobs: - deploy-docs: - name: Build & Deploy Documentation - runs-on: ubuntu-latest + docs: + name: Documentation + uses: ./.github/workflows/quarto-build.yml + with: + deploy: ${{ inputs.deploy }} permissions: contents: write - - steps: - - name: Checkout (full history) - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - with: - fetch-depth: 0 - - - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - - - name: Set up uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 - - - name: Install dependencies - run: uv pip install --system -e ".[dev,docs]" - - - name: Install Quarto CLI ${{ env.QUARTO_VERSION }} - uses: quarto-dev/quarto-actions/setup@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 - with: - version: ${{ env.QUARTO_VERSION }} - - - name: Generate API reference with quartodoc - run: | - uv run python docs/quartodoc_build.py - uv run quartodoc interlinks - - - name: Render Quarto site - run: uv run quarto render - - - name: Deploy to GitHub Pages - uses: quarto-dev/quarto-actions/publish@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 - with: - target: gh-pages - path: _site - render: "false" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + secrets: inherit diff --git a/.github/workflows/quarto-build.yml b/.github/workflows/quarto-build.yml new file mode 100644 index 00000000..d2763017 --- /dev/null +++ b/.github/workflows/quarto-build.yml @@ -0,0 +1,96 @@ +# ============================================================================= +# .github/workflows/quarto-build.yml — Reusable Documentation Build +# ============================================================================= +# Single source of truth for the quartodoc + Quarto render + gh-pages deploy +# sequence. Called via workflow_call from: +# - release.yml (docs job, after every release on main) +# - docs.yml (manual rebuild via workflow_dispatch) +# The `deploy` input allows render-only dry runs (CI verification of doc +# changes on a branch) without publishing to the production gh-pages site. +# ============================================================================= + +name: Quarto Build + +on: + workflow_call: + inputs: + deploy: + description: "Publish the rendered site to gh-pages (false = render-only dry run)" + type: boolean + default: true + ref: + description: "Git ref to build from (empty = the triggering commit)" + type: string + default: "" + +# NOTE: no workflow-level `permissions:` here. A called workflow may not +# request more than its caller's job grants; `read-all` would demand read on +# every scope while callers grant only `contents: write` (startup_failure). +# The job below declares exactly what it needs. + +env: + QUARTO_VERSION: "1.8.27" + PYTHON_VERSION: "3.13" + +jobs: + build-docs: + name: Build & Deploy Documentation + runs-on: ubuntu-latest + permissions: + contents: write + # Serialize every gh-pages push across ALL callers (release.yml and + # docs.yml). With per-workflow groups the two workflows can race on the + # gh-pages ref and the loser fails with "cannot lock ref". + concurrency: + group: gh-pages-deploy + cancel-in-progress: false + + steps: + - name: Checkout (full history) + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + ref: ${{ inputs.ref }} + + - name: Set up Python ${{ env.PYTHON_VERSION }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + + # setup-uv >= v6 enables caching by default on GitHub-hosted runners; + # key on the committed lockfile + pyproject so torch & friends are + # restored instead of re-downloaded. + - name: Set up uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + cache-dependency-glob: | + uv.lock + pyproject.toml + + - name: Install dependencies + run: uv sync --extra dev --extra docs + + - name: Install Quarto CLI ${{ env.QUARTO_VERSION }} + uses: quarto-dev/quarto-actions/setup@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 + with: + version: ${{ env.QUARTO_VERSION }} + + - name: Generate API reference with quartodoc + run: | + uv run python docs/quartodoc_build.py + uv run quartodoc interlinks + + # `freeze: auto` + the committed _freeze/ directory means unchanged + # {python} blocks reuse stored outputs instead of re-executing. + - name: Render Quarto site + run: uv run quarto render + + - name: Deploy to GitHub Pages + if: inputs.deploy + uses: quarto-dev/quarto-actions/publish@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 + with: + target: gh-pages + path: _site + render: "false" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61d53a01..433863d5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,10 +3,13 @@ # ============================================================================= # Triggers on every push to main. Tests are NOT run here — they are gated on # PRs by ci.yml (the required fast check) and nightly (full suite). -# 1. Semantic-release checks conventional commits — if a releasable change -# is found (feat:, fix:, etc.) it bumps version, publishes to PyPI, -# and creates a GitHub release. -# 2. Builds and deploys quartodoc documentation to GitHub Pages. +# 1. release job: semantic-release checks conventional commits — if a +# releasable change is found (feat:, fix:, etc.) it bumps version, +# publishes to PyPI, and creates a GitHub release. +# 2. docs job: delegates to the reusable quarto-build.yml workflow, which +# builds and deploys the quartodoc documentation to GitHub Pages +# (serialized against manual docs.yml runs via the shared +# gh-pages-deploy concurrency group). # ============================================================================= name: Release @@ -34,31 +37,30 @@ jobs: steps: # ── Checkout ────────────────────────────────────────────────────── - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # ── Python + uv ────────────────────────────────────────────────── - - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.13" - - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: - enable-cache: true cache-dependency-glob: | uv.lock pyproject.toml - name: Install dependencies - run: uv sync --extra dev --extra docs + run: uv sync --extra dev # ── Semantic Release ────────────────────────────────────────────── # NOTE: tests are intentionally NOT run here. The fast suite is the # required check on every PR to main (ci.yml) and the full suite runs # nightly, so re-running them on the merge commit only duplicated cost # without adding signal. This pipeline publishes; it does not re-test. - - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 @@ -87,25 +89,18 @@ jobs: packages-dir: dist/ skip-existing: true - # ── Documentation ───────────────────────────────────────────────── - - name: Install Quarto CLI - uses: quarto-dev/quarto-actions/setup@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 - with: - version: "1.8.27" - - - name: Generate API reference - run: | - uv run --extra docs python docs/quartodoc_build.py - uv run --extra docs quartodoc interlinks - - - name: Render documentation - run: uv run --extra docs quarto render - - - name: Deploy to GitHub Pages - uses: quarto-dev/quarto-actions/publish@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 - with: - target: gh-pages - path: _site - render: "false" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ── Documentation (reusable workflow, shared with docs.yml) ─────────── + # Builds from `main` rather than the triggering commit: by the time this + # job starts, semantic-release has already pushed its "chore(release)" + # version-bump commit, so the rendered docs show the just-released version + # (same behavior as the old in-place render after the bump). + docs: + name: Documentation + needs: release + uses: ./.github/workflows/quarto-build.yml + with: + deploy: true + ref: main + permissions: + contents: write + secrets: inherit diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 30b3ebd7..6db6aacd 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -4,12 +4,13 @@ name: Scorecard supply-chain security +# Weekly schedule + branch-protection changes only. The previous push trigger +# re-ran the full supply-chain scan on every merge to main, which added CI +# minutes without changing the score between weekly runs. on: branch_protection_rule: schedule: - cron: '20 7 * * 2' - push: - branches: ["main"] # Declare default permissions as read only. permissions: @@ -40,13 +41,13 @@ jobs: publish_results: true - name: Upload artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: SARIF file path: results.sarif retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.5 + uses: github/codeql-action/upload-sarif@820e3160e279568db735cee8ed8f8e77a6da7818 # v3.32.6 with: sarif_file: results.sarif diff --git a/uv.lock b/uv.lock index da9e5e45..61052f15 100644 --- a/uv.lock +++ b/uv.lock @@ -2926,7 +2926,7 @@ wheels = [ [[package]] name = "spotoptim" -version = "0.12.4" +version = "0.12.5" source = { editable = "." } dependencies = [ { name = "black" },