¿Qué significa el versionado semántico (`major.minor.patch`) y cuál es la diferencia entre `^` y `~` en las versiones del `package.json`?
Ver respuesta — intenta responderla en voz alta primero
Semver usa tres números: major.minor.patch. major sube cuando hay cambios que rompen la compatibilidad; minor sube cuando se agregan funciones compatibles; patch sube cuando se corrigen bugs sin romper nada. El prefijo ^ (caret) permite actualizar minor y patch (pero no major); el prefijo ~ (tilde) permite actualizar solo patch. Y el package-lock.json guarda las versiones exactas que se instalaron realmente, para que todos los equipos y servidores obtengan instalaciones idénticas y reproducibles.
Una versión como 4.19.2 se lee así:
4= major: cambios incompatibles. Actualizar de4.xa5.0.0puede romper tu código; probablemente tengas que ajustar cosas.19= minor: funciones nuevas que no rompen lo anterior. Pasar de4.19a4.20te da features nuevas y todo lo viejo sigue funcionando.2= patch: correcciones de bugs, sin funciones nuevas ni cambios que rompan. De4.19.2a4.19.3solo arreglos.
Los prefijos le dicen a npm hasta dónde puede actualizar al instalar:
^(caret): "acepta cualquier versión que no cambie el major". Es el prefijo por defecto de npm.^4.19.0permite instalar4.19.5,4.20.0,4.99.9... pero nunca5.0.0.
~(tilde): "acepta solo cambios de patch dentro del mismo minor".~4.19.0permite instalar4.19.1,4.19.9... pero no4.20.0.
- Sin prefijo (
4.19.0): versión exacta y fija, no actualiza a nada.
La lógica: ^ es más permisivo (confía en que minor y patch no rompen), ~ es más conservador (solo confía en los arreglos de bugs).
El rol de package-lock.json: el package.json guarda rangos (^4.19.0), no versiones exactas. El problema es que "el rango ^4.19.0" hoy puede resolverse a 4.19.2 y mañana a 4.20.0, dando instalaciones distintas en máquinas distintas. El package-lock.json resuelve eso: registra la versión exacta que se instaló de cada paquete y de cada sub-dependencia, más su hash de integridad. Así, cuando otra persona (o el servidor) hace npm install, obtiene exactamente el mismo árbol de dependencias. Por eso el package-lock.json debe subirse al repositorio.
Rangos en el package.json y qué versiones aceptan:
{
"dependencies": {
"express": "^4.19.0",
"cors": "~4.19.0",
"lodash": "4.19.0"
}
}
"express": "^4.19.0"(caret): acepta>=4.19.0y<5.0.0. Instalaría, por ejemplo,4.21.0o4.19.7, pero jamás5.0.0."cors": "~4.19.0"(tilde): acepta>=4.19.0y<4.20.0. Instalaría4.19.1,4.19.8, pero no4.20.0."lodash": "4.19.0"(sin prefijo): versión fija, instala exactamente4.19.0.
Comandos útiles en la terminal:
# Instalar respetando el package-lock.json (reproducible, ideal en CI/produccion):
npm ci
# Instalar respetando los rangos del package.json (puede actualizar dentro del rango):
npm install
# Ver que version EXACTA quedo instalada de un paquete:
npm ls express
Nota: npm ci borra node_modules e instala clavado a lo que dice el package-lock.json, sin tocarlo. Es lo que se usa en integración continua y despliegues para garantizar builds idénticos.
Confundir ^ con ~, o creer que ^ puede saltar de major. ^4.19.0 nunca llega a 5.0.0, solo se mueve dentro del 4.x. Otro error clásico es no subir el package-lock.json al repositorio (o borrarlo): sin él, cada npm install puede resolver los rangos a versiones distintas, y aparece el temido "en mi máquina funciona" porque cada quien terminó con versiones diferentes de las dependencias.
"Semver usa tres números: major, minor y patch. El major cambia cuando algo rompe la compatibilidad, el minor cuando se agregan funciones compatibles y el patch cuando se arreglan bugs sin romper nada. En el package.json, el caret, el símbolo circunflejo, permite actualizar minor y patch pero no major, así que ^4.19.0 puede llegar hasta antes de 5.0.0; la tilde es más estricta y solo permite patch, así que ~4.19.0 se queda dentro del 4.19. Y para que las instalaciones sean reproducibles está el package-lock.json, que guarda las versiones exactas de todo el árbol de dependencias; por eso lo subo al repo y en CI uso npm ci, que instala clavado a ese lock."
La última versión publicada de un paquete es 2.8.4. Tienes estas tres entradas. ¿Qué versión instalaría cada una?
{
"a": "^2.3.0",
"b": "~2.3.0",
"c": "2.3.0"
}
Ver respuesta
a: "^2.3.0"-> instala2.8.4. El caret permite subir minor y patch dentro del major2, así que agarra la más nueva disponible por debajo de3.0.0.b: "~2.3.0"-> instala la última2.3.xdisponible (por ejemplo2.3.9si existiera; si2.3.0es la única2.3.x, se queda en2.3.0). La tilde no salta a2.8.4porque eso cambiaría el minor.c: "2.3.0"-> instala exactamente2.3.0. Versión fija, no actualiza.