En tancar el mòdul 3 va quedar una pregunta sense resposta: quants paquets entren de debò a reservalia/api:a3f9c21, qui els manté i quina versió exacta es va fer servir. És el segon dels quatre fronts pendents, i el més silenciós de tots: ningú no obre un tiquet que digui "les dependències estan malament", però un dilluns qualsevol la CI es posa vermella sense que ningú hagi tocat el codi, o una actualització menor canvia el comportament d'una funció i les reserves dels negocis amb horari partit comencen a calcular-se malament. Aquesta lliçó converteix aquell punt cec en una cosa gestionada: què garanteix exactament un lockfile i què no, com es trien els rangs de versió, com s'automatitzen les actualitzacions sense ofegar l'equip en pull requests, com s'auditen les dependències transitives, com es desen en memòria cau al pipeline sense trencar la reproductibilitat, i quan una organització acaba necessitant el seu propi registre. El que no tractarem aquí són les vulnerabilitats ni els atacs a la cadena de subministrament —paquets maliciosos, npm audit com a porta de qualitat, signatura d'artefactes—: tot això és la lliçó 04-03, i s'apuntala en el que construïm avui.

Contingut

  1. L'inventari real de Reservalia
  2. Què garanteix exactament un lockfile
  3. npm ci davant de npm install, i què passa si algú edita el lockfile a mà
  4. Lockfiles en altres ecosistemes
  5. Rangs de versió i el compromís entre pedaços i reproductibilitat
  6. Dependències transitives: veure-les, entendre-les i forçar-les
  7. Actualització automatitzada: el dependabot.yml de Reservalia
  8. Estratègia d'actualització: què es fusiona sol i què es revisa
  9. Memòria cau de dependències al pipeline i la seva invalidació
  10. Registres privats i rèpliques
  11. Triar una llibreria i jubilar les abandonades
  12. Errors Comuns i Consells
  13. Exercicis
  14. Conclusió

  1. L'inventari real de Reservalia

La Marta demana la dada i en Diego la treu en trenta segons:

npm ls --all --workspace apps/api | wc -l                # 1 · l arbre complet
npm ls --omit=dev --all --parseable | sort -u | wc -l    # 2 · nomes el que arriba a produccio
  1. npm ls --all desplega l'arbre sencer, incloses les dependències de les dependències. Sense --all només en veuries el primer nivell, que és la part que ja coneixies.
  2. --omit=dev descarta les eines de desenvolupament, que no viatgen dins de la imatge. --parseable imprimeix rutes en lloc d'un arbre, així que es poden ordenar i comptar sense duplicats.

El resultat sorprèn tothom la primera vegada:

apps/api apps/web Total del monorepo
Dependències directes de producció 14 9 23
Dependències de desenvolupament 21 18 39
Paquets totals a l'arbre 612 891 1.147
Paquets que acaben dins de la imatge 418 418
Mantenidors diferents implicats ~330

Vint-i-tres decisions conscients es converteixen en més de mil paquets escrits per gent que l'equip no coneix, publicats en horaris que no controla i executats amb els mateixos permisos que el codi de Reservalia. El risc no és hipotètic: al mòdul 3 vam veure que el change failure rate es va encallar al 6,5 %, i una de les causes identificades va ser exactament això —una actualització menor d'una llibreria de dates que va canviar el comportament de parseISO amb zones horàries i va trencar calcularForats per als negocis amb horari partit, sense que cap prova ho detectés perquè cap no cobria aquell cas—. La regla que ordena la resta de la lliçó: una dependència no és codi gratis, és codi que has adoptat. Si falla, el client truca a Reservalia, no al mantenidor.

  1. Què garanteix exactament un lockfile

Un package.json declara intencions ("express": "^4.19.2" vol dir "qualsevol 4.x a partir de la 4.19.2"). Un package-lock.json registra fets: la versió exacta de cada paquet de l'arbre, d'on es va descarregar i la seva empremta criptogràfica.

{
  "node_modules/express": {
    "version": "4.19.2",
    "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
    "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
    "engines": { "node": ">= 0.10.0" }
  }
}

