SemVer signifie Semantic Versionning. C'est une manière de numéroter les versions succéssives d'un module. Je vous invite à aller lire l'article sur SemVer avant de lire la suite si vous n'êtes pas certain de bien comprendre ce que c'est. Je vais ici citer certaines parties de ce post afin de faire un rapide résumé :
Une version s'applique à un produit, une application, une bibliothèque, un OS, tout ce qui a une progression en informatique. Cela permet de définir l'avancement du produit. SemVer s'écrit de cette façon : X.Y.Z où X est "majeur", Y est "mineur", Z est "patch".
SemVer est un contrat que vous signez avec vos utilisateurs, une forme de respect qui leur permet d'adapter leur code ou non en fonction des versions que vous proposez.
Cela veut dire que si vous avez corrigé un bug dans votre lib et que cela n'affecte en rien le code écrit par votre utilisateur, alors incrémentez Z (+0.0.1).
Si vous avez fait des ajouts dans votre app qui peuvent être intéressants pour l'utilisateur et mérite une certaine attention afin d'améliorer le code qui utilise votre lib, alors incrémentez Y (+0.1.0).
Si en revanche, vous avez cassé ne serait-ce qu'une partie de l'API ("breaking changes"), que vous avez juste changé des noms de méthodes / fonctions déjà existantes (et donc sûrement utilisées par quelqu'un d'autre), que finalement cela nécessite forcément une modification de la part de l'utilisateur sous peine que sa propre app ne fonctionne plus, alors incrémentez X (+1.0.0).
Maintenant que nous sommes au point sur SemVer, passons à la suite.
Lorsqu'on veut publier un module sur npm, on utilise principalement deux
commandes : npm version
et npm publish
.
npm version
nous permet de dire quelle composante de notre numéro de version
on souhaite incrémenter. Ainsi, npm version major
augmentera la partie
MAJEUR
, npm version minor
augmentera la partie MINEUR
et
npm version patch
... je crois que vous l'avez maintenant. La commande crée
aussi un tag git ayant pour nom le numéro de version.
Une fois la nouvelle version prête, npm publish
permet, vous l'aurez deviné
(j'espère), de la publier sur npm.
Ce processus semble simple à première vue. Selon les modifications qui ont été
apportées au module depuis la dernière version, on décide de la composante
SemVer à incrémenter, on lance un npm version
puis un npm publish
et c'est
terminé. Pourtant, une étape du processus implique une décision prise par un
humain, et est donc soumise à interprétation ainsi qu'à de potentielles erreurs
ou oublis.
Imaginons : je travaille sur mon super module au nom extrêmement original
foobar
. Celui-ci est actuellement en version 1.2.3
et plusieurs personnes
ont proposé des pull requests ajoutant des fonctionnalités et corrigeant des
bugs. Il est temps de publier une nouvelle version de foobar
afin que tout le
monde puisse bénéficier de ces améliorations. Je passe en revue ce qui a été
ajouté, et je décide d'incrémenter la composante MINEUR
de mon numéro de
version. Je publie cette nouvelle version, documente le tout dans une note de
version, je suis heureux.
Manque de chance, quelques temps après la publication, les issues Github de mon projet s'affolent. J'ai raté une modification non rétrocompatible, les utilisateurs de mon module (auxquels je promets de respecter SemVer) se sont joyeusement empressés de faire la mise à jour dans leur application, et maintenant tout est cassé. Sueurs froides, j'étais pourtant sûr de mon coup, et maintenant je dois me débrouiller pour régler ce problème dans l'urgence. "Accessoirement", j'ai aussi perdu la confiance que m'accordaient certains utilisateurs.
Après avoir réglé le problème, une question se pose : comment éviter cela à l'avenir ?
Eviter ce problème, c'est la promesse de semantic-release. Ce module nous permet de ne plus avoir à décider nous-même du prochain numéro de version de notre module.
Le principe est simple : à chaque fois que je fais un commit, je décris dans le message qui l'accompagne ce que j'ai effectué dans celui-ci, dans un format précis qui pourra être analysé automatiquement afin de calculer le prochain numéro de version. C'est un deal entre nous et semantic-release : si on respecte ce format, il nous garantit de correctement incrémenter les différentes composantes du numéro de version, et de publier automatiquement chaque nouvelle version de notre module.
Par défaut, semantic-release se base sur la convention de message de commit du projet AngularJS, qui propose le format suivant :
():
Ainsi, si un commit correspond à la correction d'un bug, je peux lui attribuer un message de la forme (exemples tirés de la documentation) :
fix(pencil): stop graphite breaking when too much pressure applied
Dans le cas d'un ajout de fonctionnalité :
feat(pencil): add 'graphiteWidth' option
Et dans le cas d'une modification qui casse la rétrocompatibilité :
perf(pencil): remove graphiteWidth option
BREAKING CHANGE: The graphiteWidth option has been removed. The default
graphite width of 10mm is always used for performance reasons.
Ainsi, lorsque semantic-release va passer tous les commits en revue, il pourra analyser le type de chacun d'entre eux, ainsi que rechercher les "BREAKING CHANGE", afin d'incrémenter le numéro de version comme il se doit.
Petit bonus : semantic-release se charge aussi de vous générer des notes de version et de les ajouter à votre release sur Github.
Finalement, en écrivant de bons messages de commits (ce qui devrait déjà être le cas, non ?) qui suivent un formalisme précis, vous vous épargnez d'avoir à gérer vous-même SemVer pour votre module.
Voyons maintenant comment mettre en place semantic-release sur un projet de module npm.
La première chose à faire, c'est d'installer la CLI :
npm install -g semantic-release-cli
Puis de lancer le setup dans le dossier de votre projet :
semantic-release-cli setup
semantic-release vous pose alors quelques questions :
What is your npm registry?
: appuyez directement sur entrée pour
sélectionner le registry npm par défautWhat is your npm username?
: entrez votre username npmWhat is your npm password?
: entrez votre mot de passe npmWhat is your GitHub username?
: entrez votre username GitHubWhat is your GitHub password?
: entrez votre mot de passe GitHubWhat is your GitHub two-factor authentication code?
: si vous avez activé
l'authentification à deux facteurs sur GitHub, entrez le code que vous aurez
reçu iciWhat CI are you using?
: nous allons ici utiliser Travis CI, mais vous
pouvez utiliser ce que vous voulez si vous avez un système de CI particulier.
Pour la suite, il n'y a pas besoin de maîtriser Travis CI ni l'intégration
continue en soit. Je vous suggère juste de lire
l'article d'introduction si le
concept d'intégration continue ne vous parle pas du toutDo you want a .travis.yml file with semantic-release setup?
: Yes !Voilà, tout est configuré et prêt à être utilisé :
semantic release
a été ajouté à votre package.json
version
de votre package.json
a été modifié en
0.0.0-development
. Vous n'aurez plus jamais à y toucher, c'est
semantic-release qui fera ce travail avant de publier chaque nouvelle version.travis.yml
contenant la configuration Travis CI nécessaire a été
créé. Vous pouvez le modifier selon vos besoins. Dans l'immédiat, la chose
urgente est d'adapter la liste des versions de Node sur lesquelles Travis va
lancer vos testsgit push
Une petite note concernant Travis CI : le but de semantic-release étant d'automatiser le processus de publication d'un module npm, celui-ci va tout bonnement refuser de se lancer en dehors d'un environnement d'intégration continue. Une configuration de base est fournie, il n'y a donc pas à se soucier de ça, mais sans intégration continue, pas de semantic-release. C'est pourquoi l'utilisation d'une plateforme d'intégration continue vous est imposée.
Maintenant que tout est en place, il va falloir travailler un peu, faire
quelques commits afin d'arriver au point où on voudra publier une nouvelle
version. Disons que je travaille sur un module math
qui implémente
actuellement les fonctions add
et substract
. Le module est actuellement en
version 1.0.0 sur npm, et un utilisateur m'a remonté un bug sur la fonction
add
lorsqu'on souhaite additionner deux nombres décimaux. Je règle le
problème, et arrive le moment de faire le commit. Selon la convention que nous
avons vu un peu plus tôt, celui-ci devra ressembler à :
fix(add): handle decimal numbers addition
Semantic-release pourra alors voir que ce commit est une correction de bug, qui
s'applique à la fonction add, et qui règle un problème d'addition sur des
nombres décimaux. Il pourra donc prendre la décision d'incrémenter la composante
MINEUR
de la version du module.
Satisfait de mon travail pour aujourd'hui, je décide de m'arrêter là, et je fais
un git push
. Travis CI déclenchera un build dès qu'il aura détecté ce nouveau
commit sur mon repository GitHub. Celui-ci va donc lancer mes tests, et si
ceux-ci passent, lancera le script npm créé lors du setup de semantic-release
(npm run semantic-release
). Ce script va faire les choses suivantes :
version
du
package.json
puis npm publish
)Une fois le build Travis CI terminé, je verrais donc la version 1.0.1
de
math
publiée sur npm, un nouveau tag et une nouvelle release (avec les notes
qui y sont associées) disponibles sur GitHub.
Si je décide maintenant d'ajouter les fonctions multiply
et divide
à mon
module, et d'effectuer les deux commits suivants :
feat(multiply): add multiply function
feat(divide): add divide function
Semantic-release déterminera que le prochain numéro de version est 1.1.0
, et
fera le même travail que précédemment.
Enfin, si je décide d'implémenter une fonction spécifique pour l'addition de deux nombre décimaux, je ferais le commit suivant :
feat(add): split add function into addInts and addFloats
BREAKING CHANGE: add function has been removed. You must replace it with
addInts or addFloats
Semantic-release publiera dans ce cas la version 2.0.0
, parce qu'il détectera
(grâce au "BREAKING CHANGE") que ce commit contient des modifications non
rétrocompatibles. Il s'occupera aussi d'ajouter aux notes de version une liste
des changements non rétrocompatibles.
Nous avons jusqu'ici utilisé semantic-release avec tout ce qu'il propose par
défaut. Toutefois celui-ci est basé sur un système de plugins et peut donc être
personnalisé quasiment de A à Z. Je ne détaillerais pas tout ici, mais chaque
étape du processus qui se déroule au lancement du script npm semantic-release
ne fait qu'exécuter une liste de plugins qui peuvent tous être remplacés. Vous
pouvez ainsi implémenter votre propre logique pour chaque étape. Un exemple :
écrire un plugin pour la phase d'analyse des commits afin d'utiliser une
convention différente de celle d'Angular JS.
La documentation explique bien comment écrire un plugin. Les possibilités sont virtuellement infinies, libre à vous d'adapter semantic-release à votre façon de travailler et vos envies.