Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Conventional Commits : normaliser vos messages de commit

Par mis à jour le

Améliorer cette page

Conventional Commits est une convention d'écriture des messages de commit de la forme type(portée): description, par exemple fix(auth): corriger l'expiration des sessions. Ce format rend l'historique lisible d'un coup d'œil et, surtout, exploitable par des outils : ils en tirent un changelog, calculent le prochain numéro de version et refusent un message mal formé en CI. Cette leçon s'adresse à qui sait déjà créer un commit et travaille sur un dépôt partagé. Vous y apprendrez à écrire un message conforme, à choisir son type, à signaler un changement cassant, puis à faire vérifier la convention par commitlint, sur votre poste et dans un pipeline.

  • Écrire un message conforme : type, portée, description, corps et pied
  • Choisir le type qui correspond à la modification
  • Signaler un changement cassant avec ! ou BREAKING CHANGE:
  • Bloquer un message non conforme avant le commit avec commitlint et husky
  • Faire vérifier les messages d'une demande de fusion (pull request ou merge request) par la CI
  • Relier chaque type à l'incrément de version qu'il déclenche

La convention elle-même ne demande rien d'autre que Git. L'outillage présenté ensuite, en revanche, repose sur l'écosystème Node.js :

  • Git et un dépôt où vous savez déjà créer des commits ;
  • Node.js 22.12 ou plus récent, idéalement une version LTS maintenue (22 ou 24), avec npm ;
  • un package.json à la racine du dépôt, créé au besoin par npm init -y ;
  • le package-lock.json produit par l'installation, commité : le job de CI l'utilise avec npm ci, qui échoue sans lui.

Un dépôt d'administration (scripts shell, playbooks, configurations) n'a souvent ni Node.js ni package.json. Y installer Node.js pour vérifier des messages de commit est un choix discutable : la convention s'y applique pareil, et la vérification peut se faire par un simple hook en shell, présenté plus bas, au prix de règles moins complètes que celles de commitlint.

Conventional Commits est une spécification, pas un outil : un texte court, publié en version 1.0.0 sur conventionalcommits.org, qui fixe la forme d'un message de commit. Elle s'inspire directement des conventions de commit du projet Angular et poursuit un double objectif : qu'un humain comprenne la nature d'un changement en lisant sa première ligne, et qu'un programme puisse la lire aussi. C'est ce second point qui fait sa valeur en équipe. Un historique où chaque correctif commence par fix: et chaque nouveauté par feat: peut être trié, filtré et transformé automatiquement en notes de version, sans que personne ne relise les commits un par un. La convention ne remplace pas les règles de base d'un bon message, titre court et corps qui explique le pourquoi, enseignées dans Enregistrer des modifications : elle les complète par une structure commune que les outils savent analyser.

Un message conforme commence par un type, suivi d'une portée facultative entre parenthèses, de deux-points, d'une espace, puis d'une description courte. Un corps et des pieds facultatifs peuvent suivre, chacun séparé du reste par une ligne vide. Voici le gabarit défini par la spécification :

<type>[(portée)][!]: <description>
[corps facultatif]
[pied(s) facultatif(s)]

Chaque partie a un rôle précis. Le type dit la nature du changement, la portée (scope en anglais) la zone du projet touchée, la description résume le changement en une ligne. Le corps explique le contexte et la raison ; les pieds (footers) portent des métadonnées au format Clé: valeur, comme une référence de ticket ou l'annonce d'un changement cassant.

L'exemple suivant montre un correctif complet, avec corps et pied. Il passe la vérification de commitlint présentée plus bas :

fix(auth): corriger l'expiration prématurée des sessions
Les sessions expiraient au bout de 5 minutes au lieu des 30 minutes
configurées : le délai était lu en secondes au lieu de minutes.
Refs: #142

Trois erreurs reviennent souvent chez les débutants : oublier l'espace après les deux-points (feat:ajouter n'est pas reconnu), coller le pied au corps sans ligne vide, et terminer la description par un point final. Les deux premières cassent l'analyse par les outils ; la troisième est refusée par la configuration de commitlint présentée plus bas.

