Aller au contenu

Pipeline d'intégration continue (CI)

L’intégration continue (CI, Continuous Integration) désigne l’ensemble des vérifications automatiques rejouées chaque fois qu’un contributeur propose une modification du code. Quelques termes de base, employés partout dans cette page :

  • une branche est une ligne de travail isolée, dérivée de la branche de référence main, sur laquelle on développe sans perturber le reste ;
  • un commit est un enregistrement daté d’un lot de modifications, accompagné d’un message qui en explique l’intention ;
  • pousser (push) consiste à envoyer ses commits locaux vers GitHub ;
  • une pull request (PR) est la proposition publiée sur GitHub de fusionner une branche dans main. Elle permet la revue par un autre humain et déclenche la CI ; elle n’est fusionnée qu’une fois ces deux conditions satisfaites.

Atlas orchestre la CI via GitHub Actions, le service intégré à GitHub qui exécute des workflows (suites de commandes décrites dans des fichiers .github/workflows/*.yml) sur des machines virtuelles à chaque événement (push, pull request, ou horaire programmé cron).

Objectif : aucun code non vérifié n’entre dans main. Si une vérification échoue, la pull request est bloquée tant que l’auteur n’a pas corrigé.

Le dépôt compte quatorze workflows qui se répartissent selon quand ils se déclenchent. L’enchaînement type, du point de vue d’un contributeur, est le suivant :

  1. À l’ouverture (ou à la mise à jour) d’une pull request, plusieurs workflows démarrent en parallèle : la chaîne qualité (ci.yml), les analyses de sécurité du code (codeql.yml, semgrep.yml), la détection de secrets (gitleaks.yml) et la revue des dépendances ajoutées (dependency-review.yml). Certains ne se réveillent que si la PR touche les fichiers concernés : images.yml (construction des images Docker, sur tout changement de code applicatif ou de Dockerfile) et e2e.yml (tests de bout en bout, seulement si la PR touche les apps ou sandboxes ciblées). Toutes ces vérifications doivent passer pour que la PR soit fusionnable.
  2. À la fusion sur main (après revue), d’autres workflows prennent le relais : publication de la documentation (docs.yml), publication des paquets (release.yml), construction et publication des images Docker (images.yml), génération du SBOM (sbom.yml) et analyse de référence CodeQL (codeql.yml tourne aussi sur main).
  3. Sur déclencheur programmé (cron) ou manuel, indépendamment des PR : le rappel d’audit trimestriel (audit-reminder.yml), les tests de bout en bout nocturnes (e2e.yml), l’analyse CodeQL hebdomadaire et le scan dynamique ZAP (zap-baseline.yml, manuel uniquement). En continu, dependabot-auto-merge.yml réagit aux pull requests ouvertes par le robot Dependabot.

Le diagramme suivant résume quels workflows se déclenchent à chaque moment ; chaque case est détaillée plus bas.

flowchart TD
    PR([Pull request ouverte / mise à jour]) --> CI[ci.yml<br/>lint · typecheck · test · build · docs · audit]
    PR --> CodeQL[codeql.yml<br/>SAST CodeQL]
    PR --> Semgrep[semgrep.yml<br/>SAST Semgrep]
    PR --> Gitleaks[gitleaks.yml<br/>secrets]
    PR --> DepReview[dependency-review.yml<br/>dépendances ajoutées]
    PR -. si Dockerfile / code app .-> Images[images.yml<br/>build + smoke image]
    PR -. si apps / sandboxes ciblées .-> E2E[e2e.yml<br/>tests Playwright]
    CI & CodeQL & Semgrep & Gitleaks & DepReview --> Gate{Tous verts ?}
    Gate -- oui --> Merge([Fusion sur main])
    Gate -- non --> Blocked([PR bloquée])
    Merge --> Docs[docs.yml<br/>publication doc]
    Merge --> Release[release.yml<br/>publication paquets]
    Merge --> ImagesMain[images.yml<br/>push GHCR]
    Merge --> SBOM[sbom.yml<br/>SBOM]
    Cron([Cron / manuel]) --> Audit[audit-reminder.yml]
    Cron --> E2Enight[e2e.yml nightly]
    Cron --> ZAP[zap-baseline.yml manuel]

Le tableau ci-dessous récapitule le rôle de chaque workflow. Les sections suivantes détaillent d’abord ci.yml (le plus structurant), puis chacun des autres.

Sur pull request (et la plupart aussi sur main) :

WorkflowRôle
ci.ymlChaîne qualité : lint, typecheck, tests, build, documentation, audits
codeql.ymlAnalyse statique de sécurité (SAST) avec CodeQL
semgrep.ymlAnalyse statique de sécurité (SAST) avec Semgrep, complémentaire à CodeQL
gitleaks.ymlDétection de secrets committés
dependency-review.ymlRevue des nouvelles dépendances (vulnérabilités, licences)
images.ymlConstruction des images Docker + test de démarrage (smoke)
e2e.ymlTests de bout en bout (Playwright) sur les sandboxes
pr-issue-link.ymlAvertit (non bloquant) si une PR référence une issue sans mot-clé de fermeture

Sur main (après fusion) :

WorkflowRôle
docs.ymlPublication du site de documentation sur GitHub Pages
release.ymlPublication des paquets sur npm via Changesets
images.ymlPublication des images Docker sur GHCR
sbom.ymlGénération du SBOM (inventaire des dépendances)

Programmés (cron) ou manuels :

WorkflowRôle
audit-reminder.ymlOuvre une issue de rappel d’audit transverse trimestriel
zap-baseline.ymlScan dynamique OWASP ZAP (DAST) — déclenchement manuel
dependabot-auto-merge.ymlAuto-merge des montées de dépendances sûres ouvertes par Dependabot

ci.yml est la chaîne qualité du dépôt. Il regroupe plusieurs jobs (un job est un groupe d’étapes exécuté sur une machine dédiée). Un premier job, changes, détecte si la PR ne touche que de la documentation : dans ce cas, les jobs lourds (lint, typecheck, test, build, audit) sautent leur travail coûteux tout en restant « verts » (CI adaptative, ADR 0034). Tous les autres jobs ne dépendent que de changes (needs: [changes]) : ils partent donc tous en parallèle dès que changes a répondu, sans aucune barrière d’attente entre eux. C’est un choix assumé d’accélération (ADR 0061) : chaîner build derrière lint/typecheck/test n’ajoutait que de l’attente, car ce sont des contrôles de qualité indépendants et non des prérequis de compilation. L’ordre réel des tâches de build (un paquet avant ceux qui en dépendent, ^build) est géré à l’intérieur de chaque job par Turbo, pas par l’enchaînement des jobs.

Le graphe ci-dessous montre cet enchaînement et les conditions de passage :

flowchart LR
    push([push / PR]) --> changes[changes<br/>doc seulement ?]
    changes --> lint[lint]
    changes --> typecheck[typecheck]
    changes --> test[test]
    changes --> build[build]
    changes --> docs[docs]
    changes --> audit[audit]
    changes --> dataops[dataops]

changes démarre seul dès le push. Tous les autres jobs (lint, typecheck, test, build, docs, audit, dataops) attendent uniquement sa réponse, puis s’exécutent en parallèle : aucun n’attend un autre. Chacun lit le résultat de changes (drapeau RUN) pour décider s’il saute son travail coûteux sur une PR documentaire, tout en sortant toujours « vert » (jamais skipped, qui resterait « Pending » et bloquerait le merge — ADR 0034).

Le lint analyse le code sans l’exécuter pour repérer les erreurs de style et les motifs dangereux (variables non utilisées, expressions régulières coûteuses, oublis de formatage). C’est la première barrière : elle attrape les défauts les moins chers à corriger.

Fenêtre de terminal
pnpm format:check # Prettier vérifie le formatage
pnpm lint # ESLint applique les règles de style/sécurité

Le typecheck vérifie que tous les types TypeScript sont cohérents (une valeur déclarée comme texte n’est jamais utilisée comme nombre). Il attrape une classe entière de bugs avant même l’exécution.

Fenêtre de terminal
pnpm typecheck # TypeScript vérifie les types
pnpm svelte:check # Vérification supplémentaire pour les fichiers .svelte

Les tests exécutent le code avec des données connues pour vérifier qu’il produit le résultat attendu (voir Tests). On mesure en plus la couverture : la proportion du code réellement exercée par au moins un test.

Fenêtre de terminal
pnpm test:coverage # Tous les tests avec mesure de couverture

Le build (compilation) transforme le code source en artefacts prêts à être exécutés ou publiés : chaque sous-projet est compilé, puis on vérifie que la taille des fichiers livrés au navigateur (bundle) ne dépasse pas le budget fixé. Cette étape attrape les erreurs qui n’apparaissent qu’à la compilation et garde les pages légères pour l’utilisateur final.

Fenêtre de terminal
pnpm build # Compilation de chaque sous-projet
pnpm audit:size # Vérifie les budgets de taille de bundle

build ne dépend plus de lint, typecheck ni test : il part en parallèle d’eux dès la réponse de changes (ADR 0061). Ces vérifications sont des contrôles de qualité indépendants, pas des prérequis de compilation ; l’ordre interne des paquets (^build) est assuré par Turbo.

L’audit inspecte les dépendances et le code à la recherche de vulnérabilités, de licences incompatibles, de code mort, de duplication ou de versions obsolètes. C’est le contrôle d’hygiène : il garde le projet sain dans la durée.

Fenêtre de terminal
pnpm audit:structure # Respect des règles de structure du monorepo
pnpm audit:security # Vulnérabilités npm connues (CVE)
pnpm audit:licenses # Compatibilité des licences des dépendances
pnpm audit:unused # Exports, imports et fichiers jamais utilisés (knip)
pnpm audit:duplicates # Blocs de code dupliqués (jscpd)
pnpm audit:versions # Dépendances avec une mise à jour disponible (taze)
pnpm audit:dep-versions # Cohérence des spécificateurs de version entre paquets
pnpm docs:generate:check # La carte des paquets est à jour vis-à-vis du code
pnpm audit:docs # Cohérence de la documentation (compteurs, liens…)

Le job docs construit le site de documentation (celui que vous lisez) pour vérifier qu’il compile sans erreur : liens cassés, pages manquantes ou statistiques désynchronisées du code font échouer la CI. Sur une pull request, on ne fait que construire le site (validation) ; le déploiement, lui, n’a lieu qu’après fusion.

Fenêtre de terminal
pnpm docs:build # Construit le site Astro Starlight

Sur main, ce job est suivi du déploiement sur GitHub Pages via le workflow docs.yml (voir plus bas).

ci.yml n’est qu’un workflow parmi quatorze. Voici ce que font les autres, regroupés par fonction. Toutes les descriptions sont tirées des fichiers .github/workflows/*.yml du dépôt.

  • codeql.yml — analyse statique (SAST) avec CodeQL. Analyse le code JavaScript/TypeScript avec les suites de requêtes security-extended et security-and-quality, et remonte les alertes dans l’onglet Security du dépôt. Se déclenche sur pull request et sur main (analyse de référence), plus un passage hebdomadaire programmé (pour capter les nouvelles règles CodeQL) et un déclenchement manuel. Limite connue, documentée dans le fichier : l’extracteur de CodeQL ne parse pas les fichiers .svelte, dont le code est donc complété par le lint Svelte et la revue.
  • semgrep.yml — analyse statique (SAST) avec Semgrep. SAST complémentaire à CodeQL : Semgrep apporte des règles p/typescript plus spécifiques au TypeScript et les motifs p/owasp-top-ten. Tourne sur pull request uniquement (et en manuel), en se limitant au diff par rapport à la base de la PR. Seuls les findings de sévérité error font échouer la PR ; les avertissements restent visibles sans bloquer.
  • gitleaks.yml — détection de secrets. Recherche les jetons, clés et autres secrets committés. Sur pull request, scanne les commits ajoutés par la branche ; sur main, les commits du push ; un déclenchement manuel permet un audit de tout l’historique. La configuration vit dans .gitleaks.toml.
  • dependency-review.yml — revue des dépendances. Sur chaque pull request, compare les dépendances ajoutées au graphe de dépendances de GitHub : il bloque toute nouvelle vulnérabilité de sévérité high ou critical, et toute dépendance dont la licence n’est pas dans la liste autorisée.
  • sbom.yml — génération du SBOM. À chaque push sur main (et en manuel), produit le SBOM (Software Bill of Materials, nomenclature logicielle) au format CycloneDX avec l’outil cdxgen, le valide, puis l’archive comme artefact (conservé 90 jours).
  • zap-baseline.yml — test dynamique (DAST). Scan dynamique OWASP ZAP en mode baseline (passif) contre une URL fournie à l’exécution. Déclenchement manuel uniquement pour l’instant (pas d’horaire programmé), l’URL cible étant passée au lancement.
  • docs.yml — publication de la documentation. Construit le site Astro Starlight (statistiques + carte des paquets compris) et le déploie sur GitHub Pages. Se déclenche sur main quand des fichiers de documentation ou de sources de paquets changent (et en manuel).
  • release.yml — publication des paquets. Sur main, s’appuie sur Changesets : soit il ouvre/met à jour une pull request « version packages », soit — une fois celle-ci fusionnée — il publie les paquets sur npm public et GitHub Packages avec une attestation de provenance signée par OIDC.
  • images.yml — images Docker. Construit les images de déploiement des unités à configuration runtime/publique (atlas-dashboard, crf-dashboard, le service Hono). Sur pull request : build + test de démarrage (smoke : on lance le conteneur et on vérifie que son healthcheck passe), sans publication. Sur main : build puis publication sur GHCR (le registre d’images de GitHub). Ne tourne que si la PR touche du code applicatif, un Dockerfile ou des fichiers liés (ADR 0043).
  • dependabot-auto-merge.yml — montées de dépendances. Réagit aux pull requests ouvertes par le robot Dependabot : active la fusion automatique pour les montées jugées sûres (tous les correctifs patch, et les montées minor sur les dépendances de développement uniquement). Les montées minor sur les dépendances de production et toutes les montées major restent en revue manuelle.
  • e2e.yml — tests Playwright. Lance les tests smoke Playwright (qui pilotent un vrai navigateur) des sandboxes amarre et sillage, après avoir démarré leur pile Docker complète. Se déclenche chaque nuit (programmé à 03:00 UTC), en manuel, et sur pull request seulement si le diff touche ces apps, leurs sandboxes ou ce workflow. Un préalable détecte si la source externe requise est présente ; sinon le job se termine en succès avec un avertissement plutôt que d’échouer.

Rappels programmés (n’exécutent aucune analyse eux-mêmes)

Section intitulée « Rappels programmés (n’exécutent aucune analyse eux-mêmes) »

Ce workflow n’audite rien directement : il ouvre une issue de rappel parce que le travail correspondant repose sur un outillage qui ne peut pas tourner sur un runner GitHub.

  • audit-reminder.yml — rappel d’audit trimestriel. Chaque trimestre (1ᵉʳ janvier / avril / juillet / octobre, 06:00 UTC), ouvre une issue de rappel pour conduire l’audit transverse du dépôt (ADR 0039).

Tout ce que fait la CI est reproductible en local. Le raccourci global :

Fenêtre de terminal
pnpm ci:checks

Lance dans l’ordre, fail-fast :

  1. format:check — formatage (le plus rapide, le plus probable à échouer)
  2. check (svelte) — vérification SvelteKit
  3. lint — ESLint
  4. typecheck — TypeScript
  5. test:coverage — tests
  6. build — compilation (le plus long)
  7. dataops:check — qualité de la catégorie dataops/ (Python : ruff, pytest avec couverture, validation des manifestes), hors graphe Turbo donc lancée à part en fin de chaîne

Les hooks Git locaux (lefthook) exécutent automatiquement les étapes 1–4 sur les fichiers modifiés avant chaque commit, et un sous-ensemble plus large avant chaque push. Voir Hooks Git.

  1. Cliquer sur le job rouge dans la pull request → onglet Details.
  2. Le log GitHub Actions s’ouvre. Identifier le step qui a échoué.
  3. Reproduire localement la commande exacte (pnpm lint, pnpm test:coverage, etc.).
  4. Corriger, recommitter, repousser — la CI relance automatiquement.

Tous les workflows échouent vite, mais plus par une chaîne de dépendances entre jobs (supprimée par l’ADR 0061). Le fail-fast vient désormais de deux mécanismes : le job changes court-circuite par chemin (une PR documentaire saute tout le travail coûteux) ; et le parallélisme fait remonter chaque échec dès que le job concerné finit, sans attendre les autres — un lint qui casse n’a pas à patienter derrière 5 min de build.

Pour accélérer la CI, Atlas utilise le cache distribué Turborepo (TURBO_TOKEN côté secrets). Quand un job construit un projet, son résultat est mis en cache ; si le code source du projet n’a pas changé, le job suivant le réutilise tel quel.

Conséquence : une pull request qui modifie un seul sous-projet ne reconstruit pas tout le dépôt, seulement ce qui est touché.