Gestion du monorepo
Le projet Charly est organisé sous la forme d'un monorepo, une structure qui centralise toutes les applications (apps/web, apps/cli, apps/docs), et les packages partagés (packages/db, packages/shared) dans un seul dépôt Git. Cette approche simplifie la gestion des dépendances, la cohérence du code, et les déploiements. Le monorepo est orchestré par Turborepo, un outil performant pour gérer les builds, les tests, et les tâches à travers plusieurs projets. Cette page explique comment Turborepo est utilisé dans Charly, comment configurer et exécuter des tâches, et comment les packages partagés sont organisés avec Bun.
Pourquoi un monorepo ?
Un monorepo offre plusieurs avantages pour Charly :
- Centralisation : Tous les composants (frontend, backend, scraping, base de données) partagent une seule base de code, facilitant les mises à jour globales.
- Cohérence : Les types partagés (
packages/shared) et les schémas de base de données (packages/db) garantissent une typisation et une validation uniformes. - Efficacité : Turborepo optimise les builds en mettant en cache les résultats, réduisant les temps d'exécution.
- Collaboration : Les développeurs travaillent dans un seul dépôt, simplifiant les contributions et la revue de code.
Tutoriel rapide sur Turborepo
Turborepo est configuré dans le fichier turbo.json à la racine du monorepo. Il définit des tâches (comme build, dev, test) qui peuvent être exécutées sur l'ensemble ou une partie des projets. Voici un guide pour comprendre et utiliser Turborepo dans Charly.
1. Structure de turbo.json
Le fichier turbo.json définit les tâches disponibles et leurs comportements. Voici un extrait simplifié du fichier utilisé dans Charly :
{
"tasks": {
"build": {
"outputs": ["dist/**"]
},
"dev": {
"persistent": true,
"cache": false
},
"test": {
"dependsOn": ["^build"]
},
"dev:web": {
"persistent": true,
"cache": false
},
"db:migrate": {
"cache": false,
"interactive": true
}
}
}- Clés principales :
outputs: Spécifie les dossiers à mettre en cache (ex.dist/**pour les builds).persistent: Indique si la tâche reste active (ex. pour les serveurs de développement).cache: Active ou désactive la mise en cache (désactivé pourdevoudb:migrate).interactive: Permet une interaction avec l'utilisateur (ex. pour les migrations de base de données).dependsOn: Définit les dépendances entre tâches (ex.testdépend debuild).
2. Ajouter une nouvelle tâche
Pour ajouter une nouvelle tâche, modifiez turbo.json. Par exemple, pour ajouter une tâche lint qui exécute ESLint sur tous les projets :
- Ajoutez la tâche dans
turbo.json:
{
"tasks": {
"lint": {
"cache": false
}
}
}- Définissez le script correspondant dans les
package.jsondes projets concernés (ex.apps/web/package.json) :
{
"scripts": {
"lint": "eslint src/**/*.{ts,tsx}"
}
}- Exécutez la tâche avec :
bun run lintTurborepo exécutera lint pour tous les projets qui définissent ce script.
3. Exécuter des tâches
Pour exécuter une tâche, utilisez la commande bun run <tâche> à la racine du monorepo. Exemples :
- Lancer le mode développement pour le frontend et le backend :
bun run dev- Construire tous les projets :
bun run build- Appliquer les migrations de base de données :
bun run db:migrate4. Tâches interactives
Les tâches marquées comme "interactive": true (ex. db:migrate, db:push) permettent une interaction avec l'utilisateur. Par exemple, db:migrate peut demander une confirmation avant d'appliquer les migrations. Ces tâches sont exécutées sans mise en cache pour garantir que les changements sont toujours appliqués.
5. Interface utilisateur du terminal
Turborepo propose une interface terminale claire pour suivre l'exécution des tâches. Lorsqu'une tâche est lancée (ex. bun run build), Turborepo affiche :
- Les projets concernés.
- L'état de chaque tâche (en cours, terminé, erreur).
- Les résultats du cache (hit ou miss).
Pour une sortie plus détaillée, ajoutez l'option --log-level=debug :
bun run build --log-level=debug6. Persistance des tâches
Les tâches marquées comme "persistent": true (ex. dev, dev:web) restent actives après leur lancement, ce qui est idéal pour les serveurs de développement. Par exemple :
bun run dev:weblance le serveur Next.js pourapps/webet reste actif pour recharger automatiquement les modifications.
Ces tâches sont configurées avec "cache": false pour éviter de mettre en cache des états intermédiaires.
7. Filtres
Turborepo permet de limiter l'exécution des tâches à certains projets en utilisant des filtres. Dans Charly, les filtres sont souvent utilisés dans les scripts du package.json racine. Exemple :
{
"scripts": {
"dev:web": "turbo run dev --filter=@charly/web"
}
}--filter=@charly/webexécute la tâchedevuniquement pour le projetapps/web.- Vous pouvez aussi utiliser des motifs comme
--filter=...@charly/dbpour inclure un projet et ses dépendances.
Pour exécuter une tâche sur un projet spécifique manuellement :
bun turbo run build --filter=@charly/webLe champ exports dans package.json
Le champ exports dans les package.json des packages partagés (packages/shared, packages/db) définit les points d'entrée pour accéder aux modules. Cela garantit une importation claire et typée des fichiers. Exemple dans packages/shared/package.json :
{
"name": "@charly/shared",
"exports": {
".": "./src/index.ts",
"./types": "./src/types.ts"
}
}".": Pointe vers le fichier principal (src/index.ts), importé avecimport { something } from '@charly/shared'."./types": Permet d'importer des types spécifiques avecimport { router } from '@charly/shared/types'.
Dans packages/db/package.json, le champ exports est plus granulaire :
{
"name": "@charly/db",
"exports": {
".": "./src/index.ts",
"./*": "./src/*"
}
}"./*": "./src/*": Permet d'importer n'importe quel fichier du dossiersrc/directement, commeimport { events } from '@charly/db/schema/events'.
Ce mécanisme assure que les applications (web, backend, cli) peuvent accéder aux modules partagés de manière prévisible et sans exposer de fichiers internes.
Workspaces avec Bun
Le monorepo utilise les workspaces pour gérer les dépendances entre les projets et les packages partagés. Dans le package.json racine, les workspaces sont définis ainsi :
{
"workspaces": ["apps/*", "packages/*"]
}apps/*: Inclutapps/web,apps/backend, etapps/cli.packages/*: Inclutpackages/dbetpackages/shared.
Bun reconnaît ces workspaces et résout automatiquement les dépendances locales. Par exemple, si apps/web dépend de @charly/db, Bun utilise la version locale dans packages/db plutôt que de télécharger une version externe.
Avantages des workspaces
- Dépendances locales : Les packages partagés sont toujours synchronisés avec la dernière version locale.
- Installation unique :
bun installà la racine installe toutes les dépendances des workspaces. - Typage cohérent : Les types partagés (
@charly/shared) sont disponibles partout sans configuration supplémentaire.
Installer localement un package partagé
Pour ajouter un package partagé (ex. @charly/db) à une application ou un autre package, suivez ces étapes :
Vérifiez que le package est défini : Assurez-vous que
packages/db/package.jsonexiste et contient un champname(ex."name": "@charly/db").Ajoutez la dépendance : Dans le
package.jsonde l'application cible (ex.apps/web/package.json), ajoutez@charly/dbcomme dépendance :json{ "dependencies": { "@charly/db": "workspace:*" } }"workspace:*"indique à Bun d'utiliser la version locale du package.
Mettez à jour les dépendances : À la racine du monorepo, exécutez :
bashbun installCela lie
@charly/dbàapps/websans télécharger de version externe.Utilisez le package : Dans le code de
apps/web, vous pouvez maintenant importer des modules depuis@charly/db. Exemple :tsimport { db } from "@charly/db"; import { events } from "@charly/db/schema/events";Vérifiez les types : Grâce au champ
exportset à TypeScript, les importations sont typées automatiquement. Assurez-vous que letsconfig.jsonde l'application inclut les chemins nécessaires (ex."paths": { "@/*": ["./src/*"] }).
Exemple pratique
Pour ajouter @charly/shared à apps/web :
- Modifiez
apps/web/package.jsonpour inclure :
{
"dependencies": {
"@charly/shared": "workspace:*"
}
}- Exécutez
bun installà la racine. - Importez les types dans
apps/web/src/index.ts:
import { router } from "@charly/shared/types";Bonnes pratiques
- Nommez les tâches de manière cohérente : Utilisez des noms comme
build,dev,testpour toutes les applications. - Évitez la mise en cache pour les tâches dynamiques : Ajoutez
"cache": falsepour des tâches commedevoudb:migrate. - Utilisez des filtres pour les tâches ciblées : Par exemple,
--filter=@charly/webpour limiter l'exécution. - Testez les changements localement : Après avoir modifié un package partagé, exécutez
bun installet vérifiez que les applications consommatrices fonctionnent.