La spécification n'impose que deux types, feat et fix ; les autres viennent de la convention Angular et sont devenus un standard de fait. feat annonce une nouvelle fonctionnalité, fix une correction de bogue. La configuration @commitlint/config-conventional, la plus utilisée, accepte en plus neuf types et refuse tout le reste. Le tableau les présente avec un exemple, à lire comme la liste fermée que votre équipe adoptera par défaut.

TypeQuand l'utiliserExemple
featNouvelle fonctionnalité visiblefeat(ui): ajouter un mode sombre
fixCorrection de boguefix(api): corriger le format de réponse JSON
docsDocumentation seuledocs: mettre à jour les instructions d'installation
styleMise en forme sans effet sur la logiquestyle(css): supprimer les marges inutiles
refactorRestructuration sans changement de comportementrefactor(api): simplifier les conditions de validation
perfAmélioration des performancesperf(cache): mettre en cache les prévisions pendant 10 minutes
testAjout ou correction de teststest(auth): ajouter les tests unitaires de la connexion
buildSystème de build, dépendancesbuild(deps): passer lodash en 4.17.21
ciConfiguration de la CIci: vérifier les messages de commit dans les merge requests
choreMaintenance qui ne touche ni le code livré ni les testschore: mettre à jour la configuration de l'éditeur
revertAnnulation d'un commit précédentrevert: annuler l'ajout du mode sombre

La spécification déclare les types insensibles à la casse, mais commitlint les exige en minuscules : Fix: ou FEAT: sont refusés. Retenez la forme minuscule, la seule qui passe partout.

La portée indique quelle partie du projet est touchée : auth, api, ui, deps. Elle est facultative, mais elle rend un long historique filtrable : retrouver tous les changements du module d'authentification revient à chercher (auth). Elle prend tout son sens sur un projet de taille moyenne ou un monorepo, un dépôt unique qui regroupe plusieurs applications. Fixez la liste des portées en équipe et documentez-la, sinon auth, authentification et login désigneront la même chose.

Omettez la portée quand le changement concerne l'ensemble du projet ou ne se rattache à aucune zone précise. Un message comme chore: mettre à jour la configuration de build reste parfaitement conforme : la cohérence compte plus que l'exhaustivité.

Un changement cassant se signale par un ! juste avant les deux-points, par un pied BREAKING CHANGE:, ou par les deux. Un changement cassant (breaking change) oblige les utilisateurs du projet à adapter leur code ou leur configuration : suppression d'une route d'API, changement de format, option renommée. Le signaler n'est pas une formalité, c'est ce qui permet aux outils de publier une version majeure au lieu d'un simple correctif. Le ! attire l'œil dans la première ligne de l'historique ; le pied explique ce qui casse et comment migrer. Le mot-clé BREAKING CHANGE est le seul élément sensible à la casse de toute la spécification : il s'écrit en majuscules, suivi de deux-points, et la forme BREAKING-CHANGE est acceptée comme synonyme. La spécification le place après une ligne vide, comme tout pied ; commitlint signale un pied collé au corps par un avertissement. Combiner les deux marqueurs est la pratique la plus lisible :

feat(api)!: renvoyer les dates au format ISO 8601
Le champ date passe du format JJ/MM/AAAA au format AAAA-MM-JJ.
BREAKING CHANGE: les clients qui lisent le champ date doivent adapter
leur analyse.

Parce qu'un outil peut en déduire le prochain numéro de version et le changelog, sans intervention humaine. La correspondance avec le versionnage sémantique (SemVer, numéros MAJEUR.MINEUR.CORRECTIF) est fixée par la spécification elle-même. Le tableau résume ce que chaque marqueur déclenche, en partant d'une version 1.4.2.

CommitIncrément SemVerVersion suivante
fix: …Correctif (PATCH)1.4.3
feat: …Mineur (MINOR)1.5.0
feat!: … ou pied BREAKING CHANGE:Majeur (MAJOR)2.0.0
docs:, chore:, refactor:, style:, test:…Aucun par défaut1.4.2

