Genera tu Changelog con Conventional Commits

Cuando entregamos software a nuestros usuarios, testers u otros desarrolladores es importante proporcionar una lista de cambios incorporados, errores solucionados y tareas secundarias relevantes para el lanzamiento de nuestras aplicaciones, juegos, servicios o bibliotecas.

El Changelog es una herramienta relevante para lograr este propósito. Muchos proyectos de código abierto, como Eureka o GRDB.swift, por ejemplo, tienen un Changelog muy completo y detallado, y en ambos casos aportan mucha información al respecto.

/img/bank-phrom-Tzm3Oyu_6sk-unsplash.jpg

Usemos máquinas para imprimir. Photo by Bank Phrom on Unsplash

Nota: No estoy diciendo que Eureka y GRDB.swift generen su Changelog usando esta técnica, pero sí es importante notar la relevancia que el registro de cambios tiene para ambos proyectos.

Qué es Conventional Commits

Es en este punto donde cabe hablar de Conventional Commits, una especificación pensada para armonizar los mensajes de commit que escribimos para mejorar su legibilidad y, al mismo tiempo, permitir la automatización de algunas tareas –sí, tareas como un Changelog, pero de eso hablaremos en un momento.

En resumen, Conventional Commits es una convención simple para escribir mensajes de commit en nuestro sistema de control de cambios preferido, Git. Se trata de cambiar nuestros mensajes de commit para evitar incluir mensajes de commit simples y sin estructura como estos

Fix crash on login

Cómo se usa

Dentro de la especificación de Conventional Commits se señala que la estructura básica de un commit debe ser

<tipo>[alcance opcional]: <descripción>

[cuerpo opcional]

[footer(s) opcional(es)]

El ejemplo anterior, siguiendo las convenciones de Conventional Commits y con una explicación adicional quedaría de la siguiente forma:

fix: the application will no longer crash on login with invalid email addresses

The application was trying to force the user input as an email by validating it using a
RegEx but the exceptions were not correctly handled

Y aunque este commit de ejemplo está omitiendo mostrar otras características de Conventional Commits como un footer, los ejemplos adicionales, mucho más completos, se pueden consultar en el sitio https://www.conventionalcommits.org/en/v1.0.0/#examples.

Integrar Conventional Commits a tu proyecto

Para esta parte de la configuración vamos a utilizar Husky, una herramienta de desarrollo que agrega Git hooks a nuestro proyecto que serán útiles para validar, primero, que nuestros commits cumplan adecuadamente con las reglas de Conventional Commits.

Husky se instala utilizando Node.js, por lo que la integración con proyectos basados en NPM tales como Angular, backend Node.js y algunos otros es inmediata, pero eso no significa que para un proyecto Swift, Kotlin o Java sea inservible, simplemente se requiere inicializar un archivo package.json para mantener las dependencias de desarrollo del proyecto instaladas.

Si tu proyecto está basado en NPM puedes omitir este paso.

Si no tienes un archivo package.json puedes inicializarlo en tu directorio principal de trabajo del repositorio con el comando

npm init -y

Puedes consultar la documentación de NPM para este comando aquí.

El siguiente paso es instalar Husky y las herramientas de Conventional Commits

npm i husky --save-dev

Una vez instalado Husky, habilitamos los Git hooks

npx husky install

Para evitar que otras personas que colaboran en nuestro repositorio deban configurar Husky de forma manual, podemos habilitar una configuración Post-Install en el archivo package.json, agregando la sección de scripts como sigue:

  "scripts": {
    "postinstall": "husky install"
  }

luego de Husky incorporamos las dependencias de commitlint,

npm i --save-dev @commitlint/{config-conventional,cli}

Y agregaremos un archivo .commitlintrc.json especificando las reglas de validación de los commits

{
  "extends": ["@commitlint/config-conventional"]
}

Finalmente, configuraremos el hook de validación de los commits

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