Tres camps i tres garanties diferents. version fixa quina versió s'instal·la, així que dues instal·lacions separades per sis mesos obtenen el mateix arbre. resolved fixa d'on ve, cosa que importa quan hi ha diversos registres configurats. I integrity és un hash del contingut del paquet: si el que s'ha descarregat no coincideix, npm avorta la instal·lació. És la diferència entre "he demanat la versió 4.19.2" i "he rebut exactament aquests bytes". Ara la part que gairebé ningú no explica: el que un lockfile NO garanteix.

Sí que garanteix No garanteix
La mateixa versió de cada paquet Que el paquet continuï estant disponible (es pot retirar del registre)
El mateix contingut, verificat per hash Que els scripts d'instal·lació facin el mateix (compilen segons la màquina)
El mateix arbre de resolució La mateixa versió de Node, del sistema o de les llibreries del SO
Reproductibilitat de les dependències Reproductibilitat de la build completa

Per això el lockfile és condició necessària però no suficient, i per això Reservalia l'acompanya d'un .nvmrc que fixa la versió de Node i d'una imatge base clavada (node:20.11.0-bookworm-slim). Les tres peces juntes —lockfile, .nvmrc, imatge base— són el que fa que a3f9c21 signifiqui alguna cosa concreta.

  1. npm ci davant de npm install, i què passa si algú edita el lockfile a mà

npm install npm ci
Llegeix el lockfile Sí, però el pot modificar Sí, i mai no el modifica
Si package.json i lockfile no concorden Resol i reescriu el lockfile Falla amb error
node_modules/ previ El reaprofita i l'apedaça L'esborra i reinstal·la
Velocitat en CI Menor i variable Més gran i constant
On fer-lo servir Al teu portàtil, en afegir un paquet Al pipeline, sempre

La fila decisiva és la segona. npm ci exigeix que el lockfile sigui coherent amb el package.json, i aquell comportament és justament el que vols en CI: si en Diego afegeix "zod": "^3.23.0" al package.json i oblida pujar el lockfile actualitzat, el job qualitat falla en vint segons amb un missatge clar, en comptes d'instal·lar en silenci una versió que ningú no ha registrat. La verificació és gratis i elimina tota una categoria de "a la meva màquina funciona". Editar el lockfile a mà és una temptació que apareix en resoldre un conflicte de merge, i mereix un advertiment explícit. Un package-lock.json no és una llista, és un graf resolt: canviar una versió a mà no recalcula les dependències d'aquella dependència, així que pots deixar l'arbre en un estat que npm no hauria produït mai —una llibreria que declara necessitar >=5.0.0 convivint amb la 4.8—. El resultat són fallades en temps d'execució impossibles de reproduir. La manera correcta de resoldre un conflicte al lockfile és regenerar-lo:

git checkout --theirs package-lock.json   # 1 · quedar-se amb una versio qualsevol
npm install                                # 2 · deixar que npm recalculi el graf
git add package-lock.json
  1. El contingut concret és igual, perquè es reescriurà sencer.
  2. npm install —aquí sí, no ci— recalcula l'arbre a partir dels package.json ja fusionats. Després convé mirar el diff resultant: si apareixen cinquanta canvis que no esperaves, és que algú havia fixat alguna cosa a mà.

  1. Lockfiles en altres ecosistemes

El concepte és universal encara que els noms canviïn. Si treballes fora de Node, aquesta taula tradueix tot l'anterior:

Ecosistema Fitxer d'intencions Lockfile Ordre reproduïble en CI
npm package.json package-lock.json npm ci
Python + Poetry pyproject.toml poetry.lock poetry install --sync
Python + pip requirements.in requirements.txt amb hashes pip install --require-hashes -r requirements.txt
Maven pom.xml No n'hi ha: versions exactes al POM mvn -B verify amb versions fixes
Gradle build.gradle gradle.lockfile (s'ha d'activar) gradle build després de --write-locks
Go go.mod go.sum (hashes) go build -mod=readonly
Rust Cargo.toml Cargo.lock cargo build --locked
PHP composer.json composer.lock composer install --no-dev