Entre deux versions, plusieurs commits s'accumulent : l'outil retient l'incrément le plus fort. Un fix: et un feat: publiés ensemble donnent donc 1.5.0, car la version mineure remet le correctif à zéro ; un seul changement cassant suffit à passer en 2.0.0.

Des outils comme semantic-release et release-please lisent les commits depuis la dernière étiquette, appliquent ce tableau et rédigent le changelog. semantic-release publie directement la version et son étiquette ; release-please ouvre d'abord une pull request de version, et c'est sa fusion qui crée l'étiquette. Les règles par défaut de semantic-release publient aussi un correctif pour un commit perf ou revert, que la spécification ne mentionne pas. Attention, avec son préréglage par défaut (angular), semantic-release ne reconnaît pas le ! : seul le pied BREAKING CHANGE: y déclenche une version majeure, sauf à configurer le préréglage conventionalcommits. standard-version, encore cité dans de nombreux tutoriels, n'est plus maintenu. La génération du changelog et la publication des notes de version côté mainteneur sont détaillées dans la leçon Maintenir un projet.

Les commits ne servent pas qu'aux outils de publication. Quand l'équipe fusionne ses pull requests en squash, le message du commit écrasé reprend en général le titre de la PR, selon la forge et son réglage : GitHub, par défaut, garde plutôt le message du commit quand la PR n'en contient qu'un. C'est pourquoi la leçon précédente, Pull requests et code review, recommandait déjà un titre au format conventionnel : une convention appliquée aux commits doit l'être aussi aux titres de PR.

commitlint vérifie un message contre les règles de la convention, et husky le déclenche automatiquement à chaque git commit. commitlint est un outil Node.js ; husky installe des hooks Git, des scripts que Git exécute à des moments précis, ici le hook commit-msg qui reçoit le message avant que le commit soit créé. Un message refusé annule le commit, et l'auteur le corrige tout de suite au lieu de le découvrir en revue. La procédure ci-dessous s'applique à commitlint 21 (version de référence 21.2.3, la dernière publiée sur npm au 9 octobre 2026) et husky 9, sous Node.js 22 ou 24, dans un dépôt qui possède déjà un package.json.

  1. Installer les paquets en dépendances de développement :

    Fenêtre de terminal
    npm install --save-dev @commitlint/cli @commitlint/config-conventional husky
  2. Déclarer la configuration : commitlint charge les règles de config-conventional. L'extension .mjs indique à Node.js un module ECMAScript, quelle que soit la configuration du projet.

    Fenêtre de terminal
    echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.mjs
  3. Initialiser husky, puis créer le hook commit-msg :

    Fenêtre de terminal
    npx husky init
    echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg

    husky init ajoute le script "prepare": "husky" au package.json, ce qui réinstalle les hooks à chaque npm install chez vos collègues. Il crée aussi un hook .husky/pre-commit qui lance npm test : adaptez-le ou supprimez-le si votre projet n'a pas encore de tests, sinon chaque commit échouera. C'est le cas d'un package.json créé par npm init -y, dont le script test sort en erreur : le commit de l'étape suivante s'arrêterait sur husky - pre-commit script failed avant même que commitlint lise le message. Sans tests, retirez ce hook :

    Fenêtre de terminal
    rm .husky/pre-commit
  4. Vérifier avec un message volontairement non conforme :

    Fenêtre de terminal
    git commit -m "test"

    Le commit est refusé et commitlint explique pourquoi :

    ⧗ --- input ---
    test
    ✖ subject may not be empty [subject-empty]
    ✖ type may not be empty [type-empty]
    ✖ found 2 problems, 0 warnings
    ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
    husky - commit-msg script failed (code 1)

    Un message conforme, comme chore: initialiser le projet, crée le commit normalement.

Un réglage surprend souvent : la règle subject-case refuse une description qui commence par une majuscule, réflexe courant en français. fix: Corriger la TVA échoue, fix: corriger la TVA passe.

Si vous êtes encore sur commitlint 20. La version 21 exige Node.js 22.12 ou plus récent : un projet resté en Node.js 20, qui n'est plus maintenu, ne peut pas l'utiliser et reste sur la version 20. Les règles présentées ici sont les mêmes, seule la sortie change de forme, avec ⧗ input: test sur une seule ligne au lieu du bloc --- input ---.

