fix: corrige les erreurs Python détectées par GitHub Actions #359
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: Build and Deploy | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| jobs: | |
| build-and-test: | |
| runs-on: ubuntu-latest | |
| steps: | |
| # ===== SETUP ===== | |
| - name: Checkout code | |
| uses: actions/checkout@v3 | |
| - name: Setup Python 3.11 | |
| uses: actions/setup-python@v4 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| cache-dependency-path: | | |
| requirements-dsfr.txt | |
| requirements-dev.txt | |
| requirements-security.txt | |
| # ===== DEPENDENCIES ===== | |
| - name: Install dependencies | |
| run: pip install -r requirements-dsfr.txt | |
| - name: Install dev dependencies | |
| run: pip install -r requirements-dev.txt | |
| - name: Install security tools | |
| run: pip install -r requirements-security.txt | |
| - name: Install system dependencies (qpdf + WeasyPrint + E2E libs) | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| qpdf \ | |
| libpango-1.0-0 \ | |
| libpangocairo-1.0-0 \ | |
| libgdk-pixbuf2.0-0 \ | |
| libffi-dev \ | |
| shared-mime-info | |
| # ===== QUALITY CHECKS ===== | |
| - name: Run Black formatter check | |
| run: python -m black --check scripts/ | |
| - name: Run Ruff linter | |
| run: python -m ruff check scripts/ | |
| - name: Run mypy type checker | |
| run: python -m mypy scripts/ hooks/ --ignore-missing-imports --check-untyped-defs | |
| # ===== SECURITY CHECKS ===== | |
| - name: Run Bandit security linter | |
| run: | | |
| echo "=== Bandit Security Analysis ===" | |
| echo "Analyzing scripts/ and hooks/ for security issues..." | |
| echo "(Excluding test files to avoid false positives)" | |
| echo "" | |
| # Generate JSON report (non-blocking) | |
| bandit -r scripts/ hooks/ -ll -x tests/ -f json -o bandit-report.json || true | |
| # Display summary and fail on HIGH/CRITICAL | |
| bandit -r scripts/ hooks/ -ll -x tests/ --format screen | |
| echo "" | |
| echo "=== Bandit analysis complete ===" | |
| - name: Check dependencies vulnerabilities (Safety) | |
| run: | | |
| echo "=== Safety CVE Check ===" | |
| echo "Checking requirements-dsfr.txt for known vulnerabilities..." | |
| echo "" | |
| # Generate JSON report (non-blocking) | |
| safety check -r requirements-dsfr.txt --json > safety-report.json || true | |
| # Fail if vulnerabilities found | |
| safety check -r requirements-dsfr.txt | |
| echo "" | |
| echo "=== No vulnerabilities found ===" | |
| - name: Upload security reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: security-reports-${{ github.run_number }} | |
| path: | | |
| bandit-report.json | |
| safety-report.json | |
| retention-days: 90 | |
| # ===== TESTS ===== | |
| - name: Run unit tests | |
| run: python -m pytest tests/unit/ -v --cov=scripts --cov=hooks --cov-report=term-missing | |
| - name: Run production scripts coverage check (89%+ required) | |
| run: | | |
| python -m pytest \ | |
| --cov=scripts \ | |
| --cov-report=term-missing \ | |
| --cov-report=html \ | |
| --cov-fail-under=89 \ | |
| tests/unit/scripts/test_calculate_scores*.py tests/unit/scripts/test_enrich_pdf*.py | |
| - name: Run hooks coverage check (100% required) | |
| run: | | |
| python -m pytest \ | |
| tests/unit/hooks/test_hooks_*.py \ | |
| --cov=hooks \ | |
| --cov-config=.coveragerc-hooks \ | |
| --cov-report=term-missing \ | |
| --cov-report=html:htmlcov-hooks \ | |
| --cov-fail-under=100 | |
| # ===== BUILD ===== | |
| - name: Calculate SPAN scores | |
| run: python scripts/calculate_scores.py | |
| - name: Generate PDF (builds site/ with ReadTheDocs theme for PDF compat) | |
| run: | | |
| ENABLE_PDF_EXPORT=1 mkdocs build --config-file mkdocs-dsfr-pdf.yml | |
| - name: Enrich PDF metadata | |
| run: python scripts/enrich_pdf_metadata.py exports/span-sg.pdf | |
| - name: Build site HTML (strict mode - overwrites site/ with DSFR theme) | |
| run: mkdocs build --config-file mkdocs-dsfr.yml --strict | |
| - name: Verify DSFR theme applied | |
| run: | | |
| if ! grep -q 'fr-header' site/index.html; then | |
| echo "❌ ERREUR: Thème DSFR non appliqué (fr-header manquant)" | |
| exit 1 | |
| fi | |
| echo "✅ Thème DSFR correctement appliqué" | |
| - name: Validate PDF structure | |
| run: qpdf --check exports/span-sg.pdf | |
| # ===== BENCHMARK (main only) ===== | |
| - name: Run benchmark | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| run: | | |
| echo "Running performance benchmarks..." | |
| python scripts/benchmark.py > benchmark-${{ github.sha }}.json | |
| echo "Benchmark complete" | |
| cat benchmark-${{ github.sha }}.json | |
| - name: Upload benchmark metrics | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: benchmark-${{ github.run_number }} | |
| path: benchmark-${{ github.sha }}.json | |
| retention-days: 365 | |
| # ===== E2E TESTS (run on all events) ===== | |
| - name: Run E2E tests (native environment) | |
| run: | | |
| echo "🧪 Running E2E tests in native GitHub Actions environment..." | |
| bash tests/e2e/ci_runner.sh | |
| - name: Upload E2E native report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-native-report-${{ github.run_number }} | |
| path: tests/e2e/report.html | |
| retention-days: 30 | |
| - name: Upload E2E native accessibility report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-native-accessibility-${{ github.run_number }} | |
| path: tests/e2e/accessibility_report.json | |
| retention-days: 30 | |
| # ===== FIX: Rebuild DSFR après E2E tests ===== | |
| # CRITIQUE: Les E2E tests (scenario_erreur_markdown.sh) font `mkdocs build --strict` | |
| # sans --config-file ni --site-dir, écrasant site/ avec Material theme. | |
| # Ce rebuild final garantit que l'artifact uploadé contient bien DSFR. | |
| - name: Clean site directory (remove Docker root-owned files) | |
| run: sudo rm -rf site/ | |
| - name: Rebuild site HTML (final DSFR, après E2E) | |
| run: mkdocs build --config-file mkdocs-dsfr.yml | |
| - name: Verify DSFR theme applied (final check) | |
| run: | | |
| if ! grep -q 'fr-header' site/index.html; then | |
| echo "❌ ERREUR: Thème DSFR non appliqué (fr-header manquant)" | |
| exit 1 | |
| fi | |
| echo "✅ Thème DSFR correctement appliqué (vérification finale)" | |
| # ===== TEST REGRESSION DSFR ===== | |
| # Exécuter test dédié après rebuild final (non inclus dans E2E runner) | |
| - name: Run DSFR theme preservation test | |
| run: bash tests/e2e/test_dsfr_theme_preserved.sh | |
| # ===== UPLOAD ARTIFACTS ===== | |
| - name: Upload site HTML | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: site-${{ github.sha }} | |
| path: site/ | |
| retention-days: 7 | |
| - name: Upload exports PDF | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: exports-${{ github.sha }} | |
| path: exports/ | |
| retention-days: 90 | |
| # ===== NOTIFICATIONS ===== | |
| - name: Send failure notification | |
| if: failure() | |
| uses: dawidd6/action-send-mail@v3 | |
| with: | |
| server_address: smtp.gmail.com | |
| server_port: 587 | |
| username: ${{ secrets.SMTP_USERNAME }} | |
| password: ${{ secrets.SMTP_PASSWORD }} | |
| subject: "[SPAN SG] CI Build Failed - ${{ github.repository }}" | |
| to: alexandra.guiderdoni@gmail.com | |
| from: GitHub Actions | |
| body: | | |
| Build failed for commit ${{ github.sha }} | |
| Branch: ${{ github.ref }} | |
| Workflow: ${{ github.workflow }} | |
| Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| deploy-staging: | |
| needs: build-and-test | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: staging | |
| url: https://alexmacapple.github.io/span-sg/staging/ | |
| steps: | |
| - name: Checkout gh-pages branch | |
| uses: actions/checkout@v3 | |
| with: | |
| ref: gh-pages | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Download site artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: site-${{ github.sha }} | |
| path: site/ | |
| - name: Download exports artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: exports-${{ github.sha }} | |
| path: exports/ | |
| - name: Deploy to /staging/ (staging) | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Clean existing /staging/ directory | |
| rm -rf staging | |
| mkdir -p staging | |
| # Copy new build | |
| cp -r site/* staging/ | |
| mkdir -p staging/exports | |
| cp -r exports/* staging/exports/ | |
| # Commit and push | |
| git add staging | |
| git commit -m "Deploy staging from ${{ github.sha }}" || exit 0 | |
| git push origin gh-pages | |
| - name: Deployment summary | |
| run: | | |
| echo "✅ Staging deployment complete" | |
| echo "URL: https://alexmacapple.github.io/span-sg/staging/" | |
| echo "Commit: ${{ github.sha }}" | |
| deploy-production: | |
| needs: [build-and-test, deploy-staging] | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: production | |
| url: https://alexmacapple.github.io/span-sg/ | |
| steps: | |
| - name: Checkout gh-pages branch | |
| uses: actions/checkout@v3 | |
| with: | |
| ref: gh-pages | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Download site artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: site-${{ github.sha }} | |
| path: site/ | |
| - name: Download exports artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: exports-${{ github.sha }} | |
| path: exports/ | |
| - name: Deploy to / (production) | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Rename downloaded artifacts to temp names | |
| mv site site-tmp | |
| mv exports exports-tmp | |
| # Clean root (preserve /staging/ and .git only) | |
| find . -mindepth 1 -maxdepth 1 ! -name 'staging' ! -name '.git' ! -name 'site-tmp' ! -name 'exports-tmp' -exec rm -rf {} + | |
| # Deploy new build | |
| cp -r site-tmp/* . | |
| mkdir -p exports | |
| cp -r exports-tmp/* exports/ | |
| # Clean temporary directories | |
| rm -rf site-tmp exports-tmp | |
| # Commit and push | |
| git add . | |
| git commit -m "Deploy production from ${{ github.sha }}" || exit 0 | |
| # Pull staging changes before push (avoid conflicts) | |
| git pull --rebase origin gh-pages | |
| git push origin gh-pages | |
| - name: Deployment summary | |
| run: | | |
| echo "✅ Production deployment complete" | |
| echo "URL: https://alexmacapple.github.io/span-sg/" | |
| echo "Commit: ${{ github.sha }}" |