Dues observacions útils. Maven és l'excepció: no té lockfile propi, i la reproductibilitat depèn que no facis servir versions LATEST ni RELEASE i que els blocs dependencyManagement estiguin complets; per això els equips seriosos de Java hi afegeixen un plugin que fixa l'arbre. I el patró de la columna dreta és sempre el mateix: existeix un mode "instal·la exactament el que diu el lockfile i falla si no quadra". Aquella és l'ordre que va al pipeline; qualsevol altra és npm install disfressat.

  1. Rangs de versió i el compromís entre pedaços i reproductibilitat

El versionat semàntic (semver) dona a MAJOR.MENOR.PEDAÇ un significat contractual: major = canvi incompatible, menor = funcionalitat nova compatible, pedaç = correcció compatible.

Rang Significat Accepta Quan fer-lo servir
^4.19.2 Compatible amb 4.19.2 4.19.3, 4.20.0 · no 5.0.0 Per defecte en una aplicació
~4.19.2 Només pedaços 4.19.3 · no 4.20.0 Dependències sensibles o inestables
4.19.2 Exacta Només aquella Eines el comportament de les quals ha de ser idèntic
* o latest Qualsevol Tot Mai

El punt que confon molta gent: amb un lockfile, el rang gairebé no importa per a la reproductibilitat. El lockfile fixa la versió instal·lada, així que ^4.19.2 i 4.19.2 produeixen exactament el mateix arbre avui. El que el rang decideix és què s'accepta quan el lockfile es regenera: en executar npm update, en afegir un paquet o quan Dependabot proposa una pujada. I aquí hi ha el compromís real. Un rang ampli (^) fa que les correccions arribin sense fricció, però aposta que el mantenidor respecta semver, cosa que no sempre passa —l'incident de parseISO de l'apartat 1 va ser justament una versió menor amb un canvi de comportament—. Un rang estret elimina les sorpreses però converteix cada correcció en feina manual, i un projecte amb 23 dependències directes fixades a versió exacta es queda obsolet en sis mesos.

La postura de Reservalia, recomanable per a la majoria: ^ per defecte al package.json, lockfile sempre compromès a git, i actualitzacions que entren per pull request revisat amb la CI en verd, mai per resolució automàtica en el moment d'instal·lar. Així el rang descriu què és acceptable i el lockfile descriu què es fa servir, i passar d'un a l'altre és sempre un commit visible.

  1. Dependències transitives: veure-les, entendre-les i forçar-les

Una dependència transitiva és la que no vas demanar: entra perquè una de les teves dependències la necessita. Dels 1.147 paquets de Reservalia, 1.124 són transitius. No apareixen al package.json, no els va triar ningú i són la immensa majoria del codi que s'executa.

npm ls date-fns      # 1 · qui porta aquest paquet i en quina versio
npm why date-fns     # 2 · la cadena de raons, a npm 9 i posteriors
npm outdated         # 3 · que es endarrerit i quant
  1. npm ls <paquet> imprimeix el camí des de les teves dependències directes fins a ell. És l'eina que respon "i això d'on ha sortit?", que és la primera pregunta de gairebé qualsevol investigació.
  2. npm why dona el mateix en format d'explicació. Si apareixen dues versions diferents del mateix paquet, npm n'ha instal·lat totes dues —una a l'arrel i una altra imbricada—, i això causa errors desconcertants quan el paquet manté estat global.
  3. npm outdated compara el que està instal·lat amb el que s'ha publicat i separa "el que permet el teu rang" de "l'últim que existeix". És el millor termòmetre de deute de dependències en deu segons.

Quan cal forçar una versió transitiva —típicament perquè la 2.3.1 té una fallada i necessites la 2.3.2, però qui la porta encara no s'ha actualitzat—, npm ofereix overrides:

{
  "overrides": {
    "semver": "7.6.2",
    "algun-paquet": { "minimist": "1.2.8" }
  }
}

