From 8fa065a79f48c54d0cd9a228023569ac2810c545 Mon Sep 17 00:00:00 2001 From: bartzbeielstein <32470350+bartzbeielstein@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:41:46 +0200 Subject: [PATCH] fix: via claude --- .github/workflows/ci.yml | 125 +++++++++------------------------ .github/workflows/docs.yml | 54 ++++----------- .github/workflows/release.yml | 127 ++++++++++++++-------------------- 3 files changed, 96 insertions(+), 210 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3202216..fc813cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,47 +1,30 @@ -name: CI Tests +# ============================================================================= +# .github/workflows/ci.yml — Continuous Integration +# ============================================================================= +# Runs on every push and PR to main/develop. +# Jobs: tests (pytest + coverage), code quality (black, isort, ruff). +# ============================================================================= + +name: CI on: push: - branches: - - main - - develop + branches: [main, develop] pull_request: - branches: - - main - - develop + branches: [main, develop] -# Principle of Least Privilege: Base permissions are read-only permissions: read-all -# Cancel stale runs when a new push arrives on the same branch or PR. concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - reuse: - name: REUSE Compliance - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up uv - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5.2.2 - - - name: Run reuse lint - run: | - uv tool run reuse lint - test: - name: Test on Python ${{ matrix.python-version }} + name: Tests (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest permissions: contents: read - id-token: write strategy: fail-fast: false @@ -50,31 +33,30 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - fetch-depth: 0 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Set up uv - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5.2.2 + uses: astral-sh/setup-uv@v5 - name: Install dependencies - run: | - uv pip install --system -e ".[dev]" + run: uv pip install --system -e ".[dev]" - name: Run pytest run: | - uv run pytest tests/ -v -n auto --tb=short --cov=src/spotoptim --cov-branch --cov-report=xml --cov-report=term + uv run pytest tests/ -v -n auto --tb=short \ + --cov=src/spotoptim --cov-branch \ + --cov-report=xml --cov-report=term - - name: Upload results to Codecov - uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3 + - name: Upload coverage to Codecov + if: always() + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - slug: sequential-parameter-optimization/spotoptim files: ./coverage.xml fail_ci_if_error: false @@ -84,72 +66,27 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Set up uv - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5.2.2 + uses: astral-sh/setup-uv@v5 - name: Install dependencies - run: | - uv pip install --system -e ".[dev]" + run: uv pip install --system -e ".[dev]" - - name: Check formatting with black - run: | - black --check src/ tests/ + - name: Check formatting (black) + run: black --check src/ tests/ continue-on-error: true - - name: Check imports with isort - run: | - isort --check-only src/ tests/ + - name: Check imports (isort) + run: isort --check-only src/ tests/ continue-on-error: true - - name: Lint with ruff - run: | - ruff check src/ tests/ - continue-on-error: true - - security: - name: Security Scan - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - - - name: Set up uv - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5.2.2 - - - name: Install dependencies - run: | - uv pip install --system -e ".[dev]" - - - name: Check dependencies with safety - run: | - safety check --json - continue-on-error: true - - - name: Security scan with bandit - run: | - bandit -r src/spotoptim/ -f sarif -o bandit-report.sarif - continue-on-error: true - - - name: Upload bandit report to security tab - if: always() - uses: github/codeql-action/upload-sarif@c6f931105cb2c34c8f901cc885ba1e2e259cf745 # v4.34.0 - with: - sarif_file: bandit-report.sarif - category: bandit + - name: Lint (ruff) + run: ruff check src/ tests/ continue-on-error: true diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 7e5e175..a576764 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,42 +1,22 @@ -# SPDX-FileCopyrightText: 2026 bartzbeielstein -# -# SPDX-License-Identifier: GPL-2.0-or-later -# # ============================================================================= -# .github/workflows/docs.yml — Documentation Build & Deploy +# .github/workflows/docs.yml — Manual Documentation Rebuild # ============================================================================= -# Pipeline: -# 1. Checkout -# 2. Set up Python + uv -# 3. Install Python dependencies -# 4. Install Quarto CLI (PINNED) -# 5. Generate API reference with quartodoc -# 6. Render Quarto site & publish to rh-pages +# Use workflow_dispatch to manually rebuild docs without a release. +# Normal doc deployment happens automatically inside release.yml. # ============================================================================= name: Documentation on: - push: - branches: - - main # Production docs only; develop pushes are not deployed. - paths: - - '**/*.qmd' - - '_quarto.yml' - - 'src/spotoptim/**' - - 'pyproject.toml' workflow_dispatch: permissions: read-all -# Prevent concurrent deployments from overwriting each other on gh-pages. -# A docs build can take several minutes; let it finish rather than cancel. concurrency: group: docs-deploy cancel-in-progress: false env: - # ⚠️ Pinned to match production safety standards. QUARTO_VERSION: "1.8.27" PYTHON_VERSION: "3.13" @@ -45,51 +25,43 @@ jobs: name: Build & Deploy Documentation runs-on: ubuntu-latest permissions: - contents: write # push access via gh-pages + contents: write steps: - name: Checkout (full history) - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2 + uses: actions/checkout@v4 with: - fetch-depth: 0 # Full history ensures cross-referencing interlinks work safely + fetch-depth: 0 - name: Set up Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5.3.0 + uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} - name: Set up uv - uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5.2.2 + uses: astral-sh/setup-uv@v5 - - name: Install Python dependencies - run: | - # Use pyproject.toml [dev] and docs extras to install quartodoc safely - uv sync --group dev --extra docs + - 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.1.2 + uses: quarto-dev/quarto-actions/setup@v2 with: version: ${{ env.QUARTO_VERSION }} - - name: Verify Quarto installation - run: quarto check - - name: Generate API reference with quartodoc - # Rebuild dynamically instead of keeping autogenerated `.qmd` tracked into git run: | uv run python docs/quartodoc_build.py uv run quartodoc interlinks - - - name: Render Quarto site run: uv run quarto render --no-cache - name: Deploy to GitHub Pages - uses: quarto-dev/quarto-actions/publish@8a96df13519ee81fd526f2dfca5962811136661b # v2.1.2 + uses: quarto-dev/quarto-actions/publish@v2 with: target: gh-pages path: _site - render: "false" # Handled in prior step + render: "false" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 272f081..5eeeff9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,15 +1,21 @@ +# ============================================================================= +# .github/workflows/release.yml — Semantic Release + PyPI + Docs +# ============================================================================= +# Triggers on push to main (i.e. when a PR from develop is merged). +# Semantic-release analyses conventional commits and, if a releasable +# change is found (feat:, fix:, etc.), bumps the version, publishes to +# PyPI, deploys documentation, and creates a back-merge PR. +# ============================================================================= + name: Release on: push: branches: - main - - develop -# Principle of Least Privilege: Base permissions are read-only permissions: read-all -# Serialize releases per-branch; never cancel an in-progress release. concurrency: group: release-${{ github.ref }} cancel-in-progress: false @@ -22,52 +28,62 @@ jobs: contents: write issues: write pull-requests: write - id-token: write + id-token: write # needed for PyPI OIDC trusted publishers if: "!contains(github.event.head_commit.message, 'chore(release)')" steps: + # ------------------------------------------------------------------ + # 1. Checkout — keep credentials so semantic-release can push tags + # ------------------------------------------------------------------ - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@v4 with: fetch-depth: 0 - persist-credentials: false + # persist-credentials defaults to true; GITHUB_TOKEN is used for + # git push operations by semantic-release. + # ------------------------------------------------------------------ + # 2. Python + uv + # ------------------------------------------------------------------ - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Set up uv - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | uv pip install --system build uv pip install --system -e ".[dev,docs]" + # ------------------------------------------------------------------ + # 3. Tests — gate the release on passing tests + # ------------------------------------------------------------------ - name: Run tests - run: | - uv run pytest tests/ -v -n auto - - - name: Verify package name in pyproject.toml - run: | - grep -q "name = \"spotoptim\"" pyproject.toml || (echo "ERROR: Package name mismatch in pyproject.toml" && exit 1) - echo "✓ Package name verified: spotoptim" + run: uv run pytest tests/ -v -n auto --tb=short + # ------------------------------------------------------------------ + # 4. Build distribution + # ------------------------------------------------------------------ - name: Build distribution packages run: | rm -rf dist/ build/ uv build - echo "Build artifacts created:" + echo "Build artifacts:" ls -lah dist/ + # ------------------------------------------------------------------ + # 5. Semantic Release (Node.js) + # ------------------------------------------------------------------ - name: Setup Node.js - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + uses: actions/setup-node@v4 with: node-version: 20 - name: Semantic Release - uses: cycjimmy/semantic-release-action@b12c8f6015dc215fe37bc154d4ad456dd3833c90 # v6.0.0 + uses: cycjimmy/semantic-release-action@v4 with: extra_plugins: | @semantic-release/git@10 @@ -75,21 +91,26 @@ jobs: @semantic-release/exec@6 conventional-changelog-conventionalcommits@7 env: - GITHUB_TOKEN: ${{ secrets.SEMANTIC_RELEASE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ------------------------------------------------------------------ + # 6. Publish to PyPI (OIDC trusted publisher — no token needed) + # ------------------------------------------------------------------ - name: Publish to PyPI if: success() - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # release/v1 + uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ skip-existing: true - # Note: No token needed with Trusted Publishers (OIDC) + # ------------------------------------------------------------------ + # 7. Documentation — quartodoc + Quarto → GitHub Pages + # ------------------------------------------------------------------ - name: Install Quarto CLI if: success() - uses: quarto-dev/quarto-actions/setup@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 + uses: quarto-dev/quarto-actions/setup@v2 with: - version: "1.7.29" + version: "1.8.27" - name: Generate API reference stubs if: success() @@ -99,19 +120,11 @@ jobs: - name: Build documentation if: success() - run: | - uv run quarto render --no-cache + run: uv run quarto render --no-cache - - name: Setup Git Credentials for Publish + - name: Deploy documentation to GitHub Pages if: success() - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git - - - name: Deploy documentation - if: success() - uses: quarto-dev/quarto-actions/publish@8a96df13519ee81fd526f2dfca5962811136661b # v2.2.0 + uses: quarto-dev/quarto-actions/publish@v2 with: target: gh-pages path: _site @@ -119,48 +132,23 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Verify PyPI Release - if: success() - run: | - echo "Release completed successfully!" - echo "Verifying package on PyPI..." - for i in {1..30}; do - if python -c "import urllib.request; urllib.request.urlopen('https://pypi.org/project/spotoptim/')" 2>/dev/null; then - echo "✓ Package found on PyPI" - break - fi - if [ $i -lt 30 ]; then - echo "Waiting for PyPI to sync (attempt $i/30)..." - sleep 10 - fi - done - # --------------------------------------------------------------------------- # Back-merge: keep develop in sync after every main release # --------------------------------------------------------------------------- - # Why: semantic-release commits CHANGELOG.md + pyproject.toml back to main. - # Without this job, develop diverges and the next develop→main PR conflicts. - # Strategy: use the built-in GITHUB_TOKEN (no extra secret needed) to open a - # PR via the pre-installed `gh` CLI. If branch-protection requires a review - # the PR waits; otherwise it can be auto-merged by the repo owner. The step - # is idempotent: if main is already merged into develop, or a PR already - # exists, it exits gracefully. back-merge: name: Back-merge main → develop runs-on: ubuntu-latest needs: release - # Only needed after production releases on main, not for rc on develop. if: github.ref == 'refs/heads/main' permissions: - contents: write # required to push/read branches - pull-requests: write # required to open the PR + contents: write + pull-requests: write steps: - name: Checkout (full history) - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@v4 with: fetch-depth: 0 - # Use the default GITHUB_TOKEN; gh CLI picks it up via GH_TOKEN below. - name: Check whether a back-merge is needed id: sync-check @@ -177,7 +165,6 @@ jobs: run: | VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/') - # Idempotency: skip if an open PR already targets develop from main. EXISTING=$(gh pr list \ --base develop --head main \ --state open \ @@ -185,7 +172,7 @@ jobs: --jq '.[0].number' 2>/dev/null || true) if [ -n "$EXISTING" ]; then - echo "Back-merge PR #${EXISTING} already open. Nothing to do." + echo "Back-merge PR #${EXISTING} already open — nothing to do." exit 0 fi @@ -193,15 +180,5 @@ jobs: --base develop \ --head main \ --title "chore: back-merge release ${VERSION} (main → develop)" \ - --body "$(cat <<'EOF' - Automated back-merge created by the Release workflow. - - Release ${VERSION} committed updated files (CHANGELOG.md, pyproject.toml) - back to main. This PR syncs those changes into develop so the next - develop → main pull request has no conflicts. - - Merge this PR as soon as convenient. - EOF - )" - + --body "Automated back-merge. Release ${VERSION} committed updated files (CHANGELOG.md, pyproject.toml) to main. This PR syncs those changes into develop." echo "✓ Back-merge PR created for release ${VERSION}"