Skip to content

Bump the frontend-npm group across 1 directory with 21 updates #115

Bump the frontend-npm group across 1 directory with 21 updates

Bump the frontend-npm group across 1 directory with 21 updates #115

Workflow file for this run

name: 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 }}
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",
}
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)
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)
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)
print(f"Detected {len(filenames)} changed files")
print(f"backend={backend} frontend={frontend} companion={companion}")
write_output("backend", backend)
write_output("frontend", frontend)
write_output("companion", companion)
PY
# ──────────────────────────────────────────────
# Backend: Python + mypy + pytest
# ──────────────────────────────────────────────
backend:
needs: changes
if: needs.changes.outputs.backend == 'true' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
id: setup-python
with:
python-version: '3.13'
cache: 'pip'
cache-dependency-path: 'backend/requirements*.txt'
- name: Cache Python virtual environment
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
id: python-cache
with:
path: backend/.venv
key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-venv-${{ hashFiles('backend/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-venv-
- name: Cache mypy
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
with:
path: backend/.mypy_cache
key: ${{ runner.os }}-mypy-${{ hashFiles('backend/app/**/*.py') }}
restore-keys: |
${{ runner.os }}-mypy-
- name: Cache ImageMagick installation
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
id: imagemagick-cache
with:
path: |
/tmp/imagemagick-appimage
/opt/imagemagick
key: ${{ runner.os }}-imagemagick-7.1.2-7
- name: Install system dependencies (image processing)
run: sudo 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
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 --cov=app --cov-report=term-missing --cov-report=html --cov-report=xml
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Sync and verify version metadata
uses: ./.github/actions/sync-version-check
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Cache Node modules
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
id: node-cache
with:
path: frontend/node_modules
key: ${{ runner.os }}-node-20-frontend-${{ hashFiles('frontend/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-20-frontend-
- name: Install dependencies
if: steps.node-cache.outputs.cache-hit != 'true'
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
# ──────────────────────────────────────────────
# 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Sync and verify version metadata
uses: ./.github/actions/sync-version-check
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.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@f51f967e158417aafa338a1e46ab22095f07aa01
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, companion]
runs-on: ubuntu-latest
steps:
- name: Check results
run: |
results="${{ needs.backend.result }} ${{ needs.frontend.result }} ${{ needs.companion.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."