Un dépôt sans Node.js peut vérifier la première ligne de chaque message avec un script shell de quelques lignes. Il contrôle la forme type(portée): description, la liste des types et la casse du type et de la portée : Fix: … et fix(Nginx): … sont refusés. Il ne vérifie en revanche ni la casse de la description ni son point final, que contrôle commitlint : fix: Corriger la TVA passe ici. Enregistrez-le dans un répertoire versionné, par exemple .githooks/commit-msg :

#!/bin/sh
# .githooks/commit-msg : vérifie la première ligne du message
premiere_ligne=$(head -n 1 "$1")
case "$premiere_ligne" in
"Merge "*|"Revert \""*|"fixup! "*|"squash! "*|"amend! "*) exit 0 ;;
esac
types='build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test'
if ! printf '%s\n' "$premiere_ligne" | grep -Eq "^($types)(\([a-z0-9-]+\))?!?: [^ ]"; then
printf '%s\n' "Message refusé : « $premiere_ligne »" >&2
printf '%s\n' "Format attendu : type(portée): description, types : $types" >&2
exit 1
fi

Le bloc case laisse passer cinq préfixes sans contrôle. Merge couvre le message proposé par git merge, qui exécute le hook. fixup! , squash! et amend! sont produits par git commit --fixup et git commit --squash, des commits provisoires qu'un git rebase --autosquash fond ensuite dans leur cible. git revert, lui, n'exécute pas le hook commit-msg : le motif Revert "…" ne sert qu'à accepter ce même message quand on le saisit à la main. Tout autre message proposé par Git est contrôlé comme le vôtre : après un git merge --squash, le message prérempli Squashed commit of the following: est refusé, et c'est à vous d'écrire un message conforme pour le commit qui regroupe la branche.

Rendez le script exécutable, indiquez à Git où trouver les hooks du dépôt, puis commitez-le pour que chaque clone le reçoive. Le réglage core.hooksPath est local à chaque clone : chaque membre de l'équipe doit le poser une fois, ce que husky automatisait par le script prepare.

Fenêtre de terminal
chmod +x .githooks/commit-msg
git config core.hooksPath .githooks
git add .githooks/commit-msg
git commit -m "chore: ajouter le hook commit-msg"
git ls-files -s .githooks/commit-msg

La dernière commande doit afficher le mode 100755 en première colonne : Git a enregistré le bit exécutable, et les clones recevront un script lançable. Depuis Windows, où chmod n'a pas d'effet sur l'index, on pose ce bit avec git update-index --chmod=+x .githooks/commit-msg avant de commiter.

Un git commit -m "test" est alors refusé avec le message Message refusé : « test », tandis que fix(nginx): augmenter le délai du proxy à 60 s passe. Un refus affiché Message refusé : « » signale une première ligne vide : le hook lit le message brut, avant que Git n'en retire les lignes vides, et le titre doit donc occuper la toute première ligne. Ce hook se contourne aussi par --no-verify : la vérification en CI reste nécessaire.

Un hook versionné est du code du dépôt, exécuté sur le poste de chaque développeur à chaque commit, avec ses droits. Une modification de .githooks/ se relit en revue comme n'importe quel script : celui qui la fait fusionner fait tourner son code chez toute l'équipe. Les hooks de husky, rangés dans .husky/, ont exactement la même propriété.

Que devient pre-commit quand core.hooksPath est posé ?

Section intitulée « Que devient pre-commit quand core.hooksPath est posé ? »

Poser core.hooksPath fait ignorer tout le répertoire .git/hooks/, pas seulement le hook commit-msg. La documentation de git config le précise : Git cherche alors ses hooks dans le répertoire indiqué, à la place de $GIT_DIR/hooks. Un hook pre-commit installé là par le framework pre-commit, qui lance par exemple gitleaks ou ansible-lint, cesse donc de tourner sans aucun message : les commits passent, les secrets aussi.

