diff --git a/README.md b/README.md index 6100795..ccc8627 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ przy pobieraniu akcji, patrz „Dostęp" niżej. | Akcja | Do czego | | --- | --- | +| [`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 | ## Jak używać @@ -47,6 +48,9 @@ Ustawienia nie ma w REST API — trzeba wyklikać. Aktualnie nadane: `cierzniak-it`. +Do nadania przy najbliższej migracji: **`ptpa`** (`website`) — pierwszy konsument +`pnpm-install`, dziś bez dostępu. + Kandydaci do dodania, gdy akcja pojedzie szerzej (repo z buildxem w CI): `leczenieran` (`wizytowka`, `elektroniczna-dokumentacja-medyczna`), `welding-technology-review` (`witryna-internetowa`), `ekoinbud` (`production-system`). diff --git a/pnpm-install/README.md b/pnpm-install/README.md new file mode 100644 index 0000000..2ab19c8 --- /dev/null +++ b/pnpm-install/README.md @@ -0,0 +1,98 @@ +# 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 **`/.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ę. diff --git a/pnpm-install/action.yaml b/pnpm-install/action.yaml new file mode 100644 index 0000000..b3773ae --- /dev/null +++ b/pnpm-install/action.yaml @@ -0,0 +1,78 @@ +name: Install pnpm dependencies +description: >- + Activates pnpm through corepack and installs dependencies from the lockfile. The version comes + from the packageManager field unless overridden; the store cache is opt-in. + +inputs: + working-directory: + description: Directory holding package.json and pnpm-lock.yaml. + required: false + default: '.' + version: + description: >- + pnpm version. Empty takes it from the packageManager field; a value overrides that field + and stops it from being enforced. + required: false + default: '' + args: + description: Full argument list for `pnpm install`; setting it drops the default --frozen-lockfile. + required: false + default: --frozen-lockfile + cache: + description: Set to "true" to cache the pnpm store between jobs. + required: false + default: 'false' + +runs: + using: composite + steps: + - name: Activate pnpm + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' + VERSION: ${{ inputs.version }} + run: | + set -euo pipefail + corepack enable + if [ -n "${VERSION}" ]; then + corepack prepare "pnpm@${VERSION}" --activate + # `prepare --activate` only sets the default corepack falls back to; a packageManager + # field outranks it, so without this the input would silently do nothing on every repo + # that has one. Written to GITHUB_ENV so the install step sees it too. + echo "COREPACK_ENABLE_PROJECT_SPEC=0" >> "$GITHUB_ENV" + fi + + # The store is pinned instead of read back from `pnpm store path`, because that command + # defaults to /.pnpm-store - inside the working tree, where a job that commits + # could sweep it up - and because it exits 0 while printing its error to stdout, which would + # silently feed garbage to actions/cache. Only npm_config_store_dir moves it; PNPM_STORE_DIR + # is not a pnpm setting. Set here rather than as a step env so it also reaches the install + # step, and only when caching is on, so the default path keeps pnpm's own store placement. + - name: Pin store and compute cache key + id: store + if: inputs.cache == 'true' + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + set -euo pipefail + echo "npm_config_store_dir=${HOME}/.pnpm-store" >> "$GITHUB_ENV" + echo "key=$(sha256sum pnpm-lock.yaml | cut -d ' ' -f 1)" >> "$GITHUB_OUTPUT" + + # actions/cache stays on major 3: from v4.2 it speaks a cache service protocol Gitea does + # not implement. Read ADR-0014 in gitea-runner/images before letting Renovate bump this. + - name: Cache pnpm store + if: inputs.cache == 'true' + uses: actions/cache@v3 + with: + path: ~/.pnpm-store + key: pnpm-store-${{ runner.os }}-${{ steps.store.outputs.key }} + restore-keys: pnpm-store-${{ runner.os }}- + + - name: Install dependencies + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + ARGS: ${{ inputs.args }} + # ARGS stays unquoted on purpose - it carries a list of flags, not a single argument. + run: pnpm install ${ARGS}