---
cover: ./img/cover.min.jpg
coverAlt: Bibliothèque futuriste en bois avec livres flottants de versions NPM, hologramme d’arbre de dépendances, et panda roux lisant « SemVer pour les nuls » sous une lumière dorée.
tags: Frontend, Backend, JavaScript, TypeScript
---

# Reprendre le contrôle de vos dépendances NPM

Je vois souvent des gens ne pas maitriser l'usage de NPM au quotidien. Des choses basiques, mais importantes, surtout quand il s'agit de se débarrasser d'une faille de sécurité dans une dépendance transitive. Je ne suis pas expert NPM, mais après quasi 10 ans à l'utiliser au quotidien, j'ai appris quelques trucs qui servent bien, et j'en apprends encore !

## C'est quoi une dépendance ?

En général, on répondra "tout ce qui est listé dans le `package.json`". Et ce n'est pas faux en soi. Mais c'est incomplet en théorie.

Déjà si vous faites du Node, vous avez au strict minimum deux dépendances : Node et `npm` (ou peut-être `yarn` ou `pnpm`, mais au fond vous avez quand même `npm` sur votre machine quoi qu'il arrive, et je ne vais pas couvrir les spécificités de `yarn` (que je ne maitrise pas assez) et `pnpm` (qui est trop intéressant pour juste faire un paragraphe dessus)). Sans ces deux outils, vous ne pouvez pas travailler au sens compiler / exécuter votre application. Donc vous dépendez de ces deux outils. Je ne vais pas rentrer plus dans le détail mais juste : pensez à mettre à jour votre version Node régulièrement, particulièrement avec le changement de [cycle de release](/2026/04/08/revue-de-presse-avril/#article-evolving-the-nodejs-release-schedulehttpsnodejsorgenblogannouncementsevolvingthenodejsreleaseschedule).

L'autre aspect qui manque c'est la notion de dépendance transitive. On qualifie de dépendance transitive une dépendance d'une de nos dépendances. Prenons un exemple simple : vous avez un projet SolidJS avec Vite, vous installez donc `vite-plugin-solid`. Vous avez donc dans votre `package.json` `solid-js`, `vite` et `vite-plugin-solid` qui sont vos dépendances directes, mais `vite-plugin-solid` dépend de `@babel/core`, `babel-preset-solid`, `solid-js`, `solid-refresh`, `vite` et `vitefu`, et si vous creusez encore vous verrez que `babel-preset-solid` dépend de `@babel/core`, `babel-plugin-jsx-dom-expressions` et `solid-js`, et on pourrait creuser un peu toutes les dépendances comme ça. Chaque dépendance pouvant dépendre d'une version différente d'une même dépendance (vous pouvez dépendance de `solid-js@1.9.12`, `babel-preset-solid` dépendre de `solid-js@1.9.11`, et `babel-preset-solid` dépendre de `solid-js@1.9.10`), ce ne serait pas forcément très stable / recommandé mais c'est possible du point de vue de NPM.

Vous avez donc des dépendances que vous maitrisez et d'autres que vous ne maitrisez pas directement. À priori.

## Visualisé vos dépendances

Commençons par notre `package.json` :

```JSON
{
  ...
  "dependencies": {
    "solid-js": "^1.9.5"
  },
  "devDependencies": {
    "typescript": "~5.8.3",
    "vite": "^6.3.5",
    "vite-plugin-solid": "^2.11.6"
  }
}
```

On voit donc 4 dépendances ici.

Maintenant, installons nos dépendances (`npm ci` si vous avez un `package-lock.json`, `npm i` sinon, j'y reviendrais plus tard).

```
❯ npm ci # si vous n'avez pas encore de package-lock.json, utilisez npm i ou npm install

added 73 packages in 1m

13 packages are looking for funding
  run `npm fund` for details
```

Et là NPM nous dit avoir installé 73 packages. On est donc bien loin des 4 qu'on voyait plus haut...

Ensuite on va utiliser `npm ls`.

```
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
├── solid-js@1.9.12
├── typescript@5.8.3
├── vite-plugin-solid@2.11.12
└── vite@6.4.2
```

C'est logique, on retrouve les dépendances de notre `package.json`. Allons regarder plus en profondeur :

```
❯ npm ls --depth 1

npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
├─┬ solid-js@1.9.12
│ ├── csstype@3.2.3
│ ├── seroval-plugins@1.5.2
│ └── seroval@1.5.2
├── typescript@5.8.3
├─┬ vite-plugin-solid@2.11.12
│ ├── @babel/core@7.29.0
│ ├── UNMET OPTIONAL DEPENDENCY @testing-library/jest-dom@^5.16.6 || ^5.17.0 || ^6.*
│ ├── @types/babel__core@7.20.5
│ ├── babel-preset-solid@1.9.12
│ ├── merge-anything@5.1.7
│ ├── solid-js@1.9.12 deduped
│ ├── solid-refresh@0.6.3
│ ├── vite@6.4.2 deduped
│ └── vitefu@1.1.3
└─┬ vite@6.4.2
  ├── UNMET OPTIONAL DEPENDENCY @types/node@^18.0.0 || ^20.0.0 || >=22.0.0
  ├── esbuild@0.25.12
  ├── fdir@6.5.0
  ├── UNMET OPTIONAL DEPENDENCY fsevents@~2.3.3
  ├── UNMET OPTIONAL DEPENDENCY jiti@>=1.21.0
  ├── UNMET OPTIONAL DEPENDENCY less@*
  ├── UNMET OPTIONAL DEPENDENCY lightningcss@^1.21.0
  ├── picomatch@4.0.4
  ├── postcss@8.5.9
  ├── rollup@4.60.1
  ├── UNMET OPTIONAL DEPENDENCY sass-embedded@*
  ├── UNMET OPTIONAL DEPENDENCY sass@*
  ├── UNMET OPTIONAL DEPENDENCY stylus@*
  ├── UNMET OPTIONAL DEPENDENCY sugarss@*
  ├── UNMET OPTIONAL DEPENDENCY terser@^5.16.0
  ├── tinyglobby@0.2.16
  ├── UNMET OPTIONAL DEPENDENCY tsx@^4.8.1
  └── UNMET OPTIONAL DEPENDENCY yaml@^2.4.2
```

Le fonctionnement de vite étant d'essayer de s'auto-configurer au maximum, on retrouve beaucoup de dépendances optionnelles. Épurons ces dépendances qui ne sont pas installées pour rendre l'arbre plus lisible

```
❯ npm ls --depth 1 | grep -v 'UNMET OPTIONAL DEPENDENCY'
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
├─┬ solid-js@1.9.12
│ ├── csstype@3.2.3
│ ├── seroval-plugins@1.5.2
│ └── seroval@1.5.2
├── typescript@5.8.3
├─┬ vite-plugin-solid@2.11.12
│ ├── @babel/core@7.29.0
│ ├── @types/babel__core@7.20.5
│ ├── babel-preset-solid@1.9.12
│ ├── merge-anything@5.1.7
│ ├── solid-js@1.9.12 deduped
│ ├── solid-refresh@0.6.3
│ ├── vite@6.4.2 deduped
│ └── vitefu@1.1.3
└─┬ vite@6.4.2
  ├── esbuild@0.25.12
  ├── fdir@6.5.0
  ├── picomatch@4.0.4
  ├── postcss@8.5.9
  ├── rollup@4.60.1
  ├── tinyglobby@0.2.16
```

> Note : `grep` permet de filtrer pour ne garder que les lignes qui contiennent un certain motif, mais si on ajoute l'option `-v`, on garde les lignes qui ne contiennent pas le motif.

Rien qu'en ajoutant un niveau de profondeur on est déjà à 20 dépendances, avec `solid-js@1.9.12` et `vite@6.4.2` qui taggé comme `deduped`, c'est à dire identifiées comme déjà résolue et donc n'ont plus besoin d'être résolus (car le package est tiré plus haut dans l'arbre et avec la même version ou une version compatible).

On peut continuer comme ça en augmentant la profondeur affichée. On peut afficher tout l'arbre directement en passant `--all`. On peut aussi cibler un package précis.

```
❯ npm ls solid-js                                       
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
├── solid-js@1.9.12
└─┬ vite-plugin-solid@2.11.12
  ├─┬ babel-preset-solid@1.9.12
  │ └── solid-js@1.9.12 deduped
  ├── solid-js@1.9.12 deduped
  └─┬ solid-refresh@0.6.3
    └── solid-js@1.9.12 deduped
```

Ici on voit où la dépendance `solid-js` est tirée dans l'arbre, la version attendue, si elle est bien dédupliquée comme on s'y attend (ici c'est le cas).

En choisissant `solid-js` dans un projet SolidJS, on s'attend à le voir. Si vous demande pour `esbuild` ?

```
❯ npm ls esbuild 
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
└─┬ vite@6.4.2
  └── esbuild@0.25.12
```

Il n'est pas dans notre `package.json` pourtant il fait bien partie de l'arbre. Une faille sur `esbuild` peut donc nous impacter. Et certains me diront qu'`esbuild` sert uniquement à la compilation donc ne part pas en production. Ce n'est pas aussi simple. Déjà `esbuild` sera installé sur chaque runner de notre CI, donc à défaut d'infecter nos utilisateurs, on peut impacter notre SI via notre CI ce qui n'est déjà pas terrible. Ensuite on peut se retrouver dans un cas où la faille n'est pas dans `esbuild` mais dans le code qu'il produit, en effet les outils de compilation vont modifier nos sources et peuvent donc injecter du code non sûr et là ça peut retomber chez les utilisateurs.

## Comment qu'on dit ce qu'on veut comme version ?

Déjà il faut comprendre ce qu'on appelle le SemVer ou **Sem**antic **Ver**sioning.

Pour faire très simple : on va définir une version sur trois nombre `X.Y.Z`. On va appeler X la version majeure, Y la mineure et Z le patch. L'idée va être de se dire qu'une modification qui vient corriger va juste incrémenter le patch, ensuite une nouvelle fonctionnalité qui ne casse rien pour les utilisateurs actuels incrémente uniquement le mineur, et si on casse quelque chose on incrémente le majeur.

> Note : je vous laisse aller voir sur Internet pour tous les détails du SemVer, je n'irai pas plus loin ici. Il reste pas mal de chose à dire : les versions alpha/beta/RC, les versions à quatre nombres, les versionnings alternatifs compatible ou non avec SemVer, etc.

À partir de là, on se dit qu'on veut parfois dire "je veux strictement cette version", parfois "je veux bien prendre un nouveau patch mais pas de changement mineur" et de temps en temps "je veux bien tout mettre à jour sauf la majeure".

Par défaut, si vous faite `npm install lodash` (pareil si vous faite `npm install lodash@latest`), NPM va ajouter à votre `package.json` dans `dependencies` `"lodash": "^4.18.1"` (la dernière version sortie à date). Sauf que là vous avez peut-être mis un doigt dans un système que vous ne vouliez pas avec le `^` devant la version. En effet `^4.18.1` veut dire "n'importe quelle version 4.x.x qui est supérieur à 4.18.1". Donc si vous refaite un `npm i` dans quelques jours/semaines/mois vous aurez peut-être la version 4.18.1, peut-être une 4.18.2 ou peut-être une 4.45.0. Est-ce que vous vouliez prendre en compte tous ces changements simplement en installant vos dépendances ? Pas sûr (même si, je vous l'accorde : avec `lodash` on peut normalement mettre à jour les yeux fermés).

Si vous voulez "toujours" tirer la version 4.18.1, il faut retirer le `^` et refaire un `npm i` (pour mettre à jour le `package-lock.json`) ou simplement faire directement `npm install lodash --save-exact` qui va ajouter au `package.json` `"lodash": "4.18.1"`.

Si vous voulez bien les mises à jours, mais seulement de patch, vous pouvez indiquer `"~4.18.1"`.

Sauf que là ça ne concerne que les versions qu'on tire directement via notre `package.json`. Si je veux forcer la version `0.27.7` d'`esbuild` je ne peux pas fonctionner comme ça.

En effet si je fais `npm i esbuild@0.27.7 --save-exact` (la dernière version à date) puis que je regarde l'arbre de dépendance :

```
❯ npm ls esbuild      
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
├── esbuild@0.27.7
└─┬ vite@6.4.2
  └── esbuild@0.25.12
```

On voit bien que j'ai version `0.27.7` à la racine de l'arbre, mais sous `vite` j'ai toujours la version `0.25.12`. Donc comment on fait ? Déjà on supprime `esbuild` qui est inutile à la racine du projet `npm uninstall esbuild`. Puis on va ajouter une section `overrides` à notre `package.json` pour forcer partout dans l'arbre une certaine version d'`esbuild` :

```JSON
{
  ...  
  "overrides": {
    "esbuild": "0.27.7"
  }
}
```

Là on fait `npm install` pour mettre à jour nos dépendances et on regarde notre arbre de dépendance :

```
❯ npm ls esbuild
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
└─┬ vite@6.4.2
  └── esbuild@0.27.7 overridden
```

Là on voit bien que la version n'est plus la même et c'est indiqué qu'elle est `overridden` montrant bien que ce n'est pas la version que NPM aurait choisi normalement.

À Noter qu'on peut aller assez loin avec cette mécanique, par exemple :

```JSON
{
  ...  
  "overrides": {
    "esbuild": "^0.27.0",
    "vite": {
      "esbuild": "^0.27.5"
    }
  }
}
```

Permet d'indiquer qu'on veut surcharger la version d'`esbuild` en autorisant n'importe quelle version compatible avec `^0.27.0` partout dans l'arbre de dépendance sauf dans le sous-arbre de `vite` où on va viser `~0.26.0` (en l’occurrence, la branche `0.26.x` ne contient que la version `0.26.0` donc NPM n'a pas beaucoup de choix de version). Ici ça ne sert pas à grand-chose mais dans certains cas, ça peut être utile. // TODO

```
❯ npm ls esbuild
npm-deps-override@0.0.0 /home/anthony_p/tmp/npm-deps-override
└─┬ vite@6.4.2
  └── esbuild@0.26.0
```

Ce n'est pas une erreur de ma part, ici on ne voit plus l'indication `overridden` alors que la version l'est bien mais passons là-dessus. Par contre on voit bien que notre version est surchargée.

Et si maintenant on ajoute ça ?

```JSON
{
    ...
    "overrides": {
        ...
        "vite-plugin-solid": {
           "solid-js": "1.9.4"
        }
    }
}
```

Normalement au moment de l'installation vous aurez une erreur du type : 

```
❯ npm i         
npm warn ERESOLVE overriding peer dependency
npm warn While resolving: solid-refresh@0.6.3
npm warn Found: solid-js@1.9.12
npm warn node_modules/solid-js
npm warn   solid-js@"^1.9.5" from the root project
npm warn
npm warn Could not resolve dependency:
npm warn peer overridden solid-js@"1.9.4" (was "^1.3") from solid-refresh@0.6.3
npm warn node_modules/solid-refresh
npm warn   solid-refresh@"^0.6.3" from vite-plugin-solid@2.11.12
npm warn   node_modules/vite-plugin-solid
npm warn
npm warn Conflicting peer dependency: solid-js@1.9.4
npm warn node_modules/solid-js
npm warn   peer overridden solid-js@"1.9.4" (was "^1.3") from solid-refresh@0.6.3
npm warn   node_modules/solid-refresh
npm warn     solid-refresh@"^0.6.3" from vite-plugin-solid@2.11.12
npm warn     node_modules/vite-plugin-solid
npm error code ERESOLVE
npm error ERESOLVE could not resolve
npm error
npm error While resolving: vite-plugin-solid@2.11.12
npm error Found: solid-js@1.9.12
npm error node_modules/solid-js
npm error   solid-js@"^1.9.5" from the root project
npm error
npm error Could not resolve dependency:
npm error peer overridden solid-js@"1.9.4" (was "^1.7.2") from vite-plugin-solid@2.11.12
npm error node_modules/vite-plugin-solid
npm error   dev vite-plugin-solid@"^2.11.6" from the root project
npm error
npm error Conflicting peer dependency: solid-js@1.9.4
npm error node_modules/solid-js
npm error   peer overridden solid-js@"1.9.4" (was "^1.7.2") from vite-plugin-solid@2.11.12
npm error   node_modules/vite-plugin-solid
npm error     dev vite-plugin-solid@"^2.11.6" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /home/anthony_p/.npm/_logs/2026-04-15T05_25_31_667Z-eresolve-report.txt
npm error A complete log of this run can be found in: /home/anthony_p/.npm/_logs/2026-04-15T05_25_31_667Z-debug-0.log
```

Et là beaucoup vous diront "passe --force et c'est bon" ou "--legacy-peer-deps c'est mieux !". Moi je vous dirais : à moins que vous soyez absolument sûr de vous ou que vous n'ayez pas du tout le choix temporairement : ne le faites pas !

Dans mon exemple j'ai indiqué à NPM que je voulais qu'il tire une version "1.9.4" de `solid-js` pour `vite-plugin-solid` ce qui implique qu'on casse le fonctionnement des peer dependancies (dépendance qu'on attend qui soit tirée dans l'arbre mais pas forcément par notre package). Passer `--force` ou `--legacy-peer-deps` ne va pas fonctionner ici de part le fonctionnement des peer dependancies (vous aurez la `1.9.12` qui sera prise partout dans l'arbre quand même) mais passer `--force` ou `--legacy-peer-deps` va transformer les erreurs en warnings, masquant au passage (car personne ne lit les warnings) des futurs problèmes et donc vous rendant beaucoup plus difficile la montée de version de vos dépendances dans le futur...

Donc si vous arrivez dans le cas où NPM vous propose `--force` ou `--legacy-peer-deps` corriger la source du problème, mais n'utilisez pas ces arguments !

## `npm i`, `npm ci`, `npm audit` et `package-lock.json`

Je vois souvent des gens utiliser sans distinction `npm i` et `npm ci`. Les deux sont fondamentalement différents dans l'usage :

- `npm i` va lire votre `package.json` puis va interroger le registry NPM (par défaut [https://npmjs.com/](https://npmjs.com/)) pour résoudre chaque dépendance (directe et transitive), voir s'il y a une nouvelle version à tirer qui correspond au pattern qu'on a indiqué, puis une opération de dédoublonnement va rentrer en œuvre, pour enfin créer votre dossier `node_modules` et créer (ou mettre à jour) le `package-lock.json` qui va être une image de votre dossier `node_modules` avec tout l'arbre, les checksums pour valider que les packages n'ont pas changé (et donc n'ont pas été infectés depuis la dernière fois où vous les avez téléchargés) ;
- `npm ci` quant à lui va lire le `package-lock.json`, vérifier qu'il est bien synchronisé avec le `package.json` (une section au début du `package-lock.json` reprend le contenu des `dependencies` et `devDependencies` du `package.json`), puis va télécharger en vérifiant les checksums les versions indiquées dans le `package-lock.json`, sans plus de traitement, pour créer le dossier `node_modules` ;

Donc autrement dit : `npm i` est là pour modifier / mettre à jour vos dépendances, `npm ci` pour les installer. Si vous utilisez continuellement `npm i` vous allez continuellement mettre à jour vos dépendances (en plus de largement perdre du temps sur cette étape), alors qu'avec `npm ci` vous allez installer les mêmes versions que toute l'équipe et que votre CI donc vous êtes certain que vous n'aurez pas d'écart à l'exécution. C'est encore plus important si vous exécutez du Node.js côté serveur, parce qu'avec des `npm i` vous n'avez aucune garantie que l'exécution se fera avec les mêmes versions de dépendances en production qu'en local.

Et `npm audit` qu'on voit régulièrement quand on fait des `npm i` ? C'est une commande qui va inspecter vos dépendances et va vous indiquer si c'est possible de mettre à jour des packages dans l'arbre de dépendance pour corriger des failles de sécurité. `npm audit` vous donnera les infos, `npm audit fix` va appliquer tout ce qui est sans risque d'un point de vue SemVer. Dans la plupart des cas, `npm audit fix` va essentiellement modifier le `package-lock.json` pour changer une version d'une dépendance transitive, donc vous ne voyez rien côté `package.json`.

Donc votre `package-lock.json` de facto n'est pas une simple image de votre `package.json` qui est généré et jetable sans questionnement. C'est un fichier qui a une vraiment importance en termes de sécurité et de garantie de fonctionnement dans le temps.

Autant que possible, aujourd'hui, évitez de supprimer le `package-lock.json`.

> Note : c'est un peu opaque de prime abord mais si vous utilisez la fonctionnalité d'installation des dépendances via IntelliJ (ou autre IDE de la suite de Jetbrains), il lancera la commande `npm install`, pas `npm ci`. J'imagine qu'ils ont fait ça pour éviter d'avoir à tester si le package-lock.json est synchronisé avant de lancer la commande `npm ci` ou `npm i`, mais c'est une erreur fondamentale de fonctionner comme ça...

## Renovate / Dependabot

Il existe des outils pour vous aider à maintenir vos dépendances à jour sans trop d'effort, particulièrement si vous avez un bon pipeline qui s'exécute sur vos MR et qui valide assez largement votre application !

Quelques astuces : 

- Mettez toutes vos versions en mode exacte (ne pas mettre de `~` ou `^` avant la version) ;
- Forcez-vous à mettre de l'auto-merge sur les patchs (normalement c'est censé être gratuit), sauf sur les dépendances internes à votre entreprise (j'ai rarement vu une librairie interne qui suivait entièrement le SemVer et j'ai toujours eu des soucis, donc dans le doute...) ;
- définissez un rôle tournant dans votre équipe de mise à jour des dépendances (essentiellement : regarder si le pipeline passe sur les branches de Renovate / Dependabot et cliquer sur "merge") ;

> Note : si on vous bloque pour une quelconque raison l'usage de Renovate / Dependabot, NPM fourni la commande [`npm outdated`](https://docs.npmjs.com/cli/v11/commands/npm-outdated) qui va vous indiquer toutes les mises à jours que vous pouvez faire. À défaut d'un outil qui travail à votre place, l'exécuter une fois par semaine vous permettra de rester plutôt à jour à moindre effort. Et si un jour vous trouvez `npm outdated` trop lent, vous pouvez regarder du côté de [`taze`](https://www.npmjs.com/package/taze) qui est plus rapide et permet de mettre à jour par lot les dépendances dans le `package.json`. 

## Astuce : SemVer Calculator

Si vous avez dû mal à identifier quelles versions seront tirées pour un certain pattern, vous pouvez utiliser le [npm SemVer Calculator](https://semver.npmjs.com/). C'est un outil en ligne fourni par NPM pour visualiser les versions compatibles avec un certain pattern.

![SemVer Calculateur avec une démonstration sur une version de lodash](./img/semver-calculator.png)

Par défaut c'est `lodash` qui est pointé, mais vous pouvez cibler un autre package, vous pouvez tester n'importe quel pattern de version. C'est vraiment pratique pour valider un pattern très vite, et comprendre comment tout ça fonctionne par la pratique.

## Conclusion

Les dépendances avec NPM c'est parfois un peu compliqué. Surtout parce qu'en général on ne prend pas le temps de se projeter sur le comment ça fonctionne vraiment avant d'avoir un problème et se battre avec. C'est souvent un moment où on s'est embourbé avec des mois de `--force` ou `--legacy-peer-deps`, où on a tout cassé et là on dit "NPM c'est de la m**de", alors que juste on s'est loupé.

NPM fait partie de ses outils avec des défauts historiques mais qui ont tendances à nous laisser décider pas mal de choses et de facto demande de comprendre un minimum ce qu'on fait pour que ça fonctionne dans le temps.

En tout cas, j'espère que cet article vous aidera à mieux gérer vos dépendances NPM ! 🤓

Sources :

- [npm ls](https://docs.npmjs.com/cli/v11/commands/npm-ls)
- [npm install](https://docs.npmjs.com/cli/v11/commands/npm-install)
- [npm ci](https://docs.npmjs.com/cli/v11/commands/npm-ci)
- [npm audit](https://docs.npmjs.com/cli/v11/commands/npm-audit)
- [npm outdated](https://docs.npmjs.com/cli/v11/commands/npm-outdated)
- [npm SemVer Calculator](https://semver.npmjs.com/)


Crédit photo : Générée via Mistral AI avec le prompt suivant : 

> Un style graphique inspiré de l'anime japonais Studio Ghibli, avec une ambiance chaleureuse, immersive, poétique et onirique. Une bibliothèque futuriste en bois sculpté, avec des escaliers en colimaçon, des échelles mobiles, et des étagères remplies de livres flottants. Chaque livre représente une version de package NPM (ex: lodash@4.18.1, esbuild@0.27.7) avec des titres lumineux et des couvertures colorées. Au centre, un grand hologramme central montre un arbre de dépendances NPM avec npm ls --all, avec des lignes lumineuses reliant les livres entre eux pour symboliser les relations entre les dépendances. En bas à droite, un panda roux anthropomorphe (style Ghibli, vêtements amples) est assis sur un tapis en tissu, un livre ouvert sur ses genoux (titre visible : "SemVer pour les nuls"), avec une tasse de thé fumant à côté. L'arrière-plan est rempli de détails organiques : plantes grimpantes, feuilles d'érable rouges/oranges/jaunes tombant doucement, et une lumière dorée tamisée évoquant un après-midi d'automne. Les couleurs dominantes sont des tons doux de beige, bleu pâle, orange chaud, et des accents de vert et de doré pour les éléments technologiques et naturels. L'image doit être détaillée, immersive, avec des textures riches et une atmosphère à la fois cosy et magique, mêlant technologie et nature de manière harmonieuse.