Le framework s'en protège à l'installation : tant que le réglage est posé, pre-commit install refuse de s'installer et le dit.

[ERROR] Cowardly refusing to install hooks with `core.hooksPath` set.
hint: `git config --unset-all core.hooksPath`

Un dépôt qui utilise déjà pre-commit n'a donc pas besoin de ce script. Il déclare la vérification du message comme un hook de l'étape commit-msg dans son .pre-commit-config.yaml, avec stages: [commit-msg], comme le montre la section Stages de hooks du guide pre-commit, puis l'active avec pre-commit install --hook-type pre-commit --hook-type commit-msg. Même conflit avec husky, qui pose lui-même core.hooksPath vers .husky/_ : un dépôt choisit un seul gestionnaire de hooks.

Commitizen pose les questions une à une et construit le message à votre place, ce qui aide pendant les premières semaines d'adoption. On l'initialise avec l'adaptateur cz-conventional-changelog, qui suit la même convention :

Fenêtre de terminal
npm install --save-dev commitizen
npx commitizen init cz-conventional-changelog --save-dev --save-exact
npx cz

La commande npx cz remplace git commit et affiche un menu de types (feat, fix…), puis demande portée, description et changement cassant. Côté éditeur, l'extension Conventional Commits de Visual Studio Code offre le même assistant dans l'interface de gestion de sources. Commitizen aide à écrire, commitlint vérifie : les deux se combinent.

Le hook local ne suffit pas : il se contourne avec git commit --no-verify et n'existe pas chez qui n'a pas lancé npm install. La vérification qui fait foi tourne donc dans le pipeline, sur chaque demande de fusion, avant la fusion. Le principe vaut pour toute CI : lancer commitlint sur la plage de commits de la demande de fusion et rendre le job bloquant. Le job ci-dessous, pour GitLab CI, où la demande de fusion s'appelle merge request, vérifie tous ses commits, de sa base jusqu'au dernier. GIT_DEPTH: 0 récupère l'historique complet : par défaut, GitLab ne clone que les 20 derniers commits, et commitlint ne trouverait pas le commit de base d'une longue merge request. L'image node:24-alpine ne contient pas Git, d'où son installation.

lint-commits:
stage: test
image: node:24-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1
variables:
GIT_DEPTH: 0
before_script:
- apk add --no-cache git
- npm ci
script:
- npx commitlint --from "$CI_MERGE_REQUEST_DIFF_BASE_SHA" --to "$CI_COMMIT_SHA" --verbose
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

Rendez ensuite ce job obligatoire en exigeant un pipeline réussi avant la fusion : aucun commit non conforme de la merge request n'est fusionné. La garantie s'arrête là. Le commit Merge branch '…' créé par la fusion par défaut n'est pas vérifié, le titre de la merge request (qui devient le message final en squash) se contrôle à part, et seule la protection de la branche interdit le push direct. La syntaxe rules: est détaillée dans le guide Conditions d'exécution GitLab CI/CD.

Pour réagir au type d'un commit, il faut tester son message avec la variable $CI_COMMIT_TITLE, jamais avec only:. C'est une erreur répandue, qui passe inaperçue tant qu'on ne vérifie pas quand le job part. Le mot-clé only: suivi d'une expression régulière compare le nom de la branche ou du tag, pas le message de commit : l'exemple suivant, à éviter, ne se déclenche que sur une branche ou un tag nommé feat… ou fix…. GitLab a d'ailleurs déprécié only: et except: au profit de rules:.

# ❌ à éviter : only compare le NOM DE LA BRANCHE OU DU TAG, pas le message de commit
deploy:
stage: deploy
script:
- echo "Déploiement"
only:
- /^feat|^fix/

La forme correcte teste la première ligne du message dans une règle rules:. Ce job ne part que sur la branche par défaut, et seulement pour un commit feat ou fix, avec ou sans portée ni ! :

deploy:
stage: deploy
script:
- echo "Déploiement de $CI_COMMIT_SHORT_SHA"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_COMMIT_TITLE =~ /^(feat|fix)(\(.+\))?!?:/

