Update dependency jest to v30 #130
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI/CD Pipeline | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| jobs: | |
| test: | |
| name: Test & Security | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| node-version: [18.x, 20.x] | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v4 | |
| - name: 🟢 Setup Node.js ${{ matrix.node-version }} | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| cache: 'npm' | |
| - name: 📦 Install dependencies | |
| run: npm ci | |
| - name: 🧪 Run tests with coverage | |
| run: npm run test:coverage | |
| - name: 📊 Upload coverage artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report-node-${{ matrix.node-version }} | |
| path: coverage/ | |
| retention-days: 30 | |
| - name: 📊 Generate badges (Node 20.x only) | |
| if: matrix.node-version == '20.x' | |
| run: | | |
| # Create badges directory | |
| mkdir -p badges | |
| # Generate coverage badge | |
| if [ -f "coverage/coverage-summary.json" ]; then | |
| COVERAGE=$(node -e " | |
| const fs = require('fs'); | |
| const coverage = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8')); | |
| const pct = Math.round(coverage.total.lines.pct); | |
| console.log(pct); | |
| ") | |
| else | |
| COVERAGE=0 | |
| fi | |
| echo "Coverage: ${COVERAGE}%" | |
| # Determine coverage color | |
| if [ "$COVERAGE" -ge 80 ]; then | |
| COV_COLOR="brightgreen" | |
| elif [ "$COVERAGE" -ge 60 ]; then | |
| COV_COLOR="yellow" | |
| else | |
| COV_COLOR="red" | |
| fi | |
| # Generate test count badge | |
| # Count actual tests from Jest output or fallback to file count | |
| TEST_COUNT=$(npm test 2>&1 | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' | head -1 || echo "26") | |
| # Security audit badge | |
| set +e | |
| AUDIT_OUTPUT=$(npm audit --audit-level=low --json 2>/dev/null) | |
| AUDIT_EXIT_CODE=$? | |
| set -e | |
| VULNS=$(echo "$AUDIT_OUTPUT" | node -e " | |
| let input = ''; | |
| process.stdin.on('data', chunk => input += chunk); | |
| process.stdin.on('end', () => { | |
| try { | |
| const audit = JSON.parse(input); | |
| const vulns = audit.vulnerabilities || {}; | |
| console.log(Object.keys(vulns).length); | |
| } catch (e) { | |
| console.log(0); | |
| } | |
| }); | |
| " 2>/dev/null || echo "0") | |
| # Determine security color | |
| if [ "$VULNS" -eq 0 ]; then | |
| SEC_COLOR="brightgreen" | |
| SEC_MESSAGE="0%20vulnerabilities" | |
| elif [ "$VULNS" -le 5 ]; then | |
| SEC_COLOR="yellow" | |
| SEC_MESSAGE="${VULNS}%20vulnerabilities" | |
| else | |
| SEC_COLOR="red" | |
| SEC_MESSAGE="${VULNS}%20vulnerabilities" | |
| fi | |
| # Download badges | |
| curl -s "https://img.shields.io/badge/coverage-${COVERAGE}%25-${COV_COLOR}?style=flat-square&logo=jest" > badges/coverage.svg | |
| curl -s "https://img.shields.io/badge/tests-${TEST_COUNT}%20passing-brightgreen?style=flat-square&logo=checkmarx" > badges/tests.svg | |
| curl -s "https://img.shields.io/badge/security-${SEC_MESSAGE}-${SEC_COLOR}?style=flat-square&logo=shield" > badges/security.svg | |
| echo "✅ Generated badges: Coverage: ${COVERAGE}% | Tests: ${TEST_COUNT} | Security: ${VULNS} vulns" | |
| - name: 📊 Upload badge artifacts | |
| if: matrix.node-version == '20.x' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: badges | |
| path: badges/ | |
| retention-days: 30 | |
| - name: 📊 Prepare GitHub Pages content | |
| if: github.ref == 'refs/heads/main' && matrix.node-version == '20.x' | |
| run: | | |
| # Create GitHub Pages directory structure | |
| mkdir -p gh-pages/coverage | |
| mkdir -p gh-pages/badges | |
| # Copy coverage report | |
| if [ -d "coverage/lcov-report" ]; then | |
| cp -r coverage/lcov-report/* gh-pages/coverage/ | |
| echo "✅ Copied coverage report" | |
| else | |
| echo "⚠️ No coverage report found" | |
| fi | |
| # Copy badges | |
| if [ -d "badges" ]; then | |
| cp -r badges/* gh-pages/badges/ | |
| echo "✅ Copied badges" | |
| else | |
| echo "⚠️ No badges found" | |
| fi | |
| # List what we're deploying | |
| echo "📂 GitHub Pages content:" | |
| find gh-pages -type f | sort | |
| - name: 📊 Deploy to GitHub Pages | |
| if: github.ref == 'refs/heads/main' && matrix.node-version == '20.x' | |
| uses: peaceiris/actions-gh-pages@v3 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| publish_dir: ./gh-pages | |
| - name: 📊 Comment coverage on PR | |
| if: github.event_name == 'pull_request' && matrix.node-version == '20.x' | |
| uses: romeovs/lcov-reporter-action@v0.4.0 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| lcov-file: ./coverage/lcov.info | |
| title: "Coverage Report (Node ${{ matrix.node-version }})" | |
| continue-on-error: true | |
| - name: 🛡️ Security audit | |
| run: npm audit --audit-level moderate | |
| - name: 🔍 Check for vulnerabilities | |
| run: npm audit --audit-level high --production | |
| lint: | |
| name: Code Quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v4 | |
| - name: 🟢 Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20.x' | |
| cache: 'npm' | |
| - name: 📦 Install dependencies | |
| run: npm ci | |
| # Future: Add ESLint when we set it up | |
| # - name: 🔍 Run ESLint | |
| # run: npm run lint | |
| - name: 🎯 Check package vulnerabilities | |
| uses: actions/dependency-review-action@v4 | |
| if: github.event_name == 'pull_request' | |
| deploy: | |
| name: Deploy to Server | |
| runs-on: ubuntu-latest | |
| needs: [test, lint] | |
| if: github.ref == 'refs/heads/main' | |
| steps: | |
| - name: 📥 Checkout code | |
| uses: actions/checkout@v4 | |
| - name: 🚀 Deploy via SSH | |
| uses: appleboy/ssh-action@v1.2.2 | |
| with: | |
| host: ${{ secrets.DEPLOY_HOST }} | |
| username: ${{ secrets.DEPLOY_USER }} | |
| key: ${{ secrets.DEPLOY_KEY }} | |
| port: ${{ secrets.DEPLOY_PORT || '22' }} | |
| script: | | |
| echo "🔄 Starting deployment..." | |
| cd ${{ secrets.DEPLOY_PATH || '/opt/videoodyssee-api' }} | |
| # Pull latest code | |
| git fetch origin | |
| git reset --hard origin/main | |
| # Install dependencies | |
| npm ci --only=production | |
| # Restart service | |
| sudo systemctl restart videoodyssee-api | |
| # Verify deployment | |
| sleep 5 | |
| curl -f http://localhost:8000/health || exit 1 | |
| echo "✅ Deployment successful!" | |
| health-check: | |
| name: Post-Deploy Health Check | |
| runs-on: ubuntu-latest | |
| needs: [deploy] | |
| if: github.ref == 'refs/heads/main' | |
| steps: | |
| - name: 🏥 Remote health check | |
| run: | | |
| echo "🔍 Checking deployed API health..." | |
| HEALTH_URL="http://${{ secrets.DEPLOY_HOST }}:8000/health" | |
| # Wait a bit for deployment to complete | |
| sleep 15 | |
| # Try health check with retries | |
| for i in {1..3}; do | |
| echo "Attempt $i/3..." | |
| if curl -f "$HEALTH_URL" --connect-timeout 10 --max-time 30; then | |
| echo "✅ Health check passed!" | |
| exit 0 | |
| fi | |
| echo "❌ Health check failed, retrying in 10s..." | |
| sleep 10 | |
| done | |
| echo "❌ Health check failed after 3 attempts" | |
| exit 1 |