Skip to content

chore(deps-dev)(deps-dev): bump the dev group across 1 directory with 6 updates #242

chore(deps-dev)(deps-dev): bump the dev group across 1 directory with 6 updates

chore(deps-dev)(deps-dev): bump the dev group across 1 directory with 6 updates #242

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
frontend-check:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
with:
version: 11
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '26'
cache: 'pnpm'
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Install frontend dependencies
run: cd frontend && pnpm install --frozen-lockfile
- name: Frontend quality gate
run: cd frontend && pnpm lint && pnpm typecheck && pnpm test && pnpm build
container-check:
runs-on: ubuntu-latest
timeout-minutes: 15
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Build production container
run: docker build --tag tax-tracker:ci .
- name: Smoke test production container
run: |
container_id="$(docker run --detach --publish 127.0.0.1:8000:8000 --env PORT=8000 tax-tracker:ci)"
cleanup() {
docker rm --force "$container_id" >/dev/null 2>&1 || true
}
trap cleanup EXIT
for attempt in {1..30}; do
if curl --fail --silent http://127.0.0.1:8000/health; then
exit 0
fi
sleep 1
done
docker logs "$container_id" || true
exit 1
e2e:
runs-on: ubuntu-latest
timeout-minutes: 15
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6
with:
version: 11
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '26'
cache: 'pnpm'
cache-dependency-path: frontend/pnpm-lock.yaml
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: e2e
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.14'
- name: Install backend dependencies
run: uv sync --locked --no-dev
- name: Install frontend dependencies
run: cd frontend && pnpm install --frozen-lockfile
- name: Install browser for end-to-end tests
run: cd frontend && pnpm exec playwright install --with-deps chromium
- name: Browser workflow tests
run: cd frontend && pnpm test:e2e
- name: Retain browser test diagnostics
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: playwright-report
path: |
frontend/playwright-report
frontend/test-results
retention-days: 7
lint:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: ${{ github.job }}
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.14'
- run: uv sync --dev
- run: uv run pre-commit run --all-files
test:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: ${{ github.job }}
- run: uv run pytest
- name: Enforce service-layer coverage
run: >-
uv run pytest
--override-ini='addopts=--cov=taxtracker.services.tax_calculator
--cov=taxtracker.services.projections
--cov=taxtracker.services.w4_calculator
--cov=taxtracker.services.w4_withholding
--cov=taxtracker.services.record_tax_calculator
--cov-report=term-missing --cov-fail-under=90 -q'
typecheck:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: ${{ github.job }}
- run: uv run ty check
security:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: ${{ github.job }}
- name: Scan dependencies for known vulnerabilities
run: |
set -o pipefail
uvx uv-secure 2>&1 | tee uv-secure-report.txt
- name: Retain dependency security report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: uv-secure-report
path: uv-secure-report.txt
pslmodels-cross-check:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: ${{ github.job }}
- name: Fetch fresh PSLmodels snapshot
run: uv run python scripts/fetch_pslmodels_data.py snapshot
- name: Run cross-validation tests against fresh snapshot
run: uv run pytest tests/unit/test_pslmodels_cross_check.py -v --no-header --override-ini='addopts='
- name: Check for snapshot drift
run: |
python - <<'PY'
import json
import subprocess
from pathlib import Path
snapshot_path = Path("tests/data/pslmodels_snapshot.json")
current = json.loads(snapshot_path.read_text(encoding="utf-8"))
committed_text = subprocess.check_output(["git", "show", f"HEAD:{snapshot_path}"], text=True)
committed = json.loads(committed_text)
def normalize(payload: dict[str, object]) -> dict[str, object]:
normalized = dict(payload)
normalized.pop("generated_date", None)
return normalized
if normalize(current) == normalize(committed):
if current.get("generated_date") != committed.get("generated_date"):
print("::notice::PSL snapshot generated_date changed only; tax values are unchanged.")
else:
print("Snapshot unchanged — PSLmodels data matches committed snapshot.")
else:
print("::warning::PSLmodels snapshot tax values changed. Run locally and commit:")
print(" uv run python scripts/fetch_pslmodels_data.py snapshot")
subprocess.run(["git", "--no-pager", "diff", "--", str(snapshot_path)], check=False)
PY
release:
runs-on: ubuntu-latest
needs: [frontend-check, container-check, e2e, lint, test, typecheck, security, pslmodels-cross-check]
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, '[skip ci]') }}
permissions:
contents: read
steps:
- name: Create release App token
id: release-token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ vars.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
permission-contents: write
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0
token: ${{ steps.release-token.outputs.token }}
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-suffix: ${{ github.job }}
- name: Release with python-semantic-release action
id: semantic-release
uses: python-semantic-release/python-semantic-release@9a026e9303981c866c3425723009becb2437c757 # v10.6.2
with:
github_token: ${{ steps.release-token.outputs.token }}
git_committer_name: 'github-actions[bot]'
git_committer_email: 'github-actions[bot]@users.noreply.github.com'
changelog: false