Retour au blog
juin 2026·5 min de lectureGénéré par IA
Langue:DeutschEnglishFrançaisÖsterreichischKlingonian Version originale: English

Un guide pratique du versionnage sémantique

#devops#npm#best-practices

^2.4.1 dans un package.json a l'air précis, mais ce circonflexe change de sens selon que le mainteneur en face a réellement suivi les règles sur lesquelles il repose. Le versionnage sémantique (semver) est une promesse encodée en trois nombres, et elle ne fonctionne comme promesse que si l'éditeur comme le consommateur comprennent exactement à quoi elle engage.

Les trois nombres

Une version semver s'écrit MAJOR.MINOR.PATCH, et chaque position a un sens précis :

  • MAJOR : incrémenté pour les changements incompatibles. Tout ce qui pourrait faire cesser de fonctionner du code existant et correct : une fonction supprimée, un paramètre renommé, un type de retour modifié, un nouveau champ de configuration obligatoire.
  • MINOR : incrémenté pour une nouvelle fonctionnalité rétrocompatible. De nouveaux paramètres facultatifs, de nouvelles fonctions exportées, de nouvelles options de configuration avec des valeurs par défaut raisonnables.
  • PATCH : incrémenté pour les corrections de bugs rétrocompatibles. Pas de nouvelle fonctionnalité, pas de changement de comportement, sauf « ce qui était cassé fonctionne maintenant comme documenté ».

1.4.2 → 1.5.0 signifie « du nouveau, rien ne devrait casser ». 1.4.2 → 2.0.0 signifie « lisez le changelog avant de mettre à jour ». Cette distinction est toute la valeur de semver : elle permet aux outils et aux humains de décider d'une mise à jour sans lire chaque commit.

Ce qui compte comme « incompatible » est plus strict qu'on ne le ressent

L'instinct est de réserver les versions majeures aux réécritures spectaculaires, mais la définition de semver porte sur tout changement d'API incompatible, aussi petit soit-il :

// v1.2.0
function formatPrice(amountCents) {
  return `$${(amountCents / 100).toFixed(2)}`;
}

// v1.3.0 — adding an optional param is fine, existing callers are unaffected
function formatPrice(amountCents, currency = 'USD') {
  return `${currency} ${(amountCents / 100).toFixed(2)}`;
}

// v2.0.0 — changing the parameter's meaning breaks every existing caller
function formatPrice(amountDollars, currency = 'USD') {
  return `${currency} ${amountDollars.toFixed(2)}`;
}

Ce dernier changement tient en une ligne de diff et paraît mineur, mais chaque appelant qui passe des centimes obtient désormais silencieusement un résultat faux au lieu d'une erreur, ce qui est pire qu'un changement incompatible bruyant, et non meilleur. C'est une version majeure quelle que soit la petitesse du changement de code.

Supprimer une fonction exportée publiquement, faire passer une fonction du synchrone à une fonction renvoyant une Promise, restreindre les types d'entrée acceptés, changer des messages d'erreur sur lesquels les appelants font du filtrage de motifs : tout cela constitue des changements incompatibles, même si aucun ne ressemble à « une grosse réécriture ».

Plages de versions : ce que ^ et ~ promettent réellement

npm install écrit des opérateurs de plage dans package.json, et ils n'ont de sens qu'à la lumière de ce que semver garantit :

{
  "dependencies": {
    "lodash": "^4.17.21",
    "express": "~4.18.2",
    "left-pad": "1.3.0"
  }
}
  • ^4.17.21 (circonflexe) : accepte toute version qui ne modifie pas le nombre non nul le plus à gauche. Donc de 4.17.21 jusqu'à 5.0.0 exclu : toute mise à jour mineure ou de correctif, aucune majeure. C'est la valeur par défaut de npm et le bon choix pour la plupart des dépendances : vous obtenez automatiquement corrections de bugs et nouvelles fonctionnalités, et les changements incompatibles exigent une action explicite.
  • ~4.18.2 (tilde) : accepte uniquement les mises à jour de niveau correctif : de 4.18.2 jusqu'à 4.19.0 exclu. Plus conservateur : vous choisissez manuellement les nouvelles fonctionnalités.
  • 1.3.0 (exacte) : aucune mise à jour sans modifier package.json. Adapté quand une dépendance a un historique de non-respect de semver, ou quand vous avez besoin de builds entièrement reproductibles indépendamment de package-lock.json.

La règle du « plus à gauche non nul » du circonflexe a un angle vif pour les paquets pré-1.0 : ^0.4.2 n'accepte que les mises à jour de correctif (0.4.x), pas les mineures, car avant 1.0.0, le deuxième nombre est traité comme la position des changements incompatibles. Semver documente explicitement 0.x.y comme « tout peut changer à tout moment », ce qui explique précisément pourquoi amener une bibliothèque à une 1.0.0 stable compte.

Publier votre propre paquet : faire correctement la promesse

Si vous maintenez un paquet, la discipline vous incombe, et non à vos utilisateurs :

  1. Décidez de l'incrément avant de décider de la release. Regardez le diff depuis la dernière release et classez-le : tout changement incompatible, où que ce soit → majeure. Pas de changement incompatible mais de nouvelles fonctionnalités → mineure. Sinon → correctif. Ne choisissez pas d'abord le numéro de version pour ensuite rationaliser la catégorie.
  2. Utilisez npm version plutôt que d'éditer à la main. Il met à jour package.json, crée un tag git et impose des chaînes semver valides.
npm version patch   # 1.4.2 -> 1.4.3
npm version minor    # 1.4.2 -> 1.5.0
npm version major    # 1.4.2 -> 2.0.0
  1. Rédigez une entrée de changelog par release, et pas seulement un journal de commits. « Ce qui casse, ce qui est nouveau, ce qui est corrigé » : trois phrases suffisent, mais c'est la différence entre un consommateur qui met à jour en confiance et un consommateur qui fige votre paquet pour toujours par peur.
  2. Dépréciez avant de supprimer. Marquez l'ancienne API comme dépréciée (dans la documentation, dans JSDoc, avec un avertissement à l'exécution) pendant au moins une version mineure avant de la retirer dans une majeure : cela laisse aux consommateurs une fenêtre de migration au lieu d'une surprise.

Pré-versions et métadonnées de build

Semver définit aussi des suffixes pour les versions antérieures à la disponibilité générale :

2.0.0-alpha.1
2.0.0-beta.3
2.0.0-rc.1
2.0.0

Elles se classent avant la version simple qu'elles précèdent (2.0.0-rc.1 < 2.0.0), et les gestionnaires de paquets ne les installent pas via une plage normale sauf demande explicite : npm install mypackage@next ou une pré-version exacte. C'est ainsi que l'on livre une version majeure en cours de développement pour des tests précoces sans que chaque consommateur en ^1.x.x récupère par accident un travail inachevé et incompatible.

À retenir

Semver est un contrat, pas une formalité : MAJOR promet « lisez ceci avant de mettre à jour », MINOR promet « vous pouvez le prendre automatiquement, voici du nouveau », PATCH promet « rien n'a changé sauf qu'un bug a été corrigé ». Les opérateurs de plage de package.json ne vous protègent que si les paquets dont vous dépendez respectent réellement ce contrat, et vos propres paquets publiés ne protègent vos consommateurs que si vous faites de même.

Plus récentDéboguer les fuites mémoire dans Node.jsPlus ancienLa notation Big O expliquée avec du vrai code