legal: ajouter LICENSE MIT pour conformité badge README #326
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 | |
| # ===== PRE-COMMIT VERIFICATION ===== | |
| - name: Verify pre-commit hooks configuration | |
| run: | | |
| echo "=== Pre-commit Hooks Verification ===" | |
| pip install pre-commit | |
| pre-commit run --all-files || true | |
| echo "" | |
| echo "=== Pre-commit hooks verified ===" | |
| # ===== 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: Install qpdf | |
| run: sudo apt-get update && sudo apt-get install -y qpdf | |
| - name: Validate PDF structure | |
| run: qpdf --check exports/span-sg.pdf | |
| # ===== BENCHMARK ===== | |
| - name: Run benchmark | |
| 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 | |
| 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) ===== | |
| # Phase 1: E2E native (GitHub Actions environment) | |
| - 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 | |
| # Phase 2: E2E Docker (containerized environment) | |
| - name: Install system dependencies for E2E | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libpango-1.0-0 \ | |
| libpangocairo-1.0-0 \ | |
| libgdk-pixbuf2.0-0 \ | |
| libffi-dev \ | |
| shared-mime-info | |
| - name: Setup Docker Buildx | |
| uses: docker/setup-buildx-action@v2 | |
| - name: Cache Docker layers | |
| uses: actions/cache@v3 | |
| with: | |
| path: /tmp/.buildx-cache | |
| key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile.mkdocs-test', 'requirements-dsfr.txt') }} | |
| restore-keys: | | |
| ${{ runner.os }}-buildx- | |
| - name: Build E2E test Docker image (with mkdocs-dsfr) | |
| run: | | |
| docker buildx build \ | |
| -f Dockerfile.mkdocs-test \ | |
| -t mkdocs-test:latest \ | |
| --cache-from type=local,src=/tmp/.buildx-cache \ | |
| --cache-to type=local,dest=/tmp/.buildx-cache-new,mode=max \ | |
| --load \ | |
| . | |
| - name: Move Docker cache | |
| run: | | |
| rm -rf /tmp/.buildx-cache | |
| mv /tmp/.buildx-cache-new /tmp/.buildx-cache || true | |
| - name: Run E2E tests (Docker environment) | |
| continue-on-error: true # TODO: Remove once Docker environment validated | |
| run: | | |
| docker run --rm -v $PWD:/docs --entrypoint bash mkdocs-test:latest tests/e2e/ci_runner.sh | |
| - name: Upload E2E Docker report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-docker-report-${{ github.run_number }} | |
| path: tests/e2e/report.html | |
| retention-days: 30 | |
| - name: Upload E2E Docker accessibility report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: e2e-docker-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/draft/ | |
| 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 /draft/ (staging) | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Clean existing /draft/ directory | |
| rm -rf draft | |
| mkdir -p draft | |
| # Copy new build | |
| cp -r site/* draft/ | |
| mkdir -p draft/exports | |
| cp -r exports/* draft/exports/ | |
| # Commit and push | |
| git add draft | |
| 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/draft/" | |
| 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 /draft/ and .git only) | |
| find . -mindepth 1 -maxdepth 1 ! -name 'draft' ! -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 }}" |