La primera forma reemplaça aquella versió a tot l'arbre, vinguin d'on vinguin; la segona limita el reemplaçament al subarbre d'un paquet concret, que és més segur quan no saps si els altres consumidors toleren el canvi. Un override és deute tècnic declarat i s'ha de tractar com a tal: estàs afirmant que una versió diferent de la que el mantenidor va provar funciona igual, i ningú no ho ha verificat més enllà de les teves proves. Reservalia s'imposa dues regles: cada entrada porta un comentari amb la raó i l'enllaç a l'issue original, i la llista es revisa una vegada al trimestre per treure el que ja sobra. L'equivalent en altres ecosistemes és resolutions (Yarn), dependencyManagement (Maven) o replace (Go).

  1. Actualització automatitzada: el dependabot.yml de Reservalia

Amb 1.147 paquets, mantenir-se al dia a mà és impossible; no fer-ho és acumular una migració gegant per d'aquí a dos anys. La solució és un bot que obre pull requests d'actualització: Dependabot (integrat a GitHub) o Renovate (més configurable i disponible a qualsevol plataforma). Reservalia tria Dependabot perquè ja el té a casa:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: npm
    directory: "/"                       # 1 · un sol lockfile: nomes cal l arrel
    schedule:
      interval: weekly
      day: tuesday                        # 2 · dimarts, no dilluns ni divendres
      time: "06:00"
      timezone: Europe/Madrid
    open-pull-requests-limit: 5           # 3 · sostre de PR oberts alhora
    groups:                               # 4 · agrupar per no ofegar l equip
      dev-menors:
        dependency-type: development
        update-types: [minor, patch]
      prod-patch:
        dependency-type: production
        update-types: [patch]
    ignore:
      - dependency-name: "typescript"     # 5 · les majors, a ma
        update-types: [version-update:semver-major]
    labels: [dependencies]
    commit-message: { prefix: "chore(deps)" }

  - package-ecosystem: github-actions      # 6 · les accions tambe son dependencies
    directory: "/"
    schedule: { interval: monthly }
    groups:
      accions: { patterns: ["*"] }
  1. directory: "/" apunta a on és el package-lock.json. Com que Reservalia fa servir workspaces npm amb un únic lockfile a l'arrel, una sola entrada cobreix apps/api, apps/web i packages/tipus-compartits. Amb lockfiles separats caldria una entrada per carpeta.
  2. Dimarts a primera hora. El dilluns l'equip buida la safata del cap de setmana i el divendres deixa PR oberts fins al dilluns. És un detall menor que canvia molt la taxa de revisió real.
  3. open-pull-requests-limit és la protecció contra l'efecte allau. Sense ell, la primera execució sobre un repositori desatès obre quaranta pull requests, l'equip s'aclapara i els ignora tots: el bot passa d'ajudar a ser soroll de fons.
  4. groups és l'opció més valuosa del fitxer: en comptes d'un PR per paquet, n'agrupa diversos en un. Reservalia ajunta totes les menors i pedaços d'eines de desenvolupament en un PR setmanal, i els pedaços de producció en un altre. De vint PR es passa a dos o tres.
  5. ignore per a les majors de TypeScript: una pujada de major de tsc pot treure centenars d'errors nous, i això és feina planificada, no un PR de dimarts.
  6. Les accions de GitHub són dependències com qualsevol altra, i solen oblidar-se. uses: actions/checkout@v4 és codi de tercers que s'executa amb accés al repositori; com es fixen de manera segura és assumpte de la 04-03.

Un detall que decideix si tot això funciona: una actualització automàtica només és segura si la CI del mòdul 2 és de fiar. El bot no llegeix el changelog ni entén el teu negoci; l'única cosa que separa "actualització aplicada sense drama" de "regressió en producció" és que les proves cobreixin el que importa. Si la suite és feble, automatitzar les dependències no redueix el risc: l'accelera. Per això aquest front ve després del mòdul 2 i no abans.

  1. Estratègia d'actualització: què es fusiona sol i què es revisa

Un bot que obre PR sense una política sobre què fer-ne només mou la feina de lloc. Aquesta és la taula que Reservalia escriu al README.md:

