Files
actions/pnpm-install/README.md
cierzniak e8cc842238 feat: add pnpm-install as a shared composite action
Lifts ptpa_pl's local .gitea/actions/pnpm-install into this repo, parametrised
enough for repositories we have not met yet: working-directory, install args,
pnpm version and an opt-in store cache.

Cache is off by default. ADR-0014 pins actions/cache to major 3 and no workflow
on this instance has exercised the cache server yet - a shared action that a
dozen jobs hang off is the wrong place to premiere that dependency.

Two traps found while testing against ptpa_pl's lockfile in node:24-bookworm-slim,
both avoided by pinning the store instead of reading `pnpm store path`:
that command defaults to <workspace>/.pnpm-store, inside the working tree where a
committing job could sweep it up, and it exits 0 while printing its error to
stdout, which would have fed actions/cache a garbage path silently.

The version input also carries COREPACK_ENABLE_PROJECT_SPEC=0, without which a
packageManager field outranks `corepack prepare --activate` and the input would
do nothing at all - no effect, no error.
2026-08-10 23:32:51 +02:00

99 lines
5.2 KiB
Markdown

# pnpm-install
Aktywuje pnpm przez corepack i instaluje zależności z lockfile'a.
```yaml
- uses: actions/checkout@v7
- uses: https://git.cierzniak.it/gitea-runner/actions/pnpm-install@v1
- run: pnpm run lint:js
```
Podprojekt w monorepo, z cache'em store'a:
```yaml
- uses: https://git.cierzniak.it/gitea-runner/actions/pnpm-install@v1
with:
working-directory: ./wizard
cache: 'true'
```
`args` zastępuje **całą** listę flag, więc `--frozen-lockfile` trzeba powtórzyć:
```yaml
- uses: https://git.cierzniak.it/gitea-runner/actions/pnpm-install@v1
with:
args: --frozen-lockfile --ignore-scripts
```
## Wejście
| Input | Wymagany | Domyślnie | Co to |
| --- | --- | --- | --- |
| `working-directory` | nie | `.` | Katalog z `package.json` i `pnpm-lock.yaml` |
| `version` | nie | — | Wersja pnpm. Pusta bierze ją z pola `packageManager`; podana **nadpisuje to pole** |
| `args` | nie | `--frozen-lockfile` | Pełna lista argumentów `pnpm install` |
| `cache` | nie | `false` | `'true'` cache'uje store pnpm między jobami |
Outputów nie ma.
Node i corepack pochodzą z obrazu joba — akcja nie stawia żadnego z nich. Sprawdzone na
`git.cierzniak.it/gitea-runner/node:24` (Node 24.18.1, corepack 0.35.0).
## Dlaczego tak
**`args` to jeden input, nie `frozen-lockfile` plus `extra-args`.** Kto go podaje, przejmuje
całą listę flag. Dwa inputy wymagałyby reguły, co się dzieje przy `frozen-lockfile: true`
i `args: --no-frozen-lockfile` — tu takiego stanu nie ma.
**Wersja pnpm domyślnie z `packageManager`.** To pole jest już źródłem prawdy dla corepacka
lokalnie, a wpisanie jej drugi raz do workflow tworzy dwa źródła, które rozjeżdżają się po
pierwszym bumpie. Input `version` jest przede wszystkim dla repozytoriów bez tego pola — bez
niego samo `corepack enable` nie ma czego aktywować.
**`version` dokłada `COREPACK_ENABLE_PROJECT_SPEC=0`, bo inaczej nic by nie robił.** Samo
`corepack prepare pnpm@X --activate` ustawia tylko wersję *domyślną*, do której corepack sięga
przy braku `packageManager`; obecne pole ją przebija. Sprawdzone: w projekcie z
`packageManager: pnpm@9.15.9` po `prepare pnpm@10.20.0 --activate` dalej odpowiada `9.15.9`
(`COREPACK_ENABLE_STRICT=0` też nie pomaga — dopiero `COREPACK_ENABLE_PROJECT_SPEC=0` daje
`10.20.0`). Bez tego input byłby cichym no-opem: ustawiasz, nic się nie zmienia, zero błędu.
Cena jest taka, że przy podanym `version` pole `packageManager` przestaje obowiązywać —
dlatego zostawiaj go pustym wszędzie, gdzie to pole istnieje.
**Cache jest opt-in i domyślnie wyłączony.** [ADR-0014](https://git.cierzniak.it/gitea-runner/images/src/branch/main/docs/adr/0014-akcje-cache-i-artefaktow-na-v3.md)
trzyma `actions/cache` na majorze 3, bo od v4.2 akcja mówi protokołem cache service v2,
którego Gitea nie implementuje ([go-gitea#33393](https://github.com/go-gitea/gitea/issues/33393)).
Akcja wymaga też skonfigurowanego serwera cache'a — kontener joba i kontener runnera siedzą
w różnych przestrzeniach sieciowych, więc bez tego zwraca `ETIMEDOUT`. Runnery tej instancji
mają `cache.enabled: true` i `external_server`, ale **żaden workflow jeszcze z tego nie
korzystał**. Domyślnie wyłączony cache pozwala to sprawdzić na jednym repo, zamiast robić
premierę na wszystkich naraz.
**Klucz cache'a liczy `sha256sum`, nie `hashFiles()`.** `hashFiles` to funkcja natywna
GitHuba i jej zachowanie w `act_runner` byłoby kolejną niewiadomą dokładaną do i tak
niesprawdzonego tutaj `actions/cache`. Do tego sklejanie ścieżki z `working-directory` daje
wzorce w rodzaju `.//pnpm-lock.yaml`. `sha256sum` jest w każdym obrazie z
`gitea-runner/images` i widać w logu, co policzył. `runner.os` w kluczu idzie za wzorcem
oficjalnej `pnpm/action-setup`.
**Store jest przypięty do `~/.pnpm-store`, zamiast odczytany z `pnpm store path`.** Dwa
powody, oba sprawdzone w obrazie `node:24-bookworm-slim`:
1. Domyślnie store ląduje w **`<workspace>/.pnpm-store/v3`**, czyli w drzewie roboczym —
pnpm celowo szuka miejsca na tym samym systemie plików co projekt, a `/workspace` jest
bind-mountem. Job, który commituje (jak `check-pchia` w `ptpa_pl`), ma tam minę.
2. `pnpm store path` przy błędzie **kończy się kodem 0 i wypisuje błąd na stdout**
(`EROFS: read-only file system…`). `path=` w `GITHUB_OUTPUT` dostałoby wtedy śmieci,
a `actions/cache` cache'owałby nieistniejącą ścieżkę — po cichu, bez czerwonego joba.
Przestawia go wyłącznie `npm_config_store_dir`; **`PNPM_STORE_DIR` nie jest ustawieniem pnpm**
i nie działa. Zmienna leci przez `$GITHUB_ENV`, żeby dosięgła też kroku instalacji, i tylko
przy włączonym cache — przy `cache: false` rozmieszczenie store'a zostaje takie, jakie pnpm
wybiera dziś, więc obecni konsumenci nic nie odczuwają.
Cache obejmuje `~/.pnpm-store`, a nie podkatalog z wersją, bo ten zależy od majora pnpm —
sprawdzone: `v3` przy pnpm 9, `v10` przy pnpm 10.
**Akcja obsługuje wyłącznie pnpm.** Wariant wykrywający lockfile i wołający npm albo yarn
rozmyłby kontrakt na trzy ścieżki o różnych flagach, różnym modelu cache'a i różnych trybach
błędu — z czego dwie nikt by nie testował. Gdy npm będzie potrzebny, dostanie osobną akcję.