A partir de este momento, si creamos un nuevo commit con un mensaje sin seguir las reglas de Conventional Commits el Hook de Husky va a regresar un error:

/img/bad-commit-error.png

Un error de validación de Conventional Commits

¿Y el Changelog?

En la sección anterior instalamos y configuramos hooks de Git y Commitlint con la configuración Conventional Commits para validar nuestros mensajes de commit. La siguiente etapa en este proceso será instalar una nueva dependencia, Standard Version con la que generaremos un archivo CHANGELOG.md de forma automática, basándonos en la información de nuestros commits, y también manteniendo un rastreo de las versiones lanzadas de nuestro software.

Para instalar la dependencia debemos ejecutar

npm i --save-dev standard-version

Luego, podemos complementar nuestro archivo package.json con más scripts que serán útiles en un momento:

  "scripts": {
    "postinstall": "husky install"
    "release": "standard-version",
    "release:minor": "standard-version --release-as minor",
    "release:patch": "standard-version --release-as patch",
    "release:major": "standard-version --release-as major"
  },

Y finalmente deberemos crear un archivo .versionrc.json con el siguiente contenido –que también puedes editar de acuerdo a tus necesidades:

{
  "header": "Changelog",
  "types": [
    {"type": "feat", "section": "Features"},
    {"type": "fix", "section": "Bug Fixes"},
    {"type": "chore", "hidden": true},
    {"type": "docs", "hidden": true},
    {"type": "style", "hidden": true},
    {"type": "refactor", "hidden": true},
    {"type": "perf", "hidden": true},
    {"type": "test", "hidden": true}
  ],
  "commitUrlFormat": "{{host}}/{{owner}}/{{repository}}/commit/{{hash}}",
  "compareUrlFormat": "{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}",
  "issuePrefixes": ["#"],
  "issueUrlFormat": "{{host}}/{{owner}}/{{repository}}/issues/{{id}}"
}

Aquí hace falta hacer algunas sustituciones de acuerdo a tu proyecto, específicamente en los campos commitUrlFormat, compareUrlFormat y issueUrlFormat. Para un ejemplo en Github sobre el repositorio demo de este artículo, las URLs se sustituyen como

  "commitUrlFormat": "https://github.com/OrlSan/husky-conventional-commits-demo/commit/{{hash}}",
  "compareUrlFormat": "https://github.com/OrlSan/husky-conventional-commits-demo/compare/{{previousTag}}...{{currentTag}}",
  "issuePrefixes": ["#"],
  "issueUrlFormat": "https://github.com/OrlSan/husky-conventional-commits-demo/issues/{{id}}"

Todas las opciones y detalles de uso para este archivo los puedes encontrar directamente en https://github.com/conventional-changelog/conventional-changelog-config-spec/blob/master/versions/2.1.0/README.md#types

En este punto, con todo creado, lo que resta es crear el archivo CHANGELOG

npm run release -- --first-release

Empujando los cambios a nuestro repositorio (sin olvidar empujar los tags también) con el comando git push --tags.

Repositorio demo

Si te interesa conocer el repositorio que he usado como ejemplo para este post puedes verlo en https://github.com/OrlSan/husky-conventional-commits-demo

El archivo CHANGELOG.md del proyecto incluye links hacia el commit que resuelve el Issue #3, por ejemplo.

/img/changelog-final.png

Conclusiones

Agregar y configurar Husky, Commitlint con la configuración Conventional Commits y Standard Version es una tarea un poco complicada. Sin embargo los beneficios a nivel documentación, rastreo de cambios y legibilidad del proyecto no solo para quienes desarrollan el Software sino para usuarios o testers son muchos.

En mi opinión es un aporte muy grande y, en el futuro, esto nos ahorrará la necesidad de escribir mucha documentación respecto a cambios. Tendremos a la vista un archivo CHANGELOG.md con el registro detallado de los cambios y los links a los commits que introducen nuevas características o arreglan errores de forma muy rápida.

Referencias

git  ci  cd