Tipus d'actualització Acció Qui Justificació
Pedaç de dependència de desenvolupament Auto-merge amb la CI verda Ningú Risc gairebé nul: no arriba a producció
Menor de dependència de desenvolupament Auto-merge amb la CI verda Ningú Igual; van agrupades al PR setmanal
Pedaç de dependència de producció Revisió ràpida i merge Torn setmanal Sol ser una correcció; la CI cobreix la resta
Menor de producció Revisió real: llegir el changelog Torn setmanal Aquí viu l'incident de parseISO
Major de qualsevol tipus Tiquet planificat La Marta l'assigna Canvis incompatibles: és feina
Actualització de seguretat Prioritat màxima Nuria Vegeu 04-03

Tres decisions mereixen explicació. L'auto-merge no és deixadesa: s'apuntala en el fet que el pipeline bloqueja el merge si alguna cosa falla —la porta de qualitat de la 04-01— i en el fet que aquelles dependències no viatgen dins de la imatge. S'activa amb una línia:

gh pr merge --auto --squash "$PR"    # espera els checks i fusiona nomes si son verds

Les menors de producció porten revisió humana precisament per l'incident de l'apartat 1. Semver és una promesa, no un mecanisme: un canvi de comportament sense canvi d'API és formalment "menor" i funcionalment una bomba. Llegir el changelog d'un paquet costa dos minuts i és l'única defensa real. Hi ha un torn setmanal amb nom. Sense una persona assignada, les actualitzacions són responsabilitat de tothom, que és el mateix que de ningú. Reservalia rota el torn cada setmana amb un temps acotat —mitja hora els dimarts—; el que no hi cap es queda per a la següent, però mai no s'acumula en silenci.

  1. Memòria cau de dependències al pipeline i la seva invalidació

Descarregar 1.147 paquets a cada job és la part més lenta i menys interessant de la CI. La memòria cau ho elimina, però introdueix un risc evident: si serveix una memòria cau equivocada, el pipeline verifica una cosa que no és el teu codi. La solució és que la clau de la memòria cau sigui un hash del lockfile.

      - uses: actions/setup-node@v4
        with:
          node-version-file: .nvmrc
          cache: npm                      # 1 · drecera: ja fa servir el hash del lockfile
      - run: npm ci

      # Equivalent explicit, per entendre que fa la drecera:
      - uses: actions/cache@v4
        with:
          path: ~/.npm                                                     # 2
          key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}   # 3
          restore-keys: npm-${{ runner.os }}-                               # 4
  1. cache: npm a setup-node fa exactament el de sota. És el que Reservalia fa servir des de la 02-02; aquí veiem per què funciona.
  2. Es desa en memòria cau ~/.npm, no node_modules/. La primera és la memòria cau de descàrregues de npm, independent de la plataforma; la segona conté binaris compilats per a un sistema concret i enllaços interns, i restaurar-la en una altra màquina produeix fallades difícils de diagnosticar. Restaurar ~/.npm i executar npm ci és ràpid i correcte.
  3. La clau conté el hash del lockfile. Si el lockfile canvia un sol byte, la clau canvia i la memòria cau anterior no es fa servir: la invalidació és automàtica i no depèn que ningú se'n recordi.
  4. restore-keys és la xarxa de seguretat: si no hi ha coincidència exacta, es recupera la memòria cau més recent que comenci igual i npm només descarrega el que falti. Així una actualització de dependències no obliga a baixar-ho tot des de zero.

La regla general, vàlida per a qualsevol memòria cau del pipeline: la clau ha de derivar del contingut de les entrades. Si la clau és fixa (key: npm-cache), la memòria cau no s'invalida mai i acabes executant proves contra dependències antigues —un verd fals, el risc transversal que desenvolupa la 04-04—. I una regla d'higiene: la memòria cau és un accelerador, mai una font de veritat; el pipeline ha de funcionar correctament si es buida sencera, només que més lent.

  1. Registres privats i rèpliques

Reservalia instal·la directament de registry.npmjs.org, i amb 340 negocis de pagament això comença a incomodar per tres raons concretes: si el registre públic pateix una caiguda, no es pot desplegar —i respondre a un incident pot exigir desplegar—; no hi ha on publicar @reservalia/tipus-compartits el dia que s'hagi de compartir amb l'aplicació mòbil del mòdul 5; i no existeix cap punt on aplicar una política del tipus "aquesta llibreria no es fa servir en aquesta empresa".

