Das Symptom
Lokal läuft alles. Du pushst, der CI-Lauf startet, und schon der
Installationsschritt bricht ab: npm ci beschwert sich, dass package.json und
package-lock.json nicht zusammenpassen, und beendet sich mit einem Fehler.
Kein Test ist gelaufen, keine Zeile Code wurde ausgeführt.
Der naheliegende Reflex im Team ist dann, im CI-Skript npm ci gegen
npm install zu tauschen. Der Lauf wird grün. Das Problem ist damit nicht
gelöst, sondern nur unsichtbar gemacht.
Die „Layer 8“-Wahrheit
npm ci ist nicht die schnellere Variante von npm install, sondern die
strengere. Die npm-Doku sagt trocken: Stimmen die Abhängigkeiten im Package-Lock
nicht mit denen in package.json überein, beendet sich npm ci „with an error,
instead of updating the package lock“. Das „instead“ ist der ganze Punkt.
npm install würde das Lockfile in diesem Moment stillschweigend umschreiben –
in einem Wegwerf-Container, dessen Dateisystem in vier Minuten gelöscht ist.
Damit fällt genau die Zusage weg, für die es das Lockfile gibt. Ein Lockfile
existiert, damit die Maschine, die deployt, exakt dieselben Versionsnummern
installiert wie die Maschine, auf der getestet wurde. npm ci schreibt laut Doku
„never … to package.json or any of the package-locks: installs are essentially
frozen“. Wer im CI auf npm install ausweicht, tauscht diese Zusage gegen einen
grünen Haken.
Der Abbruch ist also keine Zickerei von npm, sondern ein Wächter, der genau das
tut, wofür er eingestellt wurde: Er meldet, dass zwei Dateien, die
zusammengehören, im Repository auseinanderlaufen. Die Ursache sitzt fast immer
zwei Commits vorher, wo jemand eine Version in package.json von Hand
angefasst und danach nicht installiert hat. Das Lockfile ist kein
Build-Artefakt, das man ignorieren darf – es ist Quelltext.