Utiliser Lerna avec un monorepo pour déployer seulement les changements
Introduction
Lerna est un outil pour faciliter la maintenance et les interactions avec les monorepos.
Installation
À la racine de votre projet, lancer la commande suivante:
npm install --save-dev lerna
Structure & Configuration
tree -L 2 -I node_modules -a
Résultat:
.
├── .env.test
├── .eslintrc.js
├── .github
│ └── workflows
├── .gitignore
├── .husky
│ ├── _
│ ├── commit-msg
│ ├── pre-commit
│ └── pre-push
├── .prettierrc
├── actions
│ └── lambda-ci
├── commitlint.config.js
├── config.dev.json
├── lerna.json
├── libs
│ └── apigateway
├── package-lock.json
├── package.json
└── services
├── lambdaA
│ ├── .npmignore
│ ├── __tests__
│ ├── coverage
│ ├── jest.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── post_serviceName.json
│ ├── reports
│ ├── serverless.yml
│ └── src
└── lambdaB
├── .npmignore
├── __tests__
├── coverage
├── jest.config.js
├── package-lock.json
├── package.json
├── post_serviceName.json
├── reports
├── serverless.yml
└── src
17 directories, 19 files
Les services (dans ce cas-ci les lambdas), se trouvent tous sous services/
.
Maintenant pour configurer lerna,
Il faut créer un fichier lerna.json
à la racine du projet:
lerna.json
:
{
"packages": ["services/**", "libs/**"],
"version": "0.0.0"
}
Les package.json
; Lerna est super bien intégré avec les package.json
.
À la racine du projet, vous pouvez avoir des scripts comme suit:
{
"scripts": {
"test": "lerna run test --since HEAD~1 --parallel",
"lint": "lerna run lint --since HEAD~1 --parallel",
"audit": "lerna exec --since HEAD~1 -- npm audit --production",
"install": "lerna exec --since HEAD~1 -- npm install",
"prune": "lerna exec --since HEAD~1 -- npm prune --production",
"clean": "lerna exec -- \"rm -rf node_modules && rm -f package-lock.json\"",
"precommit": "lint-staged",
"deploy:dev": "sls deploy --stage dev",
"print:dev": "sls print --stage dev",
"deploy": "lerna run deploy --since HEAD~1"
},
"devDependencies": {
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
"eslint": "^8.4.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jest": "^25.3.0",
"eslint-plugin-prettier": "^4.0.0",
"husky": "^7.0.4",
"lerna": "^4.0.0",
"lint-staged": "^12.1.2",
"prettier": "^2.5.1"
},
"lint-staged": {
"**/*.js": "eslint"
}
}
J’ai enlevé les autres paramètres pour simplifier la lecture
La configuration ci-haut permet de lancer les commandes seulement sur les fichiers qui ont changé depuis le dernier commit. De cette façon votre CI et vos déploiements seront plus efficaces lorsqu’on travaille en monorepo.
Pour les services, vous devez avoir les commandes npm de configurées.
{
"scripts": {
"lint": "eslint *.js",
"test": "jest --coverage",
"deploy": "echo 'Deploying...'"
},
"devDependencies": {
"@types/jest": "^27.0.3",
"dotenv": "^10.0.0",
"jest": "^27.4.4",
"jest-junit": "^13.0.0"
}
}
Tests
Cette structure a été testée avec des github actions et localement.
- Valider que le
pre-commit
&pre-push
se lance seulement pour les services qui ont changé. - Valider que le
CI
lance les commandes seulement pour les services qui ont changé. - Valider que le déploiement s’effectue seulement sur les services qui ont changé lorsque la feature branch est mergé vers main.
Limitations
- La configuration courante ne prend pas en charge les changements de librairie (
libs/
);- La responsabilité reste au développeur de changer les versions manuellement. Ce qui peut aussi causer des conflits lors du merge vers la branche
main
.
- La responsabilité reste au développeur de changer les versions manuellement. Ce qui peut aussi causer des conflits lors du merge vers la branche
- C’est pourquoi je recommande la création de petites librairies pour faciliter le partage du code entre les modules.
- Des dépendances plus grosses/couplées peuvent être créées, mais je suggère fortement qu’elles doivent être utilisées/restreinte à un service seulement.
- Puis lorsque possible/nécessaire, vous pouvez sortir les bouts de code partagé pour faire une petite librairie.