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
- L'inventari real de Reservalia
- Què garanteix exactament un lockfile
npm cidavant denpm install, i què passa si algú edita el lockfile a mà- Lockfiles en altres ecosistemes
- Rangs de versió i el compromís entre pedaços i reproductibilitat
- Dependències transitives: veure-les, entendre-les i forçar-les
- Actualització automatitzada: el
dependabot.ymlde Reservalia - Estratègia d'actualització: què es fusiona sol i què es revisa
- Memòria cau de dependències al pipeline i la seva invalidació
- Registres privats i rèpliques
- Triar una llibreria i jubilar les abandonades
- Errors Comuns i Consells
- Exercicis
- Conclusió
- 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 produccionpm ls --alldesplega l'arbre sencer, incloses les dependències de les dependències. Sense--allnomés en veuries el primer nivell, que és la part que ja coneixies.--omit=devdescarta les eines de desenvolupament, que no viatgen dins de la imatge.--parseableimprimeix 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.
- 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.
npm ci davant de npm install, i què passa si algú edita el lockfile a mà
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- El contingut concret és igual, perquè es reescriurà sencer.
npm install—aquí sí, noci— recalcula l'arbre a partir delspackage.jsonja fusionats. Després convé mirar el diff resultant: si apareixen cinquanta canvis que no esperaves, és que algú havia fixat alguna cosa a mà.
- 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.
- 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.
- 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 quantnpm 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ó.npm whydona 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.npm outdatedcompara 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:
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).
- Actualització automatitzada: el
dependabot.yml de Reservalia
dependabot.yml de ReservaliaAmb 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: ["*"] }directory: "/"apunta a on és elpackage-lock.json. Com que Reservalia fa servir workspaces npm amb un únic lockfile a l'arrel, una sola entrada cobreixapps/api,apps/webipackages/tipus-compartits. Amb lockfiles separats caldria una entrada per carpeta.- 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.
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.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.ignoreper a les majors de TypeScript: una pujada de major detscpot treure centenars d'errors nous, i això és feina planificada, no un PR de dimarts.- 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.
- 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:
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.
- 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 }}- # 4cache: npmasetup-nodefa exactament el de sota. És el que Reservalia fa servir des de la 02-02; aquí veiem per què funciona.- Es desa en memòria cau
~/.npm, nonode_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~/.npmi executarnpm ciés ràpid i correcte. - 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.
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.
- 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- 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. - El token es llegeix d'una variable d'entorn. Un
.npmrcamb un token literal compromès a git és una filtració de credencials, i és sorprenentment freqüent. - 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.
- 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:
- 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. - 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.
- Està viva? Commits recents, issues que es responen, més d'un mantenidor, versió major estable.
- 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.
- 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:
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
- Conceptes Bàsics de CI/CD
- Beneficis del CI/CD
- Eines Populars de CI/CD
- El Projecte del Curs: l'Aplicació que Automatitzarem
- Mètriques DORA: Com es Mesura el Lliurament de Programari
Mòdul 2: Integració Contínua (CI)
- Introducció a la Integració Contínua
- Configuració d'un Entorn de CI
- Automatització de la Construcció
- Proves Automatitzades
- Qualitat de Codi i Anàlisi Estàtica
- Artefactes, Versionat i Promoció
- Integració amb el Control de Versions
Mòdul 3: Desplegament Continu (CD)
- Introducció al Desplegament Continu
- Automatització del Desplegament
- Infraestructura com a Codi i Entorns Reproduïbles
- Estratègies de Desplegament
- Feature Flags, Rollback i Recuperació davant Errors
- Monitoratge i Retroalimentació
Mòdul 4: Pràctiques Avançades de CI/CD
- Pipelines de CI/CD
- Gestió de Dependències
- Seguretat en CI/CD
- Escalabilitat i Rendiment
- Pipeline as Code: Plantilles, Reutilització i Proves del Pipeline
- Bases de Dades al Pipeline: Migracions Segures
Mòdul 5: Implementació de CI/CD en Projectes Reals
- Cas d'Estudi: Projecte Web
- Cas d'Estudi: Aplicació Mòbil
- Cas d'Estudi: Microserveis
- Cas d'Estudi: Modernitzar un Projecte Legacy
Mòdul 6: Eines i Tecnologies
- Jenkins
- GitLab CI/CD
- CircleCI
- Travis CI
- Docker i Kubernetes
- GitHub Actions a Fons
- Comparativa i Criteris per Triar Eina
Mòdul 7: Exercicis Pràctics
- Exercici 1: Configuració d'un Pipeline Bàsic
- Exercici 2: Integració de Proves Automatitzades
- Exercici 3: Desplegament en un Entorn de Producció
- Exercici 4: Monitoratge i Retroalimentació
- Exercici 5: Enfortir el Pipeline amb Seguretat i Secrets
- Projecte Final: Pipeline Complet d'Extrem a Extrem
