Este documento explica cómo liberar Django.
Por favor, mantenga estas instrucciones actualizadas si realiza cambios! El punto aquí es ser descriptivo, no prescriptivo, por lo que está libre de simplificar o hacer otros cambios, pero actualice este documento en consecuencia!
Hay tres tipos de liberaciones que podrías necesitar realizar:
Liberaciones de seguridad: divulgación y corrección de una vulnerabilidad. Esto generalmente implicará dos o tres liberaciones simultáneas – por ejemplo, 3.2.x, 4.0.x y, dependiendo del momento, quizás una 4.1.x.
Liberaciones de versión regular: ya sea una liberación final (por ejemplo, 4.1) o una actualización de corrección de errores (por ejemplo, 4.1.1).
Pre-liberaciones: por ejemplo, 4.2 alpha, beta o rc.
La traducción de los textos es la siguiente:
Si se trata de una liberación de seguridad, notifica con anticipación a la lista de distribución de seguridad una semana antes de la liberación real.
Revisa las notas de la liberación, buscando errores de organización y escritura. Dibuja un post de blog y anuncio de correo electrónico.
Actualiza los números de versión y crea los artefactos de la liberación.
Crea el nuevo Release en la administración en djangoproject.com.
Establece la fecha correcta pero asegúrate de que la bandera is_active esté deshabilitada.
Sube los artefactos (tarball, rueda y sumas de comprobación).
Verifica las firmas de paquete(s), comprueba si pueden instalarse y asegúrate de la funcionalidad mínima.
Sube la nueva versión(s) a PyPI.
Habilita la bandera is_active para cada liberación en la administración en djangoproject.com.
Publica el post del blog y envía los anuncios por correo electrónico.
Actualiza los números de versión después de la liberación en las ramas estables.
Agrega notas de lanzamiento vacías para el próximo parche en main y retrocede.
Hay muchos detalles, así que lee adelante.
Necesitarás algunas cosas antes de empezar. Si este es tu primer lanzamiento, necesitarás coordinarte con otro liberador para que todas estas cosas estén alineadas y escribir en la lista de correo Ops solicitando el acceso y las permisos requeridos.
Un entorno Unix con estas herramientas instaladas (en orden alfabético):
bash
git
GPG
make
man
Herramientas de hashing (generalmente md5sum, sha1sum y sha256sum en Linux, o md5 y shasum en macOS)
python
Una pareja de claves GPG. Asegúrate de que la parte privada de esta clave esté almacenada con seguridad. La parte pública necesita ser subida a tu cuenta de GitHub, y también al servidor Jenkins que ejecuta el trabajo «confirm release».
Más de una clave GPG
Si la clave que deseas usar no es tu clave de firma por defecto, necesitarás agregar -u you@example.com a cada comando de firma GPG mostrado a continuación, donde you@example.com es el dirección de correo electrónico asociada con la clave que deseas usar.
Un entorno virtual Python limpio (Python 3.9+) para construir artefactos, con las siguientes paquetes de Python instalados:
$ python -m pip install build twine
Acceso a Django’s proyecto en PyPI para subir binarios, idealmente con permisos adicionales para borrar una versión si es necesario. Crea un token de proyecto siguiendo la documentación oficial y configura tu archivo $HOME/.pypirc como se muestra a continuación:
~/.pypirc¶[distutils]
index-servers =
pypi
django
[pypi]
username = __token__
password = # User-scoped or project-scoped token, to set as the default.
[django]
repository = https://upload.pypi.org/legacy/
username = __token__
password = # A project token.
Acceso a el proyecto de Django en Transifex, con un rol de Manager. Genera un Token de API en la sección de configuración del usuario https://app.transifex.com/user/settings/api/ y configura tu archivo $HOME/.transifexrc como sigue:
~/.transifexrc¶[https://www.transifex.com]
rest_hostname = https://rest.api.transifex.com
token = # API token
Acceso al administrador de Django en djangoproject.com como «mantenedor del sitio».
Acceso a crear un post en la categoría Django Forum - Anuncios y enviar correos electrónicos a la lista de correo electrónico django-announce.
Acceso al repositorio django-security en GitHub. Entre otras cosas, esto proporciona acceso a la distribución pre-notificación (necesaria para tareas de preparación de lanzamientos de seguridad).
Acceso al proyecto Django en Read the Docs.
Unos pocos items necesitan ser atendidos antes incluso de comenzar el proceso de lanzamiento. Esto comienza aproximadamente una semana antes del lanzamiento; la mayoría de esto se puede hacer en cualquier momento que conduzca hasta el lanzamiento real.
Solicita los IDs CVE para el problema de seguridad(s) que se está liberando. Un ID CVE por problema, solicitado con Proveedor: djangoproject y Producto: django.
Generate los parches relevantes (privados) utilizando git format-patch, uno para la rama main y uno para cada rama estable que se está parcheando.
Envía la notificación previa exactamente una semana antes de la liberación de seguridad. El template para ese correo electrónico y una lista de los destinatarios están en el wiki privado django-security de GitHub. CC a los destinatarios de la notificación previa y asegúrate de incluir las IDs relevantes del CVE. Adjunta todos los parches relevantes (dirigidos a main y las ramas estables) y firma el texto del correo electrónico con la clave que utilizarás para la liberación, con un comando como:
$ gpg --clearsign --digest-algo SHA256 prenotification-email.txt
Notifica a django-announce de la próxima liberación de seguridad con un mensaje general como:
Notice of upcoming Django security releases (3.2.24, 4.2.10 and 5.0.2)
Django versions 5.0.2, 4.2.10, and 3.2.24 will be released on Tuesday,
February 6th, 2024 around 1500 UTC. They will fix one security defect
with severity "moderate".
For details of severity levels, see:
https://docs.djangoproject.com/en/dev/internals/security/#how-django-discloses-security-issues
A medida que se acerca la liberación, vigila Trac para asegurarte de que no queden bloqueadores de liberación abiertos para la próxima liberación. En circunstancias excepcionales, como para cumplir con una fecha de liberación de seguridad determinada, una liberación podría seguir adelante con un bloqueador de liberación abierto. El responsable de la liberación es confiado en la decisión de liberar con un bloqueador de liberación abierto o posponer la fecha de liberación de una liberación no de seguridad si es necesario.
Comprueba con los otros mergeadores para asegurarte de que no tengan cambios sin comitar para la liberación.
Revisa las notas de liberación, incluyendo la versión en línea para detectar cualquier enlace roto o errores reST, y asegúrate de que las notas de liberación contengan la fecha correcta.
Comprueba con cuidado que las notas de liberación mencionen los plazos de desprecación para cualquier API marcada como desprecada, y que mencionen cualquier cambio en el soporte de versión de Python.
Comprueba con cuidado que el índice de notas de liberación tenga un enlace a las notas para la nueva liberación; esto estará en docs/releases/index.txt.
Si este es un lanzamiento de características, asegúrate de que las traducciones desde Transifex hayan sido integradas. Esto se hace típicamente por parte del administrador de la traducción en lugar del lanzador, pero aquí tienes los pasos. Este proceso puede ser un poco largo, así que asegúrate de reservar entre 4-10 horas para ello y planifica este trabajo con antelación, idealmente uno o dos días antes del día de lanzamiento.
Además de tener una cuenta configurada en Transifex, el CLI debe estar disponible en tu PATH. Luego puedes obtener todas las traducciones ejecutando:
$ python scripts/manage_translations.py fetch
Este comando puede tardar un poco en ejecutarse. Cuando esté listo, inspecciona cuidadosamente el resultado para buscar posibles errores y/o advertencias. Si hay algunos, necesitarás debuguearlos y resolverlos caso por caso.
Las traducciones recién obtenidas necesitan ajustes manuales. Primero, los valores de PO-Revision-Date deben ser actualizados manualmente para que sean posteriores a POT-Creation-Date. Puedes utilizar un comando similar al siguiente para actualizar en masa todos los archivos .po (compara el diff con la rama estable relevante):
$ git diff --name-only stable/5.0.x | grep "\.po" | xargs sed -ri "s/PO-Revision-Date: [0-9\-]+ /PO-Revision-Date: $(date -I) /g"
Todos los nuevos archivos .po deben ser inspeccionados manualmente y cuidadosamente para evitar cometer cambios en un archivo sin nuevas traducciones. Además, no debe haber cambios en las «formas plurales»: si hay algunos (generalmente se reportan cambios en español y francés), necesitarás revertirlos.
Finalmente, haz commit de los archivos modificados/añadidos (ambos .po y .mo) y crea un nuevo PR dirigido a la rama estable correspondiente al lanzamiento (por ejemplo PR actualizando traducciones para 4.2).
Actualiza la página de manual de django-admin:
$ cd docs
$ make man
$ man _build/man/django-admin.1 # do a quick sanity check
$ cp _build/man/django-admin.1 man/django-admin.1
y luego haz commit de la página de manual modificada.
Si este es el «dot zero» lanzamiento de una nueva serie, crea un nuevo ramo a partir de la rama estable actual en el repositorio django-docs-translations. Por ejemplo, al lanzar Django 4.2:
$ git checkout -b stable/4.2.x origin/stable/4.1.x
$ git push origin stable/4.2.x:stable/4.2.x
Escribe el post de anuncio del lanzamiento. Puedes introducirlo en la administración en cualquier momento y marcarlo como inactivo. Aquí tienes algunos ejemplos: `ejemplo de anuncio de lanzamiento de seguridad`__, `ejemplo de anuncio de lanzamiento regular`__, `ejemplo de anuncio de pre-lanzamiento`__.
Antes de la liberación alfa, el directorio /home/www/www/media/releases/A.B debe crearse en el servidor djangoproject.
Antes del congelamiento de características, se debe crear una rama que apunte a main para preparar la próxima liberación de características. Debe revisarse y aprobarse unos días antes del congelamiento, lo que permitirá su fusión después de que se corte la rama estable. Los siguientes puntos deben abordarse en esta rama:
Actualizar el tupla VERSION en django/__init__.py, incrementando a la próxima liberación esperada (example commit).
Crear un anotado de lanzamiento para la próxima liberación de características. Utilice el anotado del lanzamiento anterior o copie los contenidos de la versión actual y elimine la mayoría de los contenidos dejando solo las cabeceras (example commit).
Eliminar las anotaciones .. versionadded:: y .. versionchanged:: en la documentación de dos liberaciones atrás, así como cualquier anotación más antigua. Por ejemplo, en Django 5.1, se eliminarán las notas para 4.2 (example commit).
Eliminar características que han alcanzado el final de su ciclo de desprecación, incluyendo sus documentaciones y la anotación .. deprecated::. Cada eliminación debe hacerse en un commit separado para claridad. En el mensaje del commit, agregue un prefijo Refs #XXXXX -- que enlace al ticket original donde comenzó la desprecación si es posible. Asegúrese de que esto se note en la sección de características eliminadas en los anotados de lanzamiento (example commit).
Incrementar las iteraciones predeterminadas PBKDF2 en django.contrib.auth.hashers.PBKDF2PasswordHasher en unos 20% (elija un número redondeado). Ejecute los tests y actualice los 3 tests de hasheador fallidos con los nuevos valores. Asegúrese de que esto se note en los anotados de lanzamiento (example commit).
Ejemplos concretos para ramas de inicio de características de liberaciones pasadas: 5.2 bootstrap, 5.1 bootstrap, 5.0 bootstrap.
Quitar secciones vacías de las notas de lanzamiento (example commit).
Construye las notas de lanzamiento localmente y lee las. Haz cualquier cambio necesario para mejorar el flujo o corregir la gramática (example commit).
Crea una nueva rama estable a partir de main. Por ejemplo, cuando se congela la característica Django 5.2:
$ git checkout -b stable/5.2.x upstream/main
$ git push upstream -u stable/5.2.x:stable/5.2.x
Al mismo tiempo, actualiza la variable django_next_version en docs/conf.py en la rama de lanzamiento estable para que apunte a la nueva versión de desarrollo. Por ejemplo, cuando creas stable/5.2.x, establece django_next_version en '6.0' en la nueva rama estable (example commit).
Ve a la página de agregar lanzamiento en el administrador, crea un objeto Release para el lanzamiento final, asegurándote de que el campo Fecha de lanzamiento esté vacío, lo que lo marca como no liberado. Por ejemplo, cuando creas stable/5.2.x, crea 5.2 con el campo Fecha de lanzamiento en blanco. Si el lanzamiento forma parte de una rama LTS, márcalo así.
Ve a la página de agregar documentación de lanzamiento en el administrador, crea un nuevo objeto DocumentRelease para el idioma inglés para el objeto de lanzamiento recién creado. No lo marques como predeterminado.
Agrega la nueva rama a Read the Docs. Dado que los nombres de versión generados automáticamente («stable-A.B.x») difieren de los nombres de versión utilizados en Read the Docs («A.B.x»), crea un ticket solicitando la nueva versión.
Solicita el nuevo clasificador en PyPI. Por ejemplo Framework :: Django :: 5.2.
Crea una página de calendario para el próximo lanzamiento en Trac. Para crear una nueva página en la Wiki, navega hasta la URL donde deseas crear la página y un botón «Crear esta página» estará disponible.
Actualiza la rama actual bajo desarrollo activa y agrega la rama de pre-lanzamiento en el proceso de lanzamiento de Django en Trac.
Updatee el archivo JSON fijo docs/fixtures/doc_releases.json para djangoproject.com, de modo que las personas sin acceso a la base de datos de producción puedan seguir ejecutando una copia actualizada del sitio de documentación (example PR). Esto se fusionará después de la última versión.
¡Este es el momento divertido! Donde realmente enviamos un lanzamiento. Si estás emitiendo múltiples versiones, repite estos pasos para cada versión.
Verifique que `Jenkins`__ está verde para las versiones(s) que estás poniendo en producción. Probablemente no debas emitir un lanzamiento hasta que esté verde, y asegúrate de que la última ejecución verde incluya los cambios que estás liberando.
Limpie las notas de lanzamiento para este lanzamiento. Haz estos cambios en main y backport a todas las ramas donde se encuentran las notas de lanzamiento para una versión particular.
Para un lanzamiento de características, elimina el encabezado EN DESARROLLO en la parte superior de las notas de lanzamiento, elimina el prefijo Expected y actualiza la fecha de lanzamiento, si es necesario (example commit).
Para un lanzamiento de parches, elimina el prefijo Expected y actualiza la fecha de lanzamiento para todas las versiones, si es necesario (example commit).
Un lanzamiento siempre comienza desde una rama de lanzamiento, por lo que asegúrate de estar en una rama estable actualizada. También debes tener disponible un entorno virtual limpio y dedicado por versión. Por ejemplo:
$ git checkout stable/4.1.x
$ git pull
Si este es un lanzamiento de seguridad, mezcla las parches adecuados desde django-security. Rebase estos parches según sea necesario para hacer que cada uno sea un commit plano en la rama de lanzamiento en lugar de un commit combinado. Para asegurarlo, mezclalos con la bandera --ff-only; por ejemplo:
$ git checkout stable/4.1.x
$ git merge --ff-only security/4.1.x
(Se asume que security/4.1.x es una rama en el repositorio django-security que contiene los parches de seguridad necesarios para la próxima versión en la serie 4.1.)
Si git se niega a fusionar con –ff-only, cambia al rama de parches de seguridad y rebasa la rama en la que vas a fusionarla (git checkout security/4.1.x; git rebase stable/4.1.x) y luego vuelve a cambiar y haz la fusión. Asegúrate de que el mensaje del commit para cada parche de seguridad explique que el commit es un parche de seguridad y que seguirá una anuncio (example security commit).
Actualiza el número de versión en django/__init__.py para la liberación. Consulta las notas sobre configurar la tupla VERSION a continuación para obtener detalles sobre VERSION (example commit).
Si se trata de un paquete pre-lanzamiento, actualiza también el clasificador «Estado de desarrollo» en pyproject.toml para reflejar esto. Un paquete pre-lanzamiento con la etiqueta rc no debe cambiar el clasificador (example commit for alpha release, example commit for beta release).
De lo contrario, asegúrate de que el clasificador esté configurado en Development Status :: 5 - Production/Stable.
Opcionalmente usa scripts auxiliares
Puedes simplificar algunos de los pasos a continuación usando scripts auxiliares del folder scripts:
Ejemplo de ejecución del script de liberación:
$ PGP_KEY_ID=<key-id> PGP_KEY_URL=<key-url> DEST_FOLDER=~/releases scripts/do_django_release.py
Verifica la liberación (después de que se subieron los artefactos):
$ VERSION=5.2.1 scripts/verify_release.sh
Etiqueta la liberación con git tag. Por ejemplo:
$ git tag --sign --message="Tag 4.1.1" 4.1.1
Puedes verificar tu trabajo ejecutando git tag --verify <etiqueta>.
Asegúrate de tener un árbol limpio absolutamente ejecutando git clean -dfx.
Ejecuta python -m build para generar los paquetes de liberación. Esto creará los artefactos de liberación (tarball y rueda) en un directorio dist/. Para Django 5.0 o anterior, necesitas ejecutar make -f extras/Makefile en su lugar.
Genera las hash de los paquetes de liberación:
$ cd dist
$ md5sum *
$ sha1sum *
$ sha256sum *
Crea un archivo «checksums», Django-<<VERSION>>.checksum.txt que contenga las hash y la información de liberación. Comienza con este modelo y inserta la versión correcta, fecha, ID de clave GPG (de gpg --list-keys --keyid-format LONG), nombre de usuario de GitHub del administrador de liberaciones, URL de liberación y checksums:
This file contains MD5, SHA1, and SHA256 checksums for the source-code
tarball and wheel files of Django <<VERSION>>, released <<DATE>>.
To use this file, you will need a working install of PGP or other
compatible public-key encryption software. You will also need to have
the Django release manager's public key in your keyring. This key has
the ID ``XXXXXXXXXXXXXXXX`` and can be imported from the MIT
keyserver, for example, if using the open-source GNU Privacy Guard
implementation of PGP:
gpg --keyserver pgp.mit.edu --recv-key XXXXXXXXXXXXXXXX
or via the GitHub API:
curl https://github.com/<<RELEASE MANAGER GITHUB USERNAME>>.gpg | gpg --import -
Once the key is imported, verify this file:
gpg --verify <<THIS FILENAME>>
Once you have verified this file, you can use normal MD5, SHA1, or SHA256
checksumming applications to generate the checksums of the Django
package and compare them to the checksums listed below.
Release packages
================
https://www.djangoproject.com/download/<<VERSION>>/tarball/
https://www.djangoproject.com/download/<<VERSION>>/wheel/
MD5 checksums
=============
<<MD5SUM>> <<RELEASE TAR.GZ FILENAME>>
<<MD5SUM>> <<RELEASE WHL FILENAME>>
SHA1 checksums
==============
<<SHA1SUM>> <<RELEASE TAR.GZ FILENAME>>
<<SHA1SUM>> <<RELEASE WHL FILENAME>>
SHA256 checksums
================
<<SHA256SUM>> <<RELEASE TAR.GZ FILENAME>>
<<SHA256SUM>> <<RELEASE WHL FILENAME>>
Firma el archivo de checksums (gpg --clearsign --digest-algo SHA256 Django-<version>.checksum.txt). Esto genera un documento firmado, Django-<version>.checksum.txt.asc que puedes verificar luego usando gpg --verify Django-<version>.checksum.txt.asc.
Ahora estás listo para poner realmente la liberación en marcha. Para hacer esto:
Crea una nueva entrada Release en el administrador de djangoproject.com. Si se trata de una liberación de seguridad, esto debe hacerse 15 minutos antes del tiempo de liberación anunciado, pero no antes:
Debes coincidir con el número de versión tal como está definido en la tarball (django-<version>.tar.gz). Por ejemplo: «5.2», «4.1.1» o «4.2rc1».
Establecer a False hasta que la versión esté completamente publicada (último paso).
Habilita si la versión forma parte de una rama LTS.
Establece la fecha de lanzamiento a hoy. Esta versión no se publicará hasta que is_active esté habilitado.
Subir los archivos tarball (django-<version>.tar.gz), rueda (django-<version>-py3-none-any.whl) y suma de comprobación (django-<version>.checksum.txt.asc) creados anteriormente.
Prueba que los paquetes de la versión se instalen correctamente utilizando pip. Aquí tienes un método simple (esto solo prueba que los binarios estén disponibles, que se instalen correctamente y que las migraciones y el servidor de desarrollo comiencen a funcionar, pero capturará errores tontos): https://code.djangoproject.com/wiki/ReleaseTestNewVersion.
Ejecuta la construcción `confirm-release`__ en Jenkins para verificar los archivos de suma de comprobación (por ejemplo, utiliza 4.2rc1 para https://media.djangoproject.com/pgp/Django-4.2rc1.checksum.txt).
Carga los paquetes de liberación en PyPI (solo carga el archivo de rueda para pre-lanzamientos):
$ twine upload --repository django dist/*
Actualiza la recién creada Release en la administración de djangoproject.com y habilita la bandera is_active.
Pushe tu trabajo y la nueva etiqueta:
$ git push
$ git push --tags
Haz que el post del blog anunciando la liberación esté en vivo.
Para una nueva versión de liberación (por ejemplo, 4.1, 4.2), actualiza la versión estable predeterminada de los documentos cambiando la bandera is_default a True en el objeto DocumentRelease apropiado en la base de datos de docs.djangoproject.com (esto automáticamente cambiará a False para todos los demás); puedes hacer esto utilizando la administración del sitio.
Crea nuevos objetos DocumentRelease para cada idioma que tenga una entrada para la liberación anterior. Actualiza el archivo `robots.docs.txt`__ de djangoproject.com copiando el resultado generado al ejecutar el comando manage_translations.py robots_txt en la rama estable actual del repositorio `django-docs-translations`__. Por ejemplo, cuando se libera Django 4.2:
$ git checkout stable/4.2.x
$ git pull
$ python manage_translations.py robots_txt
Publica el anuncio de liberación en la lista de correo django-announce y el Foro de Django. Esto debería incluir un enlace al post del blog de anuncios.
Si esta es una liberación de seguridad, envía un correo electrónico separado a oss-security@lists.openwall.com. Proporciona un asunto descriptivo, por ejemplo, «Django» más el título del problema desde las notas de la liberación (incluyendo CVE ID). El cuerpo del mensaje debería incluir los detalles de la vulnerabilidad, por ejemplo, el texto del post del blog de anuncios. Incluye un enlace al post del blog de anuncios.
Estás casi listo! Solo queda hacer:
Si esto no es una versión prelanzada, actualiza el tupla VERSION en django/__init__.py nuevamente, incrementando a lo que sea la próxima versión esperada. Por ejemplo, después de lanzar 4.1.1, actualiza VERSION a VERSION = (4, 1, 2, 'alpha', 0) (example commit).
Agrega la versión en la lista de versiones de Trac si es necesario (y haz que sea la versión predeterminada cambiando el parámetro default_version en el archivo trac.ini de code.djangoproject.com, si se trata de una versión final). La nueva versión X.Y debe agregarse después de la versión alpha y la versión predeterminada debe actualizarse después de la «versión punto cero».
Si esta fue una versión final:
Actualiza el ramal estable actual y elimina el ramal prelanzado en el proceso de lanzamiento de Django en Trac.
Actualiza la página de descarga de djangoproject.com (example PR).
Si esta fue una versión de seguridad, actualiza Archivo de problemas de seguridad con detalles sobre los problemas resueltos.
Si se trató de una versión prelanzada, es necesario actualizar los catálogos de traducciones:
Haz un nuevo ramal a partir del ramal estable recientemente lanzado:
git checkout stable/A.B.x
git checkout -b update-translations-catalog-A.B.x
Asegúrate de que el entorno virtual dedicado a la versión esté habilitado y ejecuta lo siguiente:
$ cd django
$ django-admin makemessages -l en --domain=djangojs --domain=django
processing locale en
Revisa la diferencia antes de enviarla y evita hacer cambios en los archivos .po sin nuevas traducciones (example commit).
Hacer una solicitud de extracción contra la rama estable correspondiente y fusionar una vez aprobada.
Porta adelante las traducciones de origen actualizadas a la rama main (ejemplo de commit).
Si esta era una pre-establecimiento rc, llama a las traducciones para la próxima versión en la categoría Django Forum - Internacionalización.
La informe de versión de Django se controla mediante la tupla VERSION en django/__init__.py. Esta es una tupla de cinco elementos, cuyos elementos son:
Versión mayor.
Versión menor.
Versión micro.
Estado – puede ser uno de «alpha», «beta», «rc» o «final».
Número de serie, para paquetes alpha/beta/RC que se ejecutan en secuencia (permitiendo, por ejemplo, «beta 1», «beta 2», etc.).
Para una versión final, el estado siempre es «final» y el número de serie siempre es 0. Un número de serie 0 con un estado «alpha» se reportará como «pre-alpha».
Algunos ejemplos:
(4, 1, 1, "final", 0) → «4.1.1»
(4, 2, 0, "alpha", 0) → «4.2 pre-alpha»
(4, 2, 0, "beta", 1) → «4.2 beta 1»
may 31, 2026