Una vez tenemos claros los fundamentos de GitHub Actions, vamos a presentar las acciones necesarias para definir pasos relacionados con proyectos Node.js como, por ejemplo, cómo instalar Node.js, cómo compilar el código, cómo ejecutar las pruebas automatizadas, etc.

Al finalizar, sabrá:

  • Cómo clonar el repositorio Git de trabajo.

  • Cómo instalar y configurar Node.js.

  • Cómo instalar las dependencias de un proyecto de Node.js.

  • Cómo ejecutar comandos varios durante el flujo de trabajo.

  • Cómo publicar cambios realizados por el flujo de trabajo en el repositorio de Git.

Introducción

Son muchas las acciones que podemos utilizar en nuestros flujos de trabajo de GitHub Actions. Puede echar un vistazo en https://github.com/marketplace?type=actions.

En esta lección, vamos a prestar atención a las más habituales cuando se trabaja con Node.js.

Acción actions/checkout

La acción actions/checkout (actions/checkout action) se utiliza para clonar un repositorio para su uso en un trabajo. Por esta razón, suele ser el primer paso de la mayoría de los trabajos. Tiene varias opciones que podemos indicar en su propiedad with, entre las cuales encontramos:

Propiedad Descripción
repository Repositorio a clonar. Su valor predeterminado es ${{ github.repository }}.
ref Nombre de la rama o etiqueta a usar.
token Token de acceso a usar. Valor predeterminado: ${{ github.token }}.
path Ruta dentro de la máquina virtual o contenedor donde realizar la clonación.
fetch-depth Número de confirmaciones a clonar. Valor predeterminado: 0, todas.
submodules ¿Clonar los submódulos? Valor predeterminado: true.

Lo más habitual, si consultamos flujos de trabajo ya publicados en GitHub, es un paso como el siguiente, para clonar el repositorio donde se encuentra el flujo:

- uses: actions/checkout@v3

Acciones de instalación de plataformas

GitHub Actions proporciona varias acciones para instalar plataformas de desarrollo como, por ejemplo, para Go, .NET, Node.js o Python. Vamos a centrarnos en la relacionada con Node.js.

Acción actions/setup-node

Para instalar Node.js en la máquina virtual o contenedor actual, usaremos la acción actions/setup-node (actions/setup-node action). Recuerde que la ejecución de los trabajos es aislada y, hacerlo en uno de ellos, no lo hace en los demás. Por lo tanto, si tiene varios trabajos que necesiten Node.js, tendrá que añadir un paso, en cada uno de ellos, para que se instale. Entre sus opciones with, podemos encontrar:

Propiedad Descripción
node-version Versión de Node.js a instalar: 14.x, 16.x, 18.x, latest, current, entre otras.
node-version-file Archivo a usar que contiene la versión a instalar. Sólo se usa si no se indica node-version.
architecture Arquitectura a instalar: x86; x64, la predeterminada; arm64; armv6l; armv7l; ppc64le; o s390x.
registry-url URL del registro a utilizar. Valor predeterminado: https://registry.npmjs.org.

Por lo general, la versión de Node.js a instalar se suele fijar con una expresión de GitHub Actions que hace uso de una variable de la estrategia matriz. He aquí un ejemplo ilustrativo:

- name: Instalación de Node.js ${{ matrix.node }}
  uses: actions/setup-node@v3
  with:
    node-version: ${{ matrix.node }}

Instalación de dependencias

La acción actions/setup-node no hace otra cosa que instalar Node.js. Para instalar las dependencias de nuestro proyecto, tenemos que hacerlo explícitamente en un paso aparte y posterior a la instalación de Node.js.

Cuando se instalan las dependencias en GitHub Actions, se suele usar el comando npm ci, en vez de npm i. Lo más común es tener un paso como el siguiente:

- name: Instalación de dependencias
  run: npm ci

