Acciones de GitHub Actions para Node.js
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:
-
Confirmar los cambios.
-
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).