## ## .github/workflows/ci.yml — GrantForge AI CI/CD ## ## Uruchamia się przy: ## - push na 'main' (deploy do Render) ## - PR do 'main' (testy + lint bez deploy) ## ## Kroki: ## 1. Lint (ruff) + type check (mypy) ## 2. Testy jednostkowe (pytest) ## 3. DeepEval RAG faithfulness (tylko main) ## 4. Build Docker image + push do ghcr.io ## 5. Trigger deploy na Render.com (webhook) ## name: CI/CD — GrantForge AI on: push: branches: [main, staging] pull_request: branches: [main, staging] env: PYTHON_VERSION: "3.11.9" REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}/grantforge-api LANGCHAIN_TRACING_V2: "true" LANGCHAIN_PROJECT: "grantforge-production" jobs: # ───────────────────────────────────────────────────────────────────────────── # JOB 1: Lint + Type Check # ───────────────────────────────────────────────────────────────────────────── lint: name: 🔍 Lint & Type Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip cache-dependency-path: backend/requirements.txt - name: Install lint tools run: pip install ruff mypy - name: Ruff lint (backend) run: python -m ruff check backend/ --select E,W,F --ignore E501,E402,W291,W293,F841,E722,E701,E712,E731,E741,F811,F401,F541 - name: Mypy type check (core modules) run: | cd backend mypy core/ --ignore-missing-imports --no-strict-optional || true # ───────────────────────────────────────────────────────────────────────────── # JOB 2: Backend Tests # ───────────────────────────────────────────────────────────────────────────── test-backend: name: 🧪 Backend Tests (pytest) runs-on: ubuntu-latest needs: lint env: GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} BIELIK_MODE: disabled DATABASE_URL: sqlite:///./test.db steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip cache-dependency-path: backend/requirements.txt - name: Install backend dependencies run: | sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config python3-dev cd backend pip install --upgrade pip pip uninstall -y pinecone-plugin-inference pip install -r requirements.txt pip install pytest pytest-asyncio httpx - name: Run unit tests with coverage run: | cd backend python -m pytest tests/ -v --tb=short -x \ --ignore=tests/test_deepeval_rag.py \ --cov=endpoints --cov-report=term-missing --cov-fail-under=50 \ 2>&1 | tail -50 - name: Check API server imports run: | cd backend python -c "import server; print('✅ server.py OK')" # ───────────────────────────────────────────────────────────────────────────── # JOB 3: DeepEval RAG Quality (tylko na push do main) # ───────────────────────────────────────────────────────────────────────────── deepeval: name: 🎯 DeepEval RAG Faithfulness runs-on: ubuntu-latest needs: test-backend if: github.event_name == 'push' && github.ref == 'refs/heads/main' continue-on-error: true # Nie blokuje deploy przy braku Pinecone key env: GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }} PINECONE_INDEX_NAME: ${{ secrets.PINECONE_INDEX_NAME }} LANGCHAIN_API_KEY: ${{ secrets.LANGCHAIN_API_KEY }} LANGCHAIN_TRACING_V2: "true" LANGCHAIN_PROJECT: grantforge-ci BIELIK_MODE: disabled steps: - uses: actions/checkout@v4 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VERSION }} cache: pip cache-dependency-path: backend/requirements.txt - name: Install dependencies + DeepEval run: | sudo apt-get update && sudo apt-get install -y libcairo2-dev pkg-config python3-dev cd backend pip install --upgrade pip pip uninstall -y pinecone-plugin-inference pip install -r requirements.txt deepeval - name: Run DeepEval RAG tests run: | cd backend python scripts/run_eval.py || true # 'true' — nie blokuje deploy przy niskim quality score (tylko raport) # ───────────────────────────────────────────────────────────────────────────── # JOB 4: Frontend Build Check # ───────────────────────────────────────────────────────────────────────────── build-frontend: name: 🏗️ Frontend Build (Vite) runs-on: ubuntu-latest needs: lint steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "20" - name: Install dependencies run: | cd frontend-react rm -rf node_modules package-lock.json npm install - name: TypeScript check run: | cd frontend-react npx tsc --noEmit || true - name: Build run: | cd frontend-react npm run build env: VITE_CLERK_PUBLISHABLE_KEY: ${{ secrets.VITE_CLERK_PUBLISHABLE_KEY }} VITE_STRIPE_PRICE_ID_PRO: ${{ secrets.VITE_STRIPE_PRICE_ID_PRO }} VITE_API_URL: "/api" VITE_APP_VERSION: "1.3.0" # ───────────────────────────────────────────────────────────────────────────── # JOB 5: Docker Build + Push (tylko main) # ───────────────────────────────────────────────────────────────────────────── docker: name: 🐳 Docker Build & Push runs-on: ubuntu-latest needs: [test-backend, build-frontend] if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | type=sha,prefix=sha-,format=short type=raw,value=latest - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # ───────────────────────────────────────────────────────────────────────────── # JOB 6: Deploy do Hugging Face Spaces (tylko main) # ───────────────────────────────────────────────────────────────────────────── deploy-huggingface: name: 🚀 Deploy to Hugging Face runs-on: ubuntu-latest needs: [docker] # deepeval jest continue-on-error, nie blokuje deploy if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Push to Hugging Face Spaces env: HF_TOKEN: ${{ secrets.HF_TOKEN }} run: | git config --global user.email "bot@grantforge.ai" git config --global user.name "GrantForge Bot" # Usuwamy starą historię, aby pozbyć się starych blobów binarnych rm -rf .git git init -b main # Konfigurujemy Git LFS dla plików binarnych (wymóg Hugging Face) git lfs install echo "*.ttf filter=lfs diff=lfs merge=lfs -text" > .gitattributes echo "*.png filter=lfs diff=lfs merge=lfs -text" >> .gitattributes echo "*.jpg filter=lfs diff=lfs merge=lfs -text" >> .gitattributes echo "*.ico filter=lfs diff=lfs merge=lfs -text" >> .gitattributes echo "*.pdf filter=lfs diff=lfs merge=lfs -text" >> .gitattributes # Tworzymy nowy, czysty commit z całą aplikacją git add .gitattributes git add . git commit -m "Deploy to Hugging Face" # Wypychamy na HF (wypchnie również obiekty LFS) git remote add huggingface https://Bogdan555:${HF_TOKEN}@huggingface.co/spaces/Bogdan555/grantforge-api git push -f huggingface main