npm ci es similar a npm i, con las siguientes características:

  • npm ci instala todas las dependencias del proyecto, no se puede usar para instalar dependencias individuales como sí puede npm i.

  • npm ci requiere que el proyecto tenga un archivo de bloqueo como, por ejemplo, package-lock.json, a diferencia de npm i que no lo exige.

  • npm ci elimina la carpeta node_modules si ya existe, antes de realizar su instalación de dependencias.

  • npm ci no escribe en los archivos package.json ni package-lock.json.

Recuerde que si tiene que instalar alguna dependencia extra, generalmente global, debe usar npm i, en vez de npm ci, pues este segundo comando no tiene la capacidad de instalar dependencias individuales.

Otro comando ampliamente utilizado es npm install-ci-test o npm cit, es similar a ejecutar npm ci && npm t.

La OpenSSF, https://openssf.org/, recomienda que el flujo de integración continua use npm i –no-package-lock, con el objeto de detectar, lo antes posible, nuevas versiones de nuestras dependencias. La opción --no-package-lock lo que dice es que no se tenga en cuenta el archivo package-lock.json.

Ejecución de comandos

Una vez instalada la plataforma de desarrollo y sus dependencias, es muy probable, tal y como veremos más adelante en el libro, que tengamos que compilar, ejecutar análisis estáticos de código, ejecutar las pruebas automatizadas, etc. Para ello, si todo está disponible en el entorno del trabajo, no hay más que usar un paso run para llevar a cabo la tarea necesaria. En Node.js, es muy habitual invocar un script definido en el archivo de metadatos package.json, pero no es obligatorio. He aquí algunos ejemplos ilustrativos:

- name: Análisis estático de código
  run: npm run lint

- name: Transpilación de TypeScript
  run: npm run tsc

- name: Generación de la documentación de la API
  run: npm run doc

- name: Ejecución de pruebas automatizadas
  run: npm t

Instalación de paquetes en ejecutores Ubuntu

Para instalar cualquier paquete de Ubuntu en un ejecutor Ubuntu, hay que usar un paso run como el que muestra el siguiente ejemplo, no olvide utilizar sudo:

- name: Instalación de redis-cli
  run: sudo apt install redis-tools

Ejecución del comando gh

Todos los trabajos pueden utilizar el comando gh sin necesidad de instalarlo explícitamente. Viene de fábrica. He aquí un ejemplo ilustrativo, ojo, no olvide el token de GitHub:

- name: Publish release
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  run: |
    filePath=dist/pdf/${{ inputs.fileName }}.pdf
    
    gh release create $version -t "Release: $version"
    gh release upload $version $filePath

Publicación de cambios en el repositorio Git

Atendiendo al flujo de trabajo, es posible que algunos trabajos realicen cambios en el código. Por ejemplo: el análisis de código podría cambiar código para adaptarlo a la guía de estilos de la organización; la publicación podría requerir cambiar la versión del paquete antes de su publicación; etc. Está claro que cuando realizamos cambios que deben persistirse en el repositorio, debemos llevarlos al repositorio de GitHub para que refleje la realidad dejada por el flujo. En este caso, el trabajo debe hacer lo que haríamos nosotros mismos si lo estuviéramos haciendo manualmente:

  1. Confirmar los cambios.

  2. Publicar los cambios en el repositorio.

Antes de realizar estos pasos, debemos configurar quién es el que lo está haciendo. Eso significa configurar, como mínimo, las variables de configuración user.name y user.email:

- name: Configuración de Git
  run: |
    git config --global user.name ${{ github.actor }}
    git config --global user.email ${{ github.actor }}@users.noreply.github.com

Se recomienda realizar esta configuración tras la clonación del repositorio, es decir, tras la acción actions/checkout.

A continuación, ya podemos confirmar los cambios y solicitar su publicación en el repositorio remoto:

- name: Publicación de cambios
  run: |
    version=$(node -p 'require("./lerna.json").version')
    git add .
    git commit -m $version
    git tag -a v$version -m $version
    git push --force

Si la rama en la que va a publicar los cambios está protegida, puede tener problemas, sobre todo si requiere una solicitud de integración aprobada por algún propietario del código (code owner).