Opció Què aporta Cost Quan té sentit
Registre públic directe Simplicitat total Zero Equips petits, sense paquets propis
GitHub Packages Paquets privats amb els permisos del repositori Molt baix si ja fas servir GitHub El pas següent natural de Reservalia
Artifactory / Nexus Rèplica del públic + registre privat + polítiques Alt: és infraestructura per mantenir Diverses desenes de desenvolupadors o requisits d'auditoria
Rèplica de només lectura Aïlla de caigudes i de paquets retirats Mitjà Quan el desplegament no pot dependre d'un tercer

La configuració és un fitxer, i convé entendre què fa cada línia:

# .npmrc
@reservalia:registry=https://npm.pkg.github.com     # 1 · nomes l ambit propi
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}        # 2 · des de variable, mai literal
registry=https://registry.npmjs.org                  # 3 · la resta, del public
  1. Només els paquets @reservalia/* es busquen al registre privat. Redirigir-ho tot a un registre privat sense rèplica configurada trenca la instal·lació dels altres 1.100 paquets.
  2. El token es llegeix d'una variable d'entorn. Un .npmrc amb un token literal compromès a git és una filtració de credencials, i és sorprenentment freqüent.
  3. L'ordre importa: la línia general va al final i actua com a valor per defecte.

I un avís sobre el conegut "paquet que desapareix": un mantenidor pot retirar una versió del registre públic i trencar builds que feia anys que funcionaven. L'integrity del lockfile no ajuda —verifica el contingut, no garanteix la disponibilitat—. Només una rèplica amb còpia local en protegeix, i és la raó principal per la qual una organització mitjana n'acaba muntant una.

  1. Triar una llibreria i jubilar les abandonades

La millor gestió de dependències és no tenir la dependència. Abans d'afegir-ne una, Reservalia es fa cinc preguntes en aquest ordre:

  1. Ho puc resoldre amb la biblioteca estàndard? Node 20 porta fetch, un executor de proves, crypto.randomUUID() i força coses més. Moltes dependències històriques avui sobren.
  2. Quant codi estic adoptant? Una utilitat de vint línies copiada i amb proves pròpies és preferible a un paquet que n'arrossega uns altres trenta.
  3. Està viva? Commits recents, issues que es responen, més d'un mantenidor, versió major estable.
  4. Quant costaria sortir-ne? Una llibreria encapsulada darrere d'un mòdul propi se substitueix en un dia; una els tipus de la qual apareixen en dos-cents fitxers és un matrimoni.
  5. Quina llicència té? Se sol mirar tard, i és un problema legal, no tècnic.

A l'altre extrem hi ha les dependències abandonades, amb símptomes clars: sense publicacions en dos anys, issues sense resposta, un avís de deprecació en instal·lar o incompatibilitat amb les versions noves del llenguatge. La reacció correcta no és la urgència —una llibreria estable i sense canvis pot estar senzillament acabada— sinó el registre: anotar-la en una llista de riscos, encapsular-la darrere d'una interfície pròpia per poder canviar-la sense tocar dos-cents fitxers, i planificar-ne la substitució quan bloquegi alguna cosa. El que no cal fer és descobrir-ho el dia que una fallada de seguretat obliga a actualitzar i resulta que no hi ha cap versió a la qual actualitzar.

Errors Comuns i Consells

Error 1: no comprometre el lockfile a git. Sense ell, cada instal·lació resol pel seu compte i "a la meva màquina funciona" torna a ser un argument vàlid. Error 2: fer servir npm install al pipeline, que pot reescriure el lockfile dins del runner i fer que la CI verifiqui un arbre diferent del que vas revisar.

Error 3: editar el lockfile a mà per resoldre un conflicte, deixant un graf que npm no hauria produït mai; regenera'l. Error 4: fixar-ho tot a versió exacta "per seguretat", cosa que congela el projecte i converteix cada correcció en feina manual.

Error 5: activar Dependabot sense groups ni límit de PR. Quaranta pull requests el primer dimarts, i l'equip aprèn a ignorar l'etiqueta dependencies per sempre. Error 6: automatitzar actualitzacions amb una suite de proves feble: no redueixes el risc, l'acceleres. Error 7: desar en memòria cau node_modules/ en lloc de ~/.npm, o fer servir una clau de memòria cau fixa que no s'invalida mai; totes dues coses produeixen verds falsos. Consell 1: mira l'arbre una vegada al trimestre. npm ls --all i npm outdated en deu minuts donen una idea molt clara de per on arribarà el pròxim ensurt. Consell 2: encapsula les dependències importants darrere d'un mòdul propi, perquè substituir-les sigui feina d'un dia. Consell 3: tracta els overrides com a deute, amb comentari, raó i revisió trimestral.

Exercicis

Exercici 1

La CI d'un equip es posa vermella un dilluns al matí sense que ningú hagi tocat el codi: l'últim commit és de divendres i aleshores era verd. Al pipeline fan servir npm install. Explica amb detall què ha passat, per què el package-lock.json no ho va impedir i quins dos canvis ho eviten definitivament.

Exercici 2

Dissenya la política d'actualització de dependències per a un equip de 4 persones amb una suite que cobreix el 45 % del codi i cap prova d'integració. Indica què canviaries respecte de la taula de l'apartat 8 i per què, i què faries primer.

Exercici 3

Reservalia necessita la versió 7.6.2 de semver perquè la 7.5.4 té una fallada, però la porta algun-linter com a transitiva i el seu mantenidor encara no ha publicat la correcció. Escriu la solució, explica'n els riscos i digues com evitaries que aquell apedaçament es quedi deu anys al repositori.

Solucions

Solució 1. El que ha passat: npm install no està obligat a respectar el lockfile. Si el package.json declara rangs amb ^ i durant el cap de setmana es van publicar versions noves, npm install les pot resoldre i instal·lar un arbre diferent del registrat, reescrivint el lockfile dins del runner —on ningú no en veu el diff—. N'hi ha prou que una dependència, directa o transitiva, publiqués una versió menor amb un canvi de comportament perquè la suite falli. El lockfile no ho va impedir perquè hi era, però no s'estava fent servir com a norma: descrivia un arbre que l'ordre no tenia obligació de reproduir. És exactament l'escenari de l'incident de parseISO. Els dos canvis: (1) substituir npm install per npm ci a tots els jobs, que instal·la l'arbre del lockfile sense desviar-se'n i falla si el lockfile no concorda amb el package.json; i (2) canalitzar les actualitzacions per pull request amb Dependabot, de manera que cada canvi de l'arbre sigui un commit revisable, amb la seva pròpia CI i amb el diff del lockfile al davant. Un tercer canvi complementari, més barat del que sembla: fixar també .nvmrc i la imatge base, perquè un canvi de versió de Node produeix el mateix símptoma —"vermell sense tocar res"— i es diagnostica igual de malament.

Solució 2. La dada que governa la resposta és la qualitat de la suite: amb un 45 % de cobertura i sense proves d'integració, la CI no és una xarxa de seguretat suficient, i la política de l'apartat 8 —que s'apuntala precisament en aquella xarxa— no es pot copiar tal qual. Canvis respecte de la taula: eliminar l'auto-merge de producció completament, inclosos els pedaços; mantenir l'auto-merge només per a dependències de desenvolupament, la fallada de les quals es manifesta al mateix pipeline i no a producció; agrupar-ho tot en un únic PR setmanal perquè la revisió càpiga en el temps real d'un equip de quatre; i abaixar la freqüència a quinzenal si l'equip no hi arriba, assumint el retard de manera conscient en lloc d'acumular PR ignorats.

Què faria primer: no tocar la configuració del bot, sinó la suite de proves. Concretament, escriure proves d'integració sobre els tres o quatre camins crítics de negoci, que és el que converteix una CI decorativa en una porta. Automatitzar dependències amb una xarxa foradada és substituir un risc lent i visible per un altre de ràpid i invisible. Mentrestant, una mesura barata i molt eficaç: activar Dependabot només per a alertes de seguretat, que són les que no es poden posposar, i deixar les de manteniment per quan existeixi la xarxa.

Solució 3. La solució és un override al package.json de l'arrel:

{
  "overrides": {
    "algun-linter": { "semver": "7.6.2" }
  }
}

Es fa servir la forma imbricada i no la global expressament: limita el reemplaçament al subarbre d'algun-linter, de manera que si un altre paquet depèn d'una semver antiga per una raó legítima, no se'n veu afectat. La forma global seria més còmoda i més arriscada. Riscos: s'està executant algun-linter amb una versió de semver que el seu mantenidor no ha provat; si la 7.6.2 va canviar algun comportament subtil, la fallada apareixerà dins d'una dependència, que és el pitjor lloc per diagnosticar-la. A més, l'override és silenciós: no torna a fallar quan deixa de ser necessari, simplement es queda allà fixant una versió cada vegada més antiga i bloquejant futures actualitzacions de tot aquell subarbre.

Com evitar que es fossilitzi, amb tres mesures de cost gairebé nul: un comentari al costat de l'entrada amb la data, la raó i l'enllaç a l'issue obert a algun-linter; una revisió trimestral de la llista completa d'overrides dins del torn de dependències de l'apartat 8; i, la més eficaç, subscriure's a l'issue, de manera que quan el mantenidor publiqui la correcció arribi un avís i l'apedaçament es retiri el mateix dia. Un override sense data de caducitat és indistingible d'una decisió permanent que ningú no va prendre.

Conclusió

El punt cec ha deixat de ser-ho. Reservalia sap que dins de reservalia/api:a3f9c21 hi viatgen 418 paquets d'uns 330 mantenidors, i sap veure'ls, fixar-los i actualitzar-los. Té clar què garanteix un lockfile —versió, origen i hash— i què no —disponibilitat, entorn, build completa—, i per això l'acompanya de .nvmrc i d'una imatge base clavada. Fa servir npm ci al pipeline, amb la qual cosa un lockfile incoherent falla en vint segons en lloc d'instal·lar en silenci alguna cosa que ningú no ha revisat. Sap que un lockfile es regenera i mai no s'edita a mà, i coneix l'equivalent del patró a Poetry, Gradle, Go, Cargo i Composer. Manté els rangs en ^ perquè les correccions puguin arribar, recolzant-se en el fet que el lockfile decideix el que de debò s'instal·la. I veu les seves 1.124 dependències transitives amb npm ls i npm why, fent servir overrides amb comentari i data de revisió només quan no queda cap altre remei. Sobretot, les actualitzacions han deixat de ser un esdeveniment traumàtic anual per convertir-se en un flux: un .github/dependabot.yml amb agrupació, límit de PR i calendari raonable; una política escrita que separa el que es fusiona sol del que exigeix llegir un changelog; un torn setmanal amb nom i mitja hora acotada; i una memòria cau la clau de la qual és el hash del lockfile, de manera que s'invalida sola i mai no produeix un verd fals. L'incident de parseISO que contaminava el change failure rate té ara dues defenses: revisió humana per a les menors de producció i una prova que cobreix el cas que es va trencar.

Queda un flanc per cobrir, i és el que converteix tot l'anterior en un problema de seguretat i no només de manteniment. Aquelles 1.147 dependències es descarreguen d'un registre públic, s'executen amb els permisos del pipeline i acaben dins d'una imatge que es desplega a producció; i el mateix pipeline, mentrestant, desa credencials sobre AWS, executa accions de tercers fixades per etiqueta i publica els artefactes en què tothom confia. La lliçó Seguretat en CI/CD aborda les dues meitats del problema —la seguretat del programari que passa pel pipeline i la seguretat del pipeline mateix—, afegeix a Reservalia un job seguretat amb una política de severitats que l'equip pugui sostenir, i acaba on acaba aquesta cadena de confiança: l'inventari signat del que conté cada artefacte.

Curs de CI/CD: Integració i Desplegament Continu

Mòdul 1: Introducció al CI/CD

Mòdul 2: Integració Contínua (CI)

Mòdul 3: Desplegament Continu (CD)

Mòdul 4: Pràctiques Avançades de CI/CD

Mòdul 5: Implementació de CI/CD en Projectes Reals

Mòdul 6: Eines i Tecnologies

Mòdul 7: Exercicis Pràctics

Mòdul 8: Recursos Addicionals

© Copyright 2026. Tots els drets reservats