Refinando el workflow de Hugo

Nacho - - Tiempo de lectura 4 mins

Cuando migré a Hugo, lo hice sin tener del todo claro cómo iba a ser mi workflow. Después de eso, y viendo cómo funcionaba, se me empezaron a ocurrir ideas.

El workflow de Forgejo con cron

El problema que tenía era por dos lados. Primero, estaba generando todo el sitio dos veces por día, hubiese o no contenido nuevo para subir, lo cual es relativamente caro (traer el código, instalar dependencias, generar el sitio, correr rsync). Por otro lado, estaba publicando posts que estuviesen activos sólo dos veces por día. Qué pasa si quiero algo más frecuente?

La solución a la que llegué es sacar el cron del workflow principal, y crear un workflow nuevo, liviano, que sólo verifique si hay nuevos posts para publicar, y si los hay, que llame al workflow principal.

Primero, hice un script que me liste los posts futuros:

#!/bin/sh

hugo list future | tail -n+2 | rev | cut -d, -f5 | rev

La razón por la que revierto alrededor del cut es porque el título está antes y puede tener una coma.

Luego, uso ese script en el workflow principal para guardar en el cache de Forgejo.

- name: Generar lista de posts futuros
  run: ./scripts/fechas-posts-futuros.sh > futuro.txt

- name: Guardar futuro.txt en cache
  uses: actions/cache/save@v4
  with:
    path: ./futuro.txt
    key: cache-posts-futuros-${{ forgejo.sha }}-${{ hashFiles('futuro.txt') }}

Lo siguiente es hacer otro workflow, que se encargue de verificar ese archivo guardado. Ese workflow no necesita ninguna dependencia, ni tampoco hacer checkout del código, porque toda la información necesaria está en ese archivo futuro.txt que guardé en el cache.

name: Publicar posts futuros

on:
  workflow_dispatch:
  schedule:
    - cron: '*/15 * * * *'

jobs:
  verificar-si-hay-futuro:
    runs-on: docker
    container:
      image: node:24

    outputs:
      hay_futuro_pasado: ${{ steps.validar_futuro_pasado.outputs.hay_futuro_pasado }}

    steps:
      - name: Cargar futuros.txt del cache
        uses: actions/cache/restore@v4
        with:
          path: ./futuro.txt
          key: cache-posts-futuros-
          restore-keys: |
            cache-posts-futuros-
      - name: Validar si hay algo en futuro.txt
        id: validar_futuro
        run: |
          if [ -s "futuro.txt" ]; then
            echo "hay_futuro=true" >> $FORGEJO_OUTPUT
          else
            echo "hay_futuro=false" >> $FORGEJO_OUTPUT
          fi
      - name: Verificar si fechas "futuras" que ya pasaron
        id: validar_futuro_pasado
        if: steps.validar_futuro.outputs.hay_futuro == 'true'
        run: |
          ahora=$(date +%s)
          for d in $(cat futuro.txt); do
            futuro=$(date -d $d +%s)
            if [ "$ahora" -gt "$futuro" ]; then
              echo "hay_futuro_pasado=true" >> $FORGEJO_OUTPUT
              break;
            fi
          done

  deploy-futuro:
    runs-on: docker
    needs: [verificar-si-hay-futuro]
    uses: ./.forgejo/workflows/hugo-deploy.yml
    secrets: inherit
    if: ${{ needs.verificar-si-hay-futuro.outputs.hay_futuro_pasado == 'true' }}

Este workflow corre cada 15 minutos, descarga el futuro.txt del cache, y verifica si hay alguna fecha de posts futuros que ya haya pasado. Si la hay, entonces llama al workflow principal.

Un último detalle, hay que modificar el workflow principal para que pueda ser llamado con uses:

on:
  push:
    branches: [ main ]
  workflow_dispatch:
  workflow_call: # <-- esta línea es la agregada

Con eso, debería funcionar como quiero. Un trabajo relativemente liviano (mirar un archivo en el cache y verificar las fechas que tiene) cada quince minutos, y la parte más “cara” la corro sólo cuando es necesario.

Generación de posts nuevos

El comando estándar para generar posts nuevos es

hugo new content <path>

El problema con esto es tener que pasarle el path al archivo. Estoy usando la misma estructura siempre: todos mis posts (nuevos, los del blog viejo tienen su propio estándar) están siguiendo el formato /posts/:año/:slug. Tener que escribir /posts/2025 a mano y generarle un slug también a mano. Es el tipo de cosas que pide a gritos ser automatizado (y sí, quiero seguir usando el comando de hugo porque me copia del default en archetypes). Mi objetivo es hacer algo tipo:

./scripts/post Este es el título

Y que eso me genere /posts/2025/este-es-el-título/index,md. Un script de bash sencillo:

#!/bin/bash

titulo=$*

if [ -z "$titulo" ]; then
  echo "Necesito un título"
  exit 1
fi

slug=$(echo $titulo | tr ' ' - | tr '[:upper:]' '[:lower:]')
anyo=$(date +%Y)
fullpath=posts/$anyo/$slug/index.md

hugo new content $fullpath

Lo siguiente

Todavía tengo pendiente la traducción de textos del theme. Pero bueno, estas boludeces más técnicas son más divertidas.


Este blog no tiene comentarios. Pero si querés charlar sobre cualquier post, podés mandarme un email o escribirme por Mastodon.