From 94eab19dd52c94e5a488a5891822e2f4ccab90b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Cierzniakowski?= Date: Tue, 11 Aug 2026 13:08:03 +0200 Subject: [PATCH] feat: add build-site-with-hugo as a shared composite action --- README.md | 1 + build-site-with-hugo/README.md | 98 ++++++++++++++++++++++++++++++++ build-site-with-hugo/action.yaml | 53 +++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 build-site-with-hugo/README.md create mode 100644 build-site-with-hugo/action.yaml diff --git a/README.md b/README.md index a886711..352efbf 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ przy pobieraniu akcji, patrz „Dostęp" niżej. | Akcja | Do czego | | --- | --- | +| [`build-site-with-hugo`](build-site-with-hugo/) | Hugo w podanej wersji + build do `public/`, opcjonalny serwer dla bramek jakości | | [`pnpm-install`](pnpm-install/) | corepack + `pnpm install` z lockfile'a, opcjonalny cache store'a | | [`resolve-buildx-cache`](resolve-buildx-cache/) | `DOCKER_CACHE_*` z runnera → `driver-opts` i refy cache'a dla buildx | diff --git a/build-site-with-hugo/README.md b/build-site-with-hugo/README.md new file mode 100644 index 0000000..67f5747 --- /dev/null +++ b/build-site-with-hugo/README.md @@ -0,0 +1,98 @@ +# build-site-with-hugo + +Instaluje Hugo w podanej wersji i buduje stronę do `public/`. + +```yaml + - uses: actions/checkout@v7 + - uses: https://git.cierzniak.it/gitea-runner/actions/build-site-with-hugo@v1 + with: + version: '0.154.5' +``` + +Bramka jakości — ten sam build plus serwer na czas audytu: + +```yaml + - uses: https://git.cierzniak.it/gitea-runner/actions/build-site-with-hugo@v1 + with: + version: '0.154.5' + serve-port: '8080' + + - run: pa11y-ci +``` + +`args` zastępuje **całą** listę flag, więc `--gc --minify` trzeba powtórzyć: + +```yaml + - uses: https://git.cierzniak.it/gitea-runner/actions/build-site-with-hugo@v1 + with: + version: '0.154.5' + args: --gc --minify --baseURL http://localhost:8080/ +``` + +## Wejście + +| Input | Wymagany | Domyślnie | Co to | +| --- | --- | --- | --- | +| `version` | tak | — | Wersja Hugo, bez wiodącego `v` (np. `0.154.5`) | +| `args` | nie | `--gc --minify` | Pełna lista argumentów `hugo` | +| `serve-port` | nie | — | Port, na którym wystawić `public/`. Pusty = nie serwuj | + +Outputów nie ma. + +Akcja buduje w katalogu roboczym joba i serwuje `public/` stamtąd — strona musi leżeć w głównym +katalogu repo. `http-server` przy `serve-port` pochodzi z obrazu joba; bramki w wizytówkach jadą +na `git.cierzniak.it/gitea-runner/node-chrome:24`. + +## Wersja bez duplikatu + +`version` jest wymagany, więc wpisany na sztywno w workflow dubluje wersję z `Dockerfile` +i rozjedzie się przy pierwszym bumpie obrazu. Odczytaj ją w jobie, zamiast przepisywać: + +```yaml + - name: Resolve Hugo version from Dockerfile + id: hugo + run: | + version="$(sed -n 's|^FROM hugomods/hugo:exts-\([0-9][0-9.]*\) .*|\1|p' Dockerfile | head -1)" + if [ -z "$version" ]; then + echo "Nie udało się odczytać wersji Hugo z Dockerfile (zmienił się format linii FROM?)." >&2 + exit 1 + fi + echo "version=$version" >> "$GITHUB_OUTPUT" + + - uses: https://git.cierzniak.it/gitea-runner/actions/build-site-with-hugo@v1 + with: + version: ${{ steps.hugo.outputs.version }} +``` + +`Dockerfile` zostaje wtedy jedynym źródłem prawdy, a CI audytuje dokładnie tę wersję, która +jedzie na produkcję. + +## Dlaczego tak + +**Wersja przychodzi wyłącznie inputem, akcja nie zagląda do `Dockerfile`.** Odczyt po stronie +akcji wiązałby ją z konwencją budowania obrazu, której repo-konsument nie musi mieć — a to jest +jedyna rzecz, jakiej Hugo do zbudowania strony nie potrzebuje. Cenę (możliwy duplikat wersji) +zdejmuje wzorzec wyżej: `Dockerfile` dalej jest źródłem prawdy, tylko czyta go workflow. + +**Bez kroku weryfikującego, czy `hugo version` zgadza się z inputem.** W lokalnych kopiach tej +akcji krok istniał, bo wersja pochodziła z `sed`-a i trzeba było sprawdzić, czy wyrażenie coś +złapało. Przy jawnym inpucie `peaceiris/actions-hugo` instaluje dokładnie żądaną wersję i wpycha +swój katalog na początek `PATH`, więc asercja porównywałaby input sam ze sobą. Zostaje wąski +przypadek: obraz joba z własnym Hugo, gdyby kiedyś wygrał `PATH` — job zbuduje się wtedy cicho +inną wersją. Jeśli to wystrzeli, krok wraca. + +**`extended: true` na sztywno.** Wszystkie strony na tej instancji jadą na obrazach +`hugomods/hugo:exts-*`, czyli extended. Input dojdzie, gdy pojawi się konsument, który go +potrzebuje. + +**`args` to jeden input z pełną listą flag.** Kto go podaje, przejmuje całość. Dwa inputy +(`minify: bool` plus `extra-args`) wymagałyby reguły, co się dzieje, gdy jeden zaprzecza +drugiemu — tu takiego stanu nie ma. + +**Wyjście `http-server` idzie do `/tmp/http-server.log`, nie do potoku kroku.** Proces w tle +z otwartym stdout trzyma potok otwarty i runner czeka na EOF długo po zakończeniu audytu — +w `cierzniakowski-pl/wizytowka` przebieg 2145 wisiał na tym do timeoutu. + +**Po starcie serwera leci polling `curl`, nie `sleep`.** Bramka, która ruszy przed serwerem, +pada na „no FCP", a ten komunikat czyta się jak problem ze stroną, nie z CI. Sześćdziesiąt prób +co sekundę, potem twardy błąd z numerem portu. diff --git a/build-site-with-hugo/action.yaml b/build-site-with-hugo/action.yaml new file mode 100644 index 0000000..566823a --- /dev/null +++ b/build-site-with-hugo/action.yaml @@ -0,0 +1,53 @@ +name: Build site with Hugo +description: >- + Installs the requested Hugo version and builds the site into public/. Optionally serves the + result, which the quality gates need. + +inputs: + version: + description: Hugo version to install, without a leading "v" (e.g. "0.154.5"). + required: true + args: + description: Full argument list for `hugo`; setting it drops the default --gc --minify. + required: false + default: --gc --minify + serve-port: + description: Port to serve public/ on. Needs http-server in the job image. Empty means do not serve. + required: false + default: '' + +runs: + using: composite + steps: + - uses: peaceiris/actions-hugo@v3 + with: + hugo-version: ${{ inputs.version }} + extended: true + + - name: Build + shell: bash + env: + ARGS: ${{ inputs.args }} + # ARGS stays unquoted on purpose - it carries a list of arguments, not a single one. + run: hugo ${ARGS} + + # Polling beats a fixed sleep: the gates fail with "no FCP" when they start before the + # server is up, and that failure reads like a site problem. + - name: Serve built site + if: inputs.serve-port != '' + shell: bash + env: + SERVE_PORT: ${{ inputs.serve-port }} + run: | + command -v http-server >/dev/null || { echo "http-server is not installed in the job image." >&2; exit 1; } + # Server output goes to a file, not to the step pipe: a background process keeps stdout + # open and the runner then waits for EOF long after the audit is done (run 2145). + http-server public -p "${SERVE_PORT}" --silent > /tmp/http-server.log 2>&1 & + for _ in $(seq 1 60); do + if curl -sfo /dev/null "http://localhost:${SERVE_PORT}/"; then + exit 0 + fi + sleep 1 + done + echo "http-server did not come up on port ${SERVE_PORT} within 60s." >&2 + exit 1