Structure du monorepo
Un monorepo est un seul dépôt Git qui héberge plusieurs projets logiciels, avec des règles partagées. Atlas en est un.
Atlas organise ses projets en neuf catégories. Huit relèvent du périmètre Node/TypeScript ; la neuvième, dataops/, héberge le code DataOps en Python (Dagster, dbt). Chaque catégorie a une responsabilité précise et des règles propres : framework imposé (un framework est un socle logiciel qui fournit une structure et des composants prêts à l’emploi, sur lesquels on bâtit son application au lieu de tout réécrire), dépendances autorisées ou interdites, présence ou absence d’un point d’entrée exécutable, etc. Le placement d’un sous-projet dans une catégorie indique d’emblée son rôle, et les règles des huit catégories Node sont vérifiées automatiquement par pnpm audit:structure (dataops/, en Python, en est exempté — voir ADR 0055).
Vue d’ensemble
Section intitulée « Vue d’ensemble »Les catégories répartissent le code selon ce qu’il fait et qui le consomme : des applications livrées aux utilisateurs (apps/), des bibliothèques de logique métier réutilisables (packages/), des serveurs HTTP de backend (services/), des outils en ligne de commande (cli/), des composants d’interface partagés (ui/), des fichiers statiques versionnés (assets/), des configurations communes (config/), des environnements de test isolés (sandbox/) et le code DataOps en Python (dataops/). Cette répartition est utile parce qu’elle rend le rôle d’un sous-projet lisible d’un coup d’œil et permet d’imposer à chaque catégorie ses propres contraintes (framework, dépendances, publication). Le tableau ci-dessous résume, pour chaque catégorie, son contenu, sa convention de nommage et si elle est publiée sur npm — le registre public de paquets JavaScript/TypeScript.
| Catégorie | Contenu | Convention de nommage | Publié sur npm ? |
|---|---|---|---|
apps/ | Applications web destinées aux utilisateurs finaux | @univ-lehavre/atlas-<nom-du-dossier> | Non (private: true) |
assets/ | Fichiers statiques versionnés (logos, images, polices) | @univ-lehavre/atlas-<nom> | Variable |
packages/ | Bibliothèques TypeScript réutilisables | @univ-lehavre/atlas-<nom> | Oui |
services/ | Serveurs HTTP déployés en backend | @univ-lehavre/atlas-<nom> | Oui |
cli/ | Outils en ligne de commande, courts, qui consomment les libs | @univ-lehavre/atlas-<nom>-cli (dossier sans -cli) | Oui |
ui/ | Composants d’interface Svelte partagés | @univ-lehavre/atlas-ui | Variable |
config/ | Configurations communes (style, types, formatage) | @univ-lehavre/atlas-<nom>-config | Oui |
sandbox/ | Environnements Docker pour tests d’intégration | Pas de convention spécifique | Non |
dataops/ | Code DataOps en Python (assets Dagster, modèles dbt) | Dossier Python (uv) ; hors graphe pnpm | Non |
Diagramme des dépendances
Section intitulée « Diagramme des dépendances »Le diagramme ci-dessous montre quelles catégories ont le droit de dépendre de quelles autres. On le regarde pour vérifier que les dépendances vont toujours dans le bon sens : le code réutilisable (en bas) ne doit jamais dépendre du code qui le consomme (en haut), ce qui garantit un graphe sans cycle et des bibliothèques restant indépendantes de leurs usages.
Les flèches indiquent « utilise ». Les projets en haut dépendent de ceux en bas, jamais l’inverse.
graph TB
APPS["apps/<br/>(applications)"]
CLI["cli/<br/>(ligne de commande)"]
SERVICES["services/<br/>(serveurs HTTP)"]
UI["ui/<br/>(composants Svelte)"]
PACKAGES["packages/<br/>(bibliothèques)"]
ASSETS["assets/<br/>(fichiers statiques)"]
CONFIG["config/<br/>(configurations)"]
APPS --> UI
APPS --> PACKAGES
CLI --> PACKAGES
CLI --> ASSETS
SERVICES --> PACKAGES
UI --> PACKAGES
APPS -.-> CONFIG
PACKAGES -.-> CONFIG
SERVICES -.-> CONFIG
CLI -.-> CONFIG
UI -.-> CONFIG
Trait plein : dépendance d’exécution. Trait pointillé : dépendance de développement (configurations TypeScript, ESLint, Prettier).
sandbox/ est isolé : aucune autre catégorie ne peut dépendre d’un projet de sandbox/.
dataops/ (Python) ne figure pas dans ce graphe : il ne dépend d’aucun paquet TypeScript et n’est dépendu par aucun. Sa seule interface avec le reste du dépôt est le contrat Parquet + manifest.json sur le stockage objet (ADR 0055), pas une dépendance de code.
Principes par catégorie
Section intitulée « Principes par catégorie »Chaque catégorie a une responsabilité unique. Les règles ci-dessous sont enforcées par le script pnpm audit:structure (cf. scripts/audit/workspace-structure.mjs).
apps/ — applications utilisateur
Section intitulée « apps/ — applications utilisateur »Rôle. Front-end SvelteKit déployable, livré à des utilisateurs finaux. Chaque app a son cycle de vie (déploiement, versionnage applicatif via Changesets, bundle) indépendant.
Règles.
- Le nom du paquet est
@univ-lehavre/atlas-<nom-du-dossier>(le préfixeatlas-est ajouté automatiquement si le dossier ne commence pas paratlas-). private: true— une app n’est jamais publiée sur npm.- Doit dépendre de
@sveltejs/kitetsvelte. - Pas de
binfield (une app n’est pas un exécutable en ligne de commande). - Ne peut pas dépendre d’une autre app — le code partagé doit être extrait vers
packages/ouui/.
packages/ — bibliothèques réutilisables
Section intitulée « packages/ — bibliothèques réutilisables »Rôle. Code métier, pur, sans dépendance vers une interface utilisateur ni vers un terminal. C’est le cœur logique du dépôt, consommé par apps/, services/, cli/ et ui/.
Règles.
- Pas de
binfield — les exécutables en ligne de commande vivent danscli/. - Pas de dépendance d’I/O terminal. Les bibliothèques d’interaction CLI (
@clack/prompts,yargs,commander,meow,inquirer,prompts) sont interdites endependencies. - Pas d’import de modules terminal.
node:readline,node:tty,'readline','tty'sont interdits dans les sourcespackages/*/src/. - Pas de Svelte ni SvelteKit en
dependencies. Si une bibliothèque a besoin de types Svelte, les déclarer enpeerDependencies(optionnel) ou endevDependencies(pour le développement). Les imports runtime desvelte,@sveltejs/kit,$app/,$lib/sont interdits dans les sources. - Pas de framework HTTP de routage (
hono,express,fastify,koa,polka) — le routage HTTP appartient àservices/. - Objectif de couverture de tests élevé (cible 100 %), car ces bibliothèques sont consommées partout.
services/ — serveurs HTTP
Section intitulée « services/ — serveurs HTTP »Rôle. Serveurs HTTP déployés en backend (par exemple : un service de proxy vers REDCap). Utilisent le framework Hono.
Règles.
- Pas de
binfield. - Doit dépendre de
hono. - Doit dépendre d’au moins un paquet
@univ-lehavre/atlas-*interne — la logique métier vit danspackages/, le service est une fine couche de routage. - Ne peut pas dépendre d’un autre service — la logique commune doit être extraite vers
packages/.
cli/ — outils en ligne de commande
Section intitulée « cli/ — outils en ligne de commande »Rôle. Exécutables Node.js exposés à l’utilisateur en ligne de commande. Restent fins : parsent les arguments, appellent une bibliothèque de packages/, formatent la sortie.
Règles.
- Le dossier ne doit pas se terminer par
-cli. Exemple :cli/net/(pascli/net-cli/). - Le paquet doit se terminer par
-clisauf exception explicitée dans le script d’audit (cas historique :@univ-lehavre/atlas-crf-openapi). - Doit avoir un
binfield pointant vers l’exécutable. - Doit dépendre d’au moins un paquet
@univ-lehavre/atlas-*interne — la logique métier vit danspackages/, le CLI est une fine couche d’I/O terminal. - C’est ici que vivent les dépendances d’I/O terminal (
@clack/prompts,yargs, etc.).
ui/ — composants d’interface partagés
Section intitulée « ui/ — composants d’interface partagés »Rôle. Bibliothèque de composants Svelte réutilisés par plusieurs applications.
Règles.
- Pas de
binfield. sveltedoit être déclaré enpeerDependencies, pas endependencies— l’application hôte fournit sa version.- Pas de dépendances CLI I/O ni HTTP de routage.
- Pas d’imports server-only dans les sources :
@sveltejs/kit/node,server-only,$env/static/private,$env/dynamic/privatesont interdits. La logique serveur appartient aux hooks SvelteKit des applications (apps/<nom>/src/hooks.server.ts).
config/ — configurations communes
Section intitulée « config/ — configurations communes »Rôle. Paquets qui exportent des configurations partagées (préréglages ESLint, TypeScript, Prettier, etc.). Consommés par tous les autres projets.
Règles.
- Pas de
binfield. - Doit pouvoir être consommé via
importdans un fichier de configuration (par exempleeslint.config.jsqui importe@univ-lehavre/atlas-shared-config).
assets/ — fichiers statiques
Section intitulée « assets/ — fichiers statiques »Rôle. Fichiers statiques versionnés : logos, images, polices, données figées. Aucun code exécutable. Consommés par les apps (souvent via un CLI d’installation qui copie les fichiers vers le dossier static/ de l’app).
Règles.
- Pas de
binfield — un outil d’installation va danscli/, jamais ici. - Pas de dépendances runtime (
dependenciesdoit être vide ou absent). Les devDependencies sont autorisées (par exemplevitestpour vérifier la présence des fichiers). - Peut être publié sur npm (cas de
@univ-lehavre/atlas-logos) ou privé selon le besoin.
sandbox/ — environnements de test
Section intitulée « sandbox/ — environnements de test »Rôle. Stacks Docker Compose pour reproduire localement les dépendances externes (Appwrite, REDCap, Mailpit, etc.) afin d’exécuter les tests d’intégration et les scénarios end-to-end.
Règles.
- Isolation totale : aucune autre catégorie (
apps/,packages/, etc.) ne peut dépendre d’un paquet desandbox/. Les dépendances vont uniquement dans l’autre sens :sandbox/peut importer du code du dépôt pour ses besoins propres. - Pas publié sur npm.
dataops/ — code DataOps en Python
Section intitulée « dataops/ — code DataOps en Python »Rôle. Le code de la plateforme DataOps (application au traitement de données des pratiques d’automatisation et de qualité du DevOps) : assets Dagster (orchestrateur de pipelines de données), modèles dbt (transformation SQL versionnée), code de synchronisation de données. Ces outils sont écrits en Python et n’ont pas d’équivalent TypeScript ; cette catégorie accueille donc du Python natif, à rebours du reste du dépôt (ADR 0055).
Règles.
- Python natif, outillé par uv (environnement et dépendances), ruff (lint et format) et pytest (tests). Le
uv.lockest versionné. - Au workspace, mais pour le cache seulement : les deux code-locations Dagster (
dataops/citation-dagster,dataops/mediawatch-dagster) sont déclarées danspnpm-workspace.yamlvia unpackage.jsonprivé. Cette déclaration sert uniquement à les faire entrer dans le cache Turbo (ADR 0066) : leurs scriptslint:py,test:pyetmanifests:pydélèguent à uv/ruff/pytest, et Turbo mémorise leurs résultats. En contrepartie, knip les exclut explicitement etaudit:structureignore le Python — sa discipline relève de ruff/pytest. - Frontière par le contrat :
dataops/ne dépend d’aucun paquet TypeScript et n’est dépendu par aucun. Sa seule interface avec le reste du dépôt est le contrat Parquet +manifest.jsonsur le stockage objet (ADR 0029 / 0054). - Pas publié sur npm.
Conventions transverses
Section intitulée « Conventions transverses »Nommage des paquets
Section intitulée « Nommage des paquets »- Tous les paquets internes sont préfixés par
@univ-lehavre/atlas-. - Le chemin du dossier dans le dépôt correspond au suffixe :
packages/auth/→@univ-lehavre/atlas-auth.
repository.directory
Section intitulée « repository.directory »Si le package.json déclare un champ repository.directory, il doit correspondre au chemin réel du paquet dans le dépôt. Permet à npm d’afficher correctement le lien « source » dans la page du paquet.
Pas de cycles de dépendances
Section intitulée « Pas de cycles de dépendances »Le script d’audit détecte les cycles dans le graphe des dépendances internes (@univ-lehavre/atlas-* → @univ-lehavre/atlas-*) et fait échouer la vérification. Un cycle signale une responsabilité mal placée : il faut extraire la zone commune dans un nouveau paquet packages/*.
Vérifier la structure
Section intitulée « Vérifier la structure »Avant d’ouvrir une pull request qui ajoute, déplace ou modifie un sous-projet :
pnpm audit:structureLe script signale, ligne par ligne, chaque écart par rapport aux règles ci-dessus. Il est lancé en CI dans le workflow ci.yml (job audit).
Outils du monorepo
Section intitulée « Outils du monorepo »Trois outils principaux organisent la vie quotidienne du dépôt :
- pnpm (gestionnaire de paquets) installe les dépendances en partageant un cache global et isole chaque sous-projet via les workspaces (fichier
pnpm-workspace.yaml) - turbo (orchestrateur de tâches) parallélise les commandes (
build,test,lint…) à travers les sous-projets et met en cache les résultats : un projet déjà construit n’est pas reconstruit - Changesets gère le versionnage : chaque pull request qui modifie un paquet publiable joint un fichier
.changeset/*.mddécrivant le changement et son impact (patch,minor,major)
Répertoires hors-workspace
Section intitulée « Répertoires hors-workspace »Les huit catégories ci-dessus décrivent les sous-projets (workspaces pnpm). À côté, quelques répertoires racine ne sont pas des workspaces : ils portent l’outillage, les correctifs de dépendances et les données de test. Chacun a son propre README.
| Répertoire | Rôle |
|---|---|
scripts/ | Automatisations transverses, non publiables : audits (audit/), génération de documentation (docs/), publication (release/), trames CRF (crf-trame/). |
patches/ | Correctifs locaux appliqués à des dépendances externes (patches pnpm), versionnés et documentés. |
fixtures/ | Jeux de données de test versionnés (ex. dictionnaires REDCap anonymisés), consommés par les tests d’intégration. |
scripts/regroupe ce qui fait vivre le dépôt sans être livré sur npm : l’audit de structure (audit/), les générateurs de la documentation (docs/— carte des paquets, référence API, statistiques du dépôt), les scripts de publication (release/) et la génération des trames et fixtures CRF (crf-trame/).patches/conserve les correctifs maintenus dans le dépôt pour ajuster une dépendance externe quand c’est nécessaire — chaque patch documente une modification locale appliquée par le gestionnaire de paquets.fixtures/porte les données de test partagées : par exemplecrf-projects/, des dictionnaires de données REDCap-importables et anonymisés (substitution déterministe de fake names, aucune donnée réelle), générés parscripts/crf-trame/.