Skip to content

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 :

json
{
  "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é pour dev ou db: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. test dépend de build).

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 :

  1. Ajoutez la tâche dans turbo.json :
json
{
  "tasks": {
    "lint": {
      "cache": false
    }
  }
}
  1. Définissez le script correspondant dans les package.json des projets concernés (ex. apps/web/package.json) :
json
{
  "scripts": {
    "lint": "eslint src/**/*.{ts,tsx}"
  }
}
  1. Exécutez la tâche avec :
bash
bun run lint

Turborepo 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 :
bash
bun run dev
  • Construire tous les projets :
bash
bun run build
  • Appliquer les migrations de base de données :
bash
bun run db:migrate

4. 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 :

bash
bun run build --log-level=debug

6. 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:web lance le serveur Next.js pour apps/web et 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 :

json
{
  "scripts": {
    "dev:web": "turbo run dev --filter=@charly/web"
  }
}
  • --filter=@charly/web exécute la tâche dev uniquement pour le projet apps/web.
  • Vous pouvez aussi utiliser des motifs comme --filter=...@charly/db pour inclure un projet et ses dépendances.

Pour exécuter une tâche sur un projet spécifique manuellement :

bash
bun turbo run build --filter=@charly/web

Le 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 :

json
{
  "name": "@charly/shared",
  "exports": {
    ".": "./src/index.ts",
    "./types": "./src/types.ts"
  }
}
  • "." : Pointe vers le fichier principal (src/index.ts), importé avec import { something } from '@charly/shared'.
  • "./types" : Permet d'importer des types spécifiques avec import { router } from '@charly/shared/types'.

Dans packages/db/package.json, le champ exports est plus granulaire :

json
{
  "name": "@charly/db",
  "exports": {
    ".": "./src/index.ts",
    "./*": "./src/*"
  }
}
  • "./*": "./src/*" : Permet d'importer n'importe quel fichier du dossier src/ directement, comme import { 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 :

json
{
  "workspaces": ["apps/*", "packages/*"]
}
  • apps/* : Inclut apps/web, apps/backend, et apps/cli.
  • packages/* : Inclut packages/db et packages/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 :

  1. Vérifiez que le package est défini : Assurez-vous que packages/db/package.json existe et contient un champ name (ex. "name": "@charly/db").

  2. Ajoutez la dépendance : Dans le package.json de l'application cible (ex. apps/web/package.json), ajoutez @charly/db comme dépendance :

    json
    {
      "dependencies": {
        "@charly/db": "workspace:*"
      }
    }
    • "workspace:*" indique à Bun d'utiliser la version locale du package.
  3. Mettez à jour les dépendances : À la racine du monorepo, exécutez :

    bash
    bun install

    Cela lie @charly/db à apps/web sans télécharger de version externe.

  4. Utilisez le package : Dans le code de apps/web, vous pouvez maintenant importer des modules depuis @charly/db. Exemple :

    ts
    import { db } from "@charly/db";
    import { events } from "@charly/db/schema/events";
  5. Vérifiez les types : Grâce au champ exports et à TypeScript, les importations sont typées automatiquement. Assurez-vous que le tsconfig.json de l'application inclut les chemins nécessaires (ex. "paths": { "@/*": ["./src/*"] }).

Exemple pratique

Pour ajouter @charly/shared à apps/web :

  • Modifiez apps/web/package.json pour inclure :
json
{
  "dependencies": {
    "@charly/shared": "workspace:*"
  }
}
  • Exécutez bun install à la racine.
  • Importez les types dans apps/web/src/index.ts :
ts
import { router } from "@charly/shared/types";

Bonnes pratiques

  • Nommez les tâches de manière cohérente : Utilisez des noms comme build, dev, test pour toutes les applications.
  • Évitez la mise en cache pour les tâches dynamiques : Ajoutez "cache": false pour des tâches comme dev ou db:migrate.
  • Utilisez des filtres pour les tâches ciblées : Par exemple, --filter=@charly/web pour limiter l'exécution.
  • Testez les changements localement : Après avoir modifié un package partagé, exécutez bun install et vérifiez que les applications consommatrices fonctionnent.