Cette règle ne voit que le dernier commit de la branche. Elle n'est fiable qu'avec la méthode de fusion fast-forward du projet : avec la méthode par commit de merge, le dernier commit s'appelle Merge branch '…' et la règle ne se déclenche pas, y compris quand la merge request est fusionnée en squash, GitLab ajoutant alors un commit de merge au-dessus du commit écrasé. Pour publier des versions, un outil comme semantic-release, qui lit tous les commits depuis la dernière étiquette, reste plus fiable.

Une convention ne tient que si l'équipe la décide, l'écrit et la fait vérifier par une machine. Les règles suivantes sont celles qui évitent les dérives les plus fréquentes une fois l'enthousiasme du premier mois passé, quand la discipline commence à se relâcher :

  • Liste fermée de types et de portées, documentée dans le dépôt (CONTRIBUTING.md) et reflétée dans commitlint.config.mjs. Ajouter un type est une décision d'équipe, pas une initiative individuelle.
  • Une description qui dit le résultat : fix(auth): corriger le délai d'expiration des sessions, jamais fix: bug.
  • Un commit, un changement : un commit qui mélange un correctif et une nouveauté ne peut porter qu'un seul type, et l'historique ment sur l'autre. Découpez, au besoin avec l'indexation partielle git add -p.
  • Vérification à deux niveaux : hook local pour corriger tôt, job CI bloquant sur les commits de la merge request.
  • Titres de PR au même format, indispensable avec la fusion en squash.
  • Révision périodique : si un type n'est jamais utilisé ou si tout finit en chore, la liste ne correspond plus au projet.

La plupart des refus de commitlint viennent de trois règles : subject-case, type-enum et subject-full-stop. Le message d'erreur nomme toujours la règle entre crochets : c'est elle qu'il faut chercher dans le tableau ci-dessous, du symptôme vers la correction.

SymptômeCauseSolution
subject must not be sentence-case […] [subject-case]Description qui commence par une majusculeÉcrire fix: corriger… en minuscule
type must be one of [build, chore, ci, …] [type-enum]Type absent de la liste, ou en majusculesChoisir un type de la liste, en minuscules
subject may not end with full stop [subject-full-stop]Point final dans la descriptionRetirer le point
subject may not be empty et type may not be emptyPas d'espace après les deux-points, ou pas de typeÉcrire feat: ajouter…, avec l'espace
footer must have leading blank line (avertissement)Pied collé au corpsInsérer une ligne vide avant BREAKING CHANGE:
Aucun contrôle au commit chez un collègueHooks non installésLancer npm install, qui exécute le script prepare
Tous les commits échouent, même conformes, sur husky - pre-commit script failedHook pre-commit créé par husky init qui lance npm testAdapter ou supprimer .husky/pre-commit
Les hooks du framework pre-commit (gitleaks…) ne tournent plus, sans erreurcore.hooksPath posé : Git ignore .git/hooks/Retirer le réglage (git config --unset-all core.hooksPath) et vérifier le message par l'étape commit-msg de pre-commit
  • Un message conforme suit la forme type(portée): description, avec une espace après les deux-points et un corps et des pieds séparés par une ligne vide.
  • La spécification ne définit que feat et fix ; les autres types viennent de la convention Angular, repris par config-conventional.
  • Un changement cassant se signale par ! avant les deux-points, par un pied BREAKING CHANGE: en majuscules, ou par les deux.
  • fix déclenche un correctif, feat une version mineure, un changement cassant une version majeure (SemVer).
  • commitlint vérifie les messages, husky le branche sur le hook commit-msg.
  • Le hook local se contourne : la vérification qui fait foi tourne en CI, sur chaque demande de fusion.
  • Sans Node.js, un hook shell vérifie la forme du message, avec des règles moins complètes que commitlint. Le réglage core.hooksPath qui l'active fait ignorer .git/hooks/ : un dépôt déjà équipé de pre-commit passe plutôt par son étape commit-msg.
  • Pour réagir au type d'un commit, la condition porte sur le message, jamais sur le nom de la branche : en GitLab CI, $CI_COMMIT_TITLE dans rules:, pas une expression dans only:.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

5 questions
8 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn