JS monorepos en prod 2 : gestion des versions et de la publication

JS monorepos en prod 2 : gestion des versions et de la publication

WORMS David

By WORMS David

11 janv. 2021

Un des grands avantages d’un monorepo est de maintenir des versions cohérentes entre les packages et d’automatiser la création des versions et la publication des packages. Cet article couvre les stratégies et les meilleures pratiques de gestion des versions et de publication avant de continuer avec le format des messages de commit, l’exécution de tests unitaires et l’intégration CI / CD dans les articles suivants :

Gestion des versions et stratégies de publication

Souvenez-vous de l’utilisation de l’indicateur --independent dans la commande lerna init. Il informe Lerna de notre stratégie de gestion des versions. Dans notre cas, les packages sont tous des plugins Gatsby, et il est logique de les regrouper dans un seul référentiel Git. Ce sont cependant des packages indépendants, avec leur propre cycle de publication et leur propre maturité. Ainsi, nous ne souhaitons pas partager une seule version pour tous.

Il est temps d’utiliser Lerna pour gérer les versions. Exécuter la commande lerna version fait les choses suivantes :

  • Invite l’utilisateur à choisir la version de chaque paquet puisque ceux-ci sont gérés indépendamment.
  • Enregistre la version dans les fichiers package.json.
  • Crée une balise avec le nom du package et la version.
  • Commit et push les modifications au serveur distant.

Mais d’abord, Lerna peut personnaliser le message de validation. Dans le fichier de configuration lerna.json, ajoutez l’entrée :

{
  ...
  "command": {
    ...
    "version": {
      ...
      "message": "chore(release): publish"
    }
  }
}

Et validez les changements :

git commit -a -m 'build: customize lerna versioning message'

Nous pouvons exécuter lerna version pour avoir une version initiale de nos deux packages :

lerna version
info cli using local version of lerna
lerna notice cli v3.22.1
lerna info versioning independent
lerna info Assuming all packages changed
? Select a new version for gatsby-remark-title-to-frontmatter (currently 0.0.0) Premajor (1.0.0-alpha.0)
? Select a new version for gatsby-caddy-redirects-conf (currently 0.0.0) 
  Patch (0.0.1) 
  Minor (0.1.0) 
  Major (1.0.0) 
  Prepatch (0.0.1-alpha.0) 
❯ Preminor (0.1.0-alpha.0) 
  Premajor (1.0.0-alpha.0) 
  Custom Prerelease 
  Custom Version 

Lerna nous présente une liste de versions incrémentielles possibles, y compris les versions patch, mineure et majeure ainsi que les numéros des versions prerelease et les versions personnalisées telles que alpha, beta et rc. Nous sommes libres de choisir une valeur différente pour chaque package. Une fois prêt, Lerna demande une confirmation finale :

lerna version
info cli using local version of lerna
lerna notice cli v3.22.1
lerna info versioning independent
lerna info Assuming all packages changed
? Select a new version for gatsby-remark-title-to-frontmatter (currently 0.0.0) Premajor (1.0.0-alpha.0)
? Select a new version for gatsby-caddy-redirects-conf (currently 0.0.0) Preminor (0.1.0-alpha.0)

Changes:
 - gatsby-remark-title-to-frontmatter: 0.0.0 => 1.0.0-alpha.0
 - gatsby-caddy-redirects-conf: 0.0.0 => 0.1.0-alpha.0

? Are you sure you want to create these versions? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished

Le package gatsby-remarque-title-to-frontmatter est une version alpha majeure préparant la version 1.0.0. Le paquet gatsby-caddy-redirects-conf est une version alpha mineure préparant la version 0.1.0.

Registre NPM privé et local

Le code source est maintenant disponible sur GitHub et le commit est taggé avec notre version. Les tags sont gatsby-remarque-title-to-frontmatter@1.0.0-alpha.0 et gatsby-caddy-redirects-conf@0.1.0-alpha.0. Les tags reflètent les noms et versions des packages.

Nos deux packages ne sont pas encore disponibles pour la communauté. Pour que d’autres utilisateurs les utilisent depuis leur package.json comme dépendances, ils doivent être publiés sur un registre NPM. Lerna fournit la commande lerna publish pour cela. Avant de décrire le fonctionnement de lerna publish, un registre NPM local est mis en place à des fins de test.

L’hébergement de votre registre NPM privé présente de multiples avantages. Il garantit qu’aucune partie externe n’a accès à vos packages. Il accélère le processus de téléchargement lorsqu’il est hébergé sur votre réseau. C’est gratuit à condition que vous utilisiez un projet de registre open source et que vous disposiez de la machine pour l’héberger.

Dans un souci de test et parce que les publications NPM ne sont pas révocables après 48 heures, un registre NPM local est utilisé comme alternative au registre NPM officiel. Verdaccio est un registre simple qui peut être démarré sur votre machine hôte. Ignorez cette étape si vous souhaitez publier vos packages directement sur le registre officiel NPM. Pour l’installer, vous pouvez utiliser Docker, Kubernetes ou NPM. Par exemple, en utilisant NPM :

npm install -g verdaccio
verdaccio

J’ai personnellement utilisé minikube et Helm :

minikube start
😄  minikube v1.14.0 on Darwin 10.15.7
✨  Using the hyperkit driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🔄  Restarting existing hyperkit VM for "minikube" ...
🐳  Preparing Kubernetes v1.19.2 on Docker 19.03.8 ...
🔎  Verifying Kubernetes components...
🌟  Enabled addons: storage-provisioner, default-storageclass
🏄  Done! kubectl is now configured to use "minikube" by default
helm repo add verdaccio https://charts.verdaccio.org
"verdaccio" has been added to your repositories
helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "verdaccio" chart repository
Update Complete. ⎈Happy Helming!⎈
helm install npm verdaccio/verdaccio 
NAME: npm
LAST DEPLOYED: Thu Dec  3 11:03:33 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app=verdaccio,release=npm" -o jsonpath="{.items[0].metadata.name}")
  kubectl port-forward --namespace default $POD_NAME 8080:4873
  echo "Visit http://127.0.0.1:8080 to use your application"
export POD_NAME=$(kubectl get pods --namespace default -l "app=verdaccio,release=npm" -o jsonpath="{.items[0].metadata.name}")
kubectl port-forward --namespace default $POD_NAME 4873:4873

Dans les deux cas, vous pouvez ouvrir votre navigateur Web et accéder à http://127.0.0.1:4873. L’écran d’accueil vous demande de créer un compte :

npm adduser --registry http://localhost:4873
Username: david
Password: 
Email: (this IS public) david@adaltas.com
Logged in as david on http://localhost:4873/.

Toutes les futures commandes lerna publish peuvent faire référence à ce registre en insérant l’argument --registry. Supprimez cet argument pour publier vos packages sur le registre public et officiel NPM.

Pour éviter de passer l’argument --registry sur chaque lerna publish, le fichier lerna.json peut stocker son emplacement :

{
  "command": {
    "publish": {
      "registry": "http://localhost:4873/"
    }
  }
}

Stocker l’adresse de registre dans lerna.json implique qu’elle sera validée dans Git et partagée avec vos collaborateurs.

Alternativement, Verdaccio peut être défini globalement comme votre registre par défaut dans votre fichier ~/.npmrc :

npm set registry http://localhost:4873
cat ~/.npmrc | grep registry=
registry=http://localhost:4873/

publication NPM

C’est le moment de publier nos packages sur le registre NPM de notre choix avec lerna publish.

Il existe deux stratégies intéressantes permettant à Lerna de savoir quelle version il doit publier. La première consiste à publier des packages du dernier commit où la version n’est pas présente dans le registre avec l’argument from-package.

lerna publish from-package                                  
info cli using local version of lerna
lerna notice cli v3.22.1
lerna info versioning independent
lerna WARN Unable to determine published version, assuming "gatsby-remark-title-to-frontmatter" unpublished.
lerna WARN Unable to determine published version, assuming "gatsby-caddy-redirects-conf" unpublished.

Found 2 packages to publish:
 - gatsby-remark-title-to-frontmatter => 1.0.0-alpha.0
 - gatsby-caddy-redirects-conf => 0.1.0-alpha.0

? Are you sure you want to publish these packages? No

La seconde stratégie consiste à rechercher le tag dans le commit actuel avec l’argument from-git. Dans notre cas, les deux ont les mêmes conséquences.

lerna publish from-git
info cli using local version of lerna
lerna info versioning independent

Found 2 packages to publish:
 - gatsby-caddy-redirects-conf => 0.1.0-alpha.0
 - gatsby-remark-title-to-frontmatter => 1.0.0-alpha.0

? Are you sure you want to publish these packages? Yes
lerna info publish Publishing packages to npm...
lerna WARN ENOLICENSE Packages gatsby-caddy-redirects-conf and gatsby-remark-title-to-frontmatter are missing a license.
lerna WARN ENOLICENSE One way to fix this is to add a LICENSE.md file to the root of this repository.
lerna WARN ENOLICENSE See https://choosealicense.com for additional guidance.
lerna success published gatsby-remark-title-to-frontmatter 1.0.0-alpha.0
lerna http fetch PUT 201 http://localhost:4873/gatsby-remark-title-to-frontmatter 824ms
lerna success published gatsby-caddy-redirects-conf 0.1.0-alpha.0
lerna http fetch PUT 201 http://localhost:4873/gatsby-caddy-redirects-conf 873ms
Successfully published:
 - gatsby-caddy-redirects-conf@0.1.0-alpha.0
 - gatsby-remark-title-to-frontmatter@1.0.0-alpha.0
lerna success published 2 packages

Par souci de clarté, les logs sont filtrés.

Filtrage du contenu du package

Tous les fichiers n’ont pas besoin d’être publiés. Le contenu qui n’est pas stocké dans Git ne sera probablement pas publié. Les mots de passes, les certificats, les fichiers de configuration privés sont de tels exemples. Par défaut, NPM et Yarn regardent vos fichiers .gitignore et importent ces directives à moins qu’il n’y ait un .npmignore.

Mais être stocké et partagé dans Git ne signifie pas nécessairement qu’il sera publié dans NPM. Vos packages doivent être aussi petits que possible. Par exemple, il n’est pas nécessaire de publier vos tests.

Vous pouvez copier le fichier .gitignore à la racine de vos paquets dans un fichier .npmignore et commencer à ajouter de nouvelles règles. Il y a cependant, à mon avis, une bien meilleure approche. Utilisez la propriété files de votre fichier package.json. Depuis la documentation package.json :

Le champ optionnel files est un array de modèles de fichiers qui décrit les entrées à inclure lorsque votre package est installé en tant que dépendance. Les modèles de fichiers suivent une syntaxe similaire à .gitignore, mais inversée : inclure un fichier, un répertoire ou un modèle de glob (*,**/*, etc.) fera en sorte que le fichier soit inclus dans l’archive tar quand il sera packagé. Omettre le champ le rendra par défaut ["*"], ce qui signifie qu’il inclura tous les fichiers.

Cependant, certains fichiers seront inclus tels que les fichiers README, CHANGELOG et LICENSE.

Les fichiers gatsby-remarque/title-to-frontmatter/package.json et gatsby/caddy-redirects-conf/package.json sont enrichis avec :

{
  "files": [
    "/lib"
  ]
}

Ensuite, les modifications sont validées :

git commit -a -m 'build: include lib in published packages'

Si nous apportions des modifications à un package avec un dossier doc et son contenu associé, il serait validé dans Git mais pas publié.

Publication de packages sélective

Remarquez que Lerna signale nos packages qui ne fournissent pas de fichier de licence. Il nous propose également de placer un fichier LICENSE.md à la racine du référentiel. Faisons juste cela et publions une nouvelle version.

curl \
  https://raw.githubusercontent.com/adaltas/node-csv/master/LICENSE \
  -o LICENSE.md
git add LICENSE.md
git commit -m 'build: MIT license'
lerna publish from-git
info cli using local version of lerna
lerna notice cli v3.22.1
lerna info versioning independent
lerna notice from-git No tagged release found
lerna success No changed packages to publish

Cette fois, exécuter lerna publish n’a aucun effet même s’il y a des changements depuis le dernier commit. Ceci est dû au fait que le commit n’a affecté aucun des packages publiés définis comme workspace Yarn. Ceci est également dû au fait que le package racine n’est pas publié, car il est marqué comme privé dans le fichier package.json :

cat package.json | grep private
  "private": true,

Publication de packages sélective basée sur le contenu

La commande lerna publish ne publie que les packages avec des modifications. Il peut aller plus loin en ne prenant pas en compte les modifications de certains fichiers sélectionnés.

Le command.publish.ignoreChanges est un tableau de globs qui ne seront pas inclus dans lerna changed/publish.

Il peut être défini globalement dans votre fichier de configuration lerna.json, par exemple pour ne pas publier de nouvelle version si les changements n’étaient que dans les tests :

{
  ...
  "ignoreChanges": [
    "**/test/**"
  ]
}

Nous devons valider les modifications et créer de nouvelles versions avant de les tester :

git commit -a -m "build: lerna version ignore test"
# Note, select 'prerelease' to match the version below
lerna version
...

Changes:
 - gatsby-remark-title-to-frontmatter: 1.0.0-alpha.0 => 1.0.0-alpha.1
 - gatsby-caddy-redirects-conf: 0.1.0-alpha.0 => 0.1.0-alpha.1

? Are you sure you want to create these versions? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished

Une fois qu’un nouveau test est validé dans l’un des packages, aucune version n’est proposée :

echo 'console.warn("TODO")' >  gatsby-remark/title-to-frontmatter/test/index.js
git add gatsby-remark/title-to-frontmatter/test/index.js
git commit -m 'test(title-to-frontmatter): todo tests'
lerna version                                                                
info cli using local version of lerna
lerna notice cli v3.22.1
lerna info versioning independent
lerna info Looking for changed packages since gatsby-caddy-redirects-conf@0.1.0-alpha.1
lerna info ignoring diff in paths matching [ '**/test/**' ]
lerna success No changed packages to version 

La modification des fichiers peut être filtrée à partir de lerna version et lerna change en appliquant l’argument --ignore-changes ou en modifiant le fichier lerna.json avec la propriété ignoreChanges. Par exemple, pour supprimer une nouvelle version lorsque seule une faute de frappe a été effectuée dans un fichier README.

Incrémentation Automatique des versions

Modifions l’un de nos packages. Le package gatsby-caddy-redirects-conf, actuellement à la version 0.1.0-alpha.1, mérite un README.

echo \
 '# Package `gatsby-caddy-redirects-conf`' \
  > gatsby/caddy-redirects-conf/README.md
git add gatsby/caddy-redirects-conf/README.md
git commit -m "docs(title-to-frontmatter): new readme file"

Au lieu de laisser Lerna nous demander quelle version définir, nous disons à Lerna d’incrémenter automatiquement la version actuelle de la prerelease :

lerna version prerelease
info cli using local version of lerna
lerna notice cli v3.22.1
lerna info versioning independent
lerna info Looking for changed packages since gatsby-caddy-redirects-conf@0.1.0-alpha.1
lerna info ignoring diff in paths matching [ '**/test/**' ]

Changes:
 - gatsby-caddy-redirects-conf: 0.1.0-alpha.1 => 0.1.0-alpha.2

? Are you sure you want to create these versions? Yes
lerna info execute Skipping releases
lerna info git Pushing tags...
lerna success version finished

Puisque nous indiquons quelle version nous souhaitons avec le mot-clé SemVer prerelease, Lerna n’a pas besoin de nous demander la version ciblée et, à la place, demande seulement de valider sa suggestion.

Notez que pour sortir d’une version prerelease et publier une version mineure ou majeure avec le Conventional Commit, la commande ressemble à lerna version --conventional-commits --conventional-gradu. Nous couvrons Conventional Commit dans l’article suivant.

CI/CD, séparation entre la gestion des versions et la publication

Si vous essayez d’exécuter la commande lerna publish sans exécuter auparavant lerna version, vous remarquerez qu’elle crée d’abord une version du package, puis publie le package. Pourquoi avons-nous appelé lerna version si elle est facultative ?

La gestion des versions est séparé de la publication car je n’exécute pas les deux commandes au même emplacement. Par exemple, je peux exécuter lerna version manuellement, lorsque je suis convaincu que mon package mérite une nouvelle version, et automatiser lerna publish à distance depuis une plateforme CI/CD, lorsque toutes les vérifications et tests réussissent et une fois une nouvelle version a été détectée.

Aide-mémoire

  • Message de validation personnalisé de Lerna
    Modify lerna.json :
    {
      ...
      "command": {
        ...
        "version": {
          ...
          "message": "chore(release): publish"
        }
      }
    }
    Valider le changement :
    git commit -a -m 'build: customize lerna versioning message'
  • Créez une nouvelle version :
    lerna version
    Incrémenter automatiquement la version prerelease :
    lerna version prerelease
    Sauf si les fichiers modifiés correspondent à un modèle :
    lerna version --ignore-changes '**/*.md' '**/__tests__/**'
  • Publier une nouvelle version
    lerna publish
    Ou conditionnel s’il y a un tag de version :
    lerna publish from-git
  • Utilisation d’un repository personnalisé
    Ajouter l’argument registry :
    lerna publish --registry http://localhost:4873/
    Ou mettre à jour lerna.json :
    {
      "command": {
        "publish": {
          "registry": "http://localhost:4873/"
        }
      }
    }
    Ou mettre à jour ~/.npmrc :
    npm set registry http://localhost:4873
  • Filtrage du contenu
    Dans ‘package.json’ :
    {
      "files": [
        "/lib"
      ]
    }

Conclusion

Les commandes Lerna version et publish sont livrées avec de nombreux arguments qui méritent d’être étudiés. Nous verrons ensuite comment appliquer le format de message de validation, comment exécuter des tests unitaires et comment automatiser la publication de packages dans un environnement CI/CD.

Canada - Maroc - France

International locations

10 rue de la Kasbah
2393 Rabbat
Canada

Nous sommes une équipe passionnée par l'Open Source, le Big Data et les technologies associées telles que le Cloud, le Data Engineering, la Data Science le DevOps…

Nous fournissons à nos clients un savoir faire reconnu sur la manière d'utiliser les technologies pour convertir leurs cas d'usage en projets exploités en production, sur la façon de réduire les coûts et d'accélérer les livraisons de nouvelles fonctionnalités.

Si vous appréciez la qualité de nos publications, nous vous invitons à nous contacter en vue de coopérer ensemble.