Skip to content

Remediate codebase health issues #584

Remediate codebase health issues

Remediate codebase health issues #584

Workflow file for this run

name: CI - Continuous Integration
on:
push:
branches:
- main
- develop
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- '*.md'
- 'plan/**'
- 'LICENSE'
- '.github/workflows/deploy-docs.yml'
pull_request:
branches:
- main
- develop
paths-ignore:
- 'docs/**'
- 'mkdocs.yml'
- '*.md'
- 'plan/**'
- 'LICENSE'
- '.github/workflows/deploy-docs.yml'
workflow_dispatch:
# Cancel in-progress runs for the same workflow and ref
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
jobs:
changes:
name: Detect Changes
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
pull-requests: read
outputs:
python: ${{ steps.filter.outputs.python }}
frontend: ${{ steps.filter.outputs.frontend }}
docker: ${{ steps.filter.outputs.docker }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@v4
id: filter
with:
filters: |
python:
- 'phentrieve/**'
- 'api/**'
- 'tests/**'
- 'pyproject.toml'
- 'uv.lock'
- '.github/workflows/ci.yml'
frontend:
- 'frontend/**'
- '.github/workflows/ci.yml'
docker:
- '**/Dockerfile'
- 'docker-compose*.yml'
- '.github/workflows/ci.yml'
python-ci:
name: Python CI
needs: changes
if: needs.changes.outputs.python == 'true'
runs-on: ubuntu-latest
env:
HF_HOME: ${{ github.workspace }}/.cache/huggingface
strategy:
fail-fast: false
matrix:
python-version: ['3.11', '3.12', '3.13']
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Cache uv dependencies
uses: actions/cache@v5
with:
path: |
~/.cache/uv
.venv
key: ${{ runner.os }}-uv-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
restore-keys: |
${{ runner.os }}-uv-${{ matrix.python-version }}-
${{ runner.os }}-uv-
- name: Install dependencies
run: |
uv sync --all-extras --dev
- name: Run Ruff format check
run: |
uv run ruff format --check phentrieve/ api/ tests/
- name: Run Ruff linting
run: |
uv run ruff check phentrieve/ api/ tests/
- name: Run mypy type checking
run: |
uv run mypy phentrieve/ api/
- name: Validate pytest collection
run: |
uv run pytest -o addopts='' tests/ --collect-only -q --strict-markers -m "not slow and not e2e"
- name: Run pytest
env:
COVERAGE_CORE: sysmon
run: |
uv run pytest tests/ -v -m "not e2e" --cov=phentrieve --cov=api --cov-report=xml --cov-report=term
- name: Upload coverage to Codecov
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v6
with:
files: ./coverage.xml
flags: python
name: python-${{ matrix.python-version }}
continue-on-error: true
frontend-ci:
name: Frontend CI
needs: changes
if: needs.changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
run: npm ci
- name: Cache ESLint
uses: actions/cache@v5
with:
path: frontend/.eslintcache
key: eslint-${{ hashFiles('frontend/package-lock.json', 'frontend/eslint.config.*') }}
restore-keys: |
eslint-
- name: Run ESLint
working-directory: frontend
run: npm run lint
- name: Run Prettier format check
working-directory: frontend
run: npm run format:check
- name: Run Vitest tests (with coverage on main, no coverage on PRs)
working-directory: frontend
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
npm run test:ci
else
npm run test:coverage
fi
- name: Upload coverage to Codecov
if: github.event_name != 'pull_request'
uses: codecov/codecov-action@v6
with:
files: ./frontend/coverage/coverage-final.json
flags: frontend
name: frontend
continue-on-error: true
- name: Build frontend
working-directory: frontend
run: npm run build
env:
VITE_API_URL: /api/v1
docker-build-test:
name: Docker Build Test
needs: changes
if: needs.changes.outputs.docker == 'true'
runs-on: ubuntu-latest
strategy:
matrix:
service: [api, frontend]
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Free disk space
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
docker system prune -af
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build ${{ matrix.service }} image
uses: docker/build-push-action@v7
with:
context: ${{ matrix.service == 'api' && '.' || './frontend' }}
file: ${{ matrix.service == 'api' && './api/Dockerfile' || './frontend/Dockerfile' }}
push: false
tags: ghcr.io/berntpopp/phentrieve/${{ matrix.service }}:test
build-args: |
${{ matrix.service == 'frontend' && 'VITE_API_URL=/api/v1' || '' }}
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.event_name == 'pull_request'
permissions:
contents: read
steps:
- name: Review dependency changes via API
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
response_body="$(mktemp)"
response_headers="$(mktemp)"
findings_json="$(mktemp)"
trap 'rm -f "$response_body" "$response_headers" "$findings_json"' EXIT
attempt=1
max_attempts=3
while true; do
curl -fsSL \
-D "$response_headers" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"https://api.github.com/repos/${REPOSITORY}/dependency-graph/compare/${BASE_SHA}...${HEAD_SHA}" \
-o "$response_body"
snapshot_warning_header="$(awk 'BEGIN{IGNORECASE=1} /^x-github-dependency-graph-snapshot-warnings:/{sub(/^[^:]+:[[:space:]]*/, ""); print; exit}' "$response_headers")"
snapshot_warning="$(printf '%s' "$snapshot_warning_header" | python -c 'import base64, sys; data = sys.stdin.read().strip(); print(base64.b64decode(data).decode("utf-8") if data else "", end="")')"
if [[ -z "${snapshot_warning}" ]] || [[ "$attempt" -ge "$max_attempts" ]]; then
break
fi
echo "Dependency graph snapshot warning detected; retrying in $((2 ** attempt))s..." >&2
sleep $((2 ** attempt))
attempt=$((attempt + 1))
done
if [[ -n "${snapshot_warning:-}" ]]; then
{
echo "## Dependency review snapshot warning"
echo
echo '```'
echo "${snapshot_warning}"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
fi
jq '
[
.[]
| select(.change_type != "removed")
| select((.scope // "unknown") == "runtime")
| . as $dependency
| ($dependency.vulnerabilities // [])[]?
| select((.severity // "" | ascii_downcase) == "high" or (.severity // "" | ascii_downcase) == "critical")
| {
manifest: $dependency.manifest,
ecosystem: $dependency.ecosystem,
name: $dependency.name,
version: $dependency.version,
scope: ($dependency.scope // "unknown"),
severity: (.severity // "unknown"),
advisory: (.advisory_ghsa_id // "unknown"),
summary: (.advisory_summary // "No summary provided"),
url: (.advisory_url // "")
}
]
' "$response_body" > "$findings_json"
if [[ "$(jq 'length' "$findings_json")" -eq 0 ]]; then
{
echo "## Dependency review"
echo
echo "No new runtime dependencies with high or critical vulnerabilities were detected."
} >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
{
echo "## Dependency review failures"
echo
echo "| Dependency | Version | Severity | Advisory | Manifest |"
echo "|---|---|---|---|---|"
jq -r '.[] | "| \(.name) | \(.version) | \(.severity) | \(.advisory) | \(.manifest) |"' "$findings_json"
echo
echo "High or critical vulnerabilities were introduced in runtime dependency changes."
} >> "$GITHUB_STEP_SUMMARY"
jq -r '.[] | "\(.severity): \(.name)@\(.version) (\(.advisory)) - \(.summary)\n\(.url)\n"' "$findings_json" >&2
exit 1
ci-summary:
name: CI Summary
needs: [changes, python-ci, frontend-ci, docker-build-test, dependency-review]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check CI results
run: |
echo "## CI Results Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Job | Result |" >> $GITHUB_STEP_SUMMARY
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Python CI | ${{ needs.python-ci.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Frontend CI | ${{ needs.frontend-ci.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Docker Build | ${{ needs.docker-build-test.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Dependency Review | ${{ needs.dependency-review.result }} |" >> $GITHUB_STEP_SUMMARY
if [[ "${{ needs.python-ci.result }}" == "failure" ]] || \
[[ "${{ needs.frontend-ci.result }}" == "failure" ]] || \
[[ "${{ needs.docker-build-test.result }}" == "failure" ]]; then
echo "One or more CI jobs failed"
exit 1
fi
if [[ "${{ needs.dependency-review.result }}" == "failure" ]]; then
echo "Dependency review found vulnerabilities"
exit 1
fi