Skip to content

Bump the backend-python-runtime group in /backend with 6 updates #535

Bump the backend-python-runtime group in /backend with 6 updates

Bump the backend-python-runtime group in /backend with 6 updates #535

Workflow file for this run

name: "CI: Test"
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
# ──────────────────────────────────────────────
# Detect which components changed
# ──────────────────────────────────────────────
changes:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
companion: ${{ steps.filter.outputs.companion }}
website: ${{ steps.filter.outputs.website }}
steps:
- name: Detect changed paths
id: filter
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
python3 <<'PY'
import json
import os
import sys
import urllib.request
output_path = os.environ["GITHUB_OUTPUT"]
backend_extras = {
"scripts/install-system-deps",
"scripts/setup-test-images",
}
frontend_extras = {
"VERSION",
"scripts/sync-version",
}
companion_extras = {
"VERSION",
"scripts/sync-version",
}
website_extras = {
"VERSION",
}
def write_output(name: str, value: bool) -> None:
with open(output_path, "a", encoding="utf-8") as handle:
handle.write(f"{name}={'true' if value else 'false'}\n")
event_name = os.environ["GITHUB_EVENT_NAME"]
if event_name == "workflow_dispatch":
for component in ("backend", "frontend", "companion"):
write_output(component, True)
write_output("website", True)
sys.exit(0)
with open(os.environ["GITHUB_EVENT_PATH"], encoding="utf-8") as handle:
event = json.load(handle)
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {os.environ['GITHUB_TOKEN']}",
"User-Agent": "sambee-ci-change-detector",
"X-GitHub-Api-Version": "2022-11-28",
}
api_url = os.environ.get("GITHUB_API_URL", "https://api.github.com")
repo = os.environ["GITHUB_REPOSITORY"]
filenames: list[str] = []
if event_name == "pull_request":
pull_request = event.get("pull_request")
if not pull_request:
raise SystemExit("pull_request event payload is missing pull_request data")
page = 1
while True:
request = urllib.request.Request(
f"{api_url}/repos/{repo}/pulls/{pull_request['number']}/files?per_page=100&page={page}",
headers=headers,
)
with urllib.request.urlopen(request) as response:
files = json.load(response)
if not files:
break
filenames.extend(file["filename"] for file in files)
if len(files) < 100:
break
page += 1
elif event_name == "push":
before = event.get("before")
after = os.environ["GITHUB_SHA"]
if not before or set(before) == {"0"}:
for component in ("backend", "frontend", "companion"):
write_output(component, True)
write_output("website", True)
print("Push event is missing a valid 'before' SHA; running all components")
sys.exit(0)
request = urllib.request.Request(
f"{api_url}/repos/{repo}/compare/{before}...{after}",
headers=headers,
)
with urllib.request.urlopen(request) as response:
compare = json.load(response)
filenames.extend(file["filename"] for file in compare.get("files", []))
else:
raise SystemExit(f"Unsupported event for change detection: {event_name}")
backend = any(name.startswith("backend/") or name in backend_extras for name in filenames)
frontend = any(name.startswith("frontend/") or name in frontend_extras for name in filenames)
companion = any(name.startswith("companion/") or name in companion_extras for name in filenames)
website = any(
name.startswith("website/")
or name.startswith("website-meta/")
or name in website_extras
for name in filenames
)
print(f"Detected {len(filenames)} changed files")
print(f"backend={backend} frontend={frontend} companion={companion} website={website}")
write_output("backend", backend)
write_output("frontend", frontend)
write_output("companion", companion)
write_output("website", website)
PY
# ──────────────────────────────────────────────
# Website docs: validator + standalone tests
# ──────────────────────────────────────────────
website-docs:
needs: changes
if: needs.changes.outputs.website == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Validate docs tree
run: python3 website/scripts/validate-docs-content.py
- name: Check committed docs structure report
run: python3 website/scripts/docs-report.py --check
- name: Run standalone docs validator tests
run: python3 -m unittest discover -s website/tests -p 'test_validate_docs_content.py'
- name: Run standalone docs report tests
run: python3 -m unittest discover -s website/tests -p 'test_docs_report.py'
# ──────────────────────────────────────────────
# Backend: Python + mypy + pytest
# ──────────────────────────────────────────────
backend:
needs: changes
if: needs.changes.outputs.backend == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
# Keep the backend test job on the same Debian family as production and the
# devcontainer because install-system-deps requires distro-provided
# ImageMagick 7, which Ubuntu's default runner image does not provide.
container:
image: python:3.13.12-slim@sha256:f1927c75e81efd1e091dbd64b6c0ecaa5630b38635a3d1c04034ac636e1f94c8
defaults:
run:
shell: bash
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Cache Python virtual environment
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: python-cache
with:
path: backend/.venv
key: ${{ runner.os }}-python-3.13.12-slim-venv-${{ hashFiles('backend/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-python-3.13.12-slim-venv-
- name: Cache mypy
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
with:
path: backend/.mypy_cache
key: ${{ runner.os }}-mypy-${{ hashFiles('backend/app/**/*.py') }}
restore-keys: |
${{ runner.os }}-mypy-
- name: Install system dependencies (image processing)
run: bash scripts/install-system-deps
- name: Install Python dependencies
if: steps.python-cache.outputs.cache-hit != 'true'
run: |
cd backend
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install --require-hashes -r requirements-dev.lock.txt
- name: Generate test images
run: QUIET=1 scripts/setup-test-images
- name: Run mypy
run: |
cd backend
source .venv/bin/activate
mypy app
- name: Run pytest (excluding performance)
env:
SECRET_KEY: test-secret-key-for-ci-only-not-for-production-use
ENCRYPTION_KEY: YYFPojCh_1WUExv5xXVEyFe0ITw_5dgZZ-fC-iZk3nU=
run: |
cd backend
source .venv/bin/activate
pytest -n auto -m 'not performance' --cov=app --cov-report=term-missing --cov-report=xml
- name: Run performance pytest
env:
SECRET_KEY: test-secret-key-for-ci-only-not-for-production-use
ENCRYPTION_KEY: YYFPojCh_1WUExv5xXVEyFe0ITw_5dgZZ-fC-iZk3nU=
run: |
cd backend
source .venv/bin/activate
pytest -m performance --cov=app --cov-append --cov-report=term-missing --cov-report=xml
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coverage-reports
path: backend/coverage.xml
retention-days: 7
compression-level: 6
# ──────────────────────────────────────────────
# Frontend: Node.js + tsc + vitest
# ──────────────────────────────────────────────
frontend:
needs: changes
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Sync and verify version metadata
uses: ./.github/actions/sync-version-check
- name: Verify Tauri package version alignment
shell: bash
run: |
if command -v python3 >/dev/null 2>&1; then
python3 scripts/check_tauri_version_alignment.py
elif command -v python >/dev/null 2>&1; then
python scripts/check_tauri_version_alignment.py
else
py scripts/check_tauri_version_alignment.py
fi
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: cd frontend && npm ci --prefer-offline
- name: TypeScript type check
run: cd frontend && npx tsc --noEmit
- name: Run tests
run: cd frontend && npm test -- --run
# ──────────────────────────────────────────────
# Frontend E2E: Playwright Chromium smoke on PRs and pushes
# ──────────────────────────────────────────────
frontend-e2e:
needs: changes
if: needs.changes.outputs.frontend == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Sync and verify version metadata
uses: ./.github/actions/sync-version-check
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: cd frontend && npm ci --prefer-offline
- name: Install Playwright Chromium
run: cd frontend && npx playwright install --with-deps chromium
- name: Run Playwright Chromium smoke tests
run: cd frontend && npm run test:e2e:smoke
# ──────────────────────────────────────────────
# Frontend E2E: Playwright Firefox editor coverage on main/workflow dispatch
# ──────────────────────────────────────────────
frontend-e2e-firefox:
needs: changes
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && needs.changes.outputs.frontend == 'true')
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Sync and verify version metadata
uses: ./.github/actions/sync-version-check
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
run: cd frontend && npm ci --prefer-offline
- name: Install Playwright Firefox
run: cd frontend && npx playwright install --with-deps firefox
- name: Run Playwright Firefox markdown tests
run: cd frontend && npm run test:e2e:firefox
# ──────────────────────────────────────────────
# Companion: Node.js + Rust + Tauri tests
# ──────────────────────────────────────────────
companion:
needs: changes
if: needs.changes.outputs.companion == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Sync and verify version metadata
uses: ./.github/actions/sync-version-check
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: companion/package-lock.json
- name: Install Tauri system dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
libwebkit2gtk-4.1-dev \
libgtk-3-dev \
libayatana-appindicator3-dev \
librsvg2-dev \
libsoup-3.0-dev \
libjavascriptcoregtk-4.1-dev
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@baf1a810e98b6a3001d0d7234864ed75a17c42fb
with:
workspaces: companion/src-tauri -> target
- name: Install dependencies
run: cd companion && npm ci --prefer-offline
- name: TypeScript type check
run: cd companion && npx tsc --noEmit
- name: Run Rust tests
run: cd companion/src-tauri && cargo test
# ──────────────────────────────────────────────
# Gate: single required check for branch protection
# ──────────────────────────────────────────────
test-gate:
if: always()
needs: [backend, frontend, frontend-e2e, frontend-e2e-firefox, companion, website-docs]
runs-on: ubuntu-latest
steps:
- name: Check results
run: |
results="${{ needs.backend.result }} ${{ needs.frontend.result }} ${{ needs.frontend-e2e.result }} ${{ needs.frontend-e2e-firefox.result }} ${{ needs.companion.result }} ${{ needs.website-docs.result }}"
for r in $results; do
if [[ "$r" != "success" && "$r" != "skipped" ]]; then
echo "Job failed or was cancelled: $r"
exit 1
fi
done
echo "All tests passed or were skipped."