Skip to content

Website prep

Website prep #111

Workflow file for this run

name: Lint
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"]
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/") 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 (ruff check + ruff format --check)
# ─────────────────────────────────────────────────────────────────────
lint-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
with:
python-version: '3.13'
cache: 'pip'
cache-dependency-path: 'backend/requirements*.txt'
- name: Install backend lint dependencies
run: pip install --require-hashes -r backend/requirements-dev.lock.txt
- name: Ruff check
run: cd backend && ruff check app
- name: Ruff format check
run: cd backend && ruff format --check app
# ─────────────────────────────────────────────────────────────────────
# Frontend: TypeScript/JavaScript (biome check)
# ─────────────────────────────────────────────────────────────────────
lint-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'
- 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') }}
- name: Install Node.js dependencies
if: steps.node-cache.outputs.cache-hit != 'true'
run: cd frontend && npm ci
- name: Biome check
run: cd frontend && npm run lint
# ─────────────────────────────────────────────────────────────────────
# Companion: Rust (clippy + rustfmt) + TypeScript/CSS (biome)
# ─────────────────────────────────────────────────────────────────────
lint-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: 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
with:
components: clippy, rustfmt
- name: Cache Rust build artifacts
uses: Swatinem/rust-cache@f51f967e158417aafa338a1e46ab22095f07aa01
with:
workspaces: companion/src-tauri -> target
- name: Clippy check
run: cd companion/src-tauri && cargo clippy --lib --tests -- -D warnings
- name: Rustfmt check
run: cd companion/src-tauri && cargo fmt --check
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '20'
- name: Cache node_modules
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5
id: companion-node-cache
with:
path: companion/node_modules
key: ${{ runner.os }}-node-20-companion-${{ hashFiles('companion/package-lock.json') }}
- name: Install companion Node.js dependencies
if: steps.companion-node-cache.outputs.cache-hit != 'true'
run: cd companion && npm ci
- name: Biome check
run: cd companion && npm run lint
# ─────────────────────────────────────────────────────────────────────
# Gate: single required check for branch protection
# ─────────────────────────────────────────────────────────────────────
lint-gate:
if: always()
needs: [lint-backend, lint-frontend, lint-companion]
runs-on: ubuntu-latest
steps:
- name: Check results
run: |
results="${{ needs.lint-backend.result }} ${{ needs.lint-frontend.result }} ${{ needs.lint-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 lint checks passed or were skipped."