Skip to content

ci(deps): bump astral-sh/setup-uv from 10.0.1 to 10.1.0 #2512

ci(deps): bump astral-sh/setup-uv from 10.0.1 to 10.1.0

ci(deps): bump astral-sh/setup-uv from 10.0.1 to 10.1.0 #2512

Workflow file for this run

name: Tests
# Zmiany czysto dokumentacyjne (docs/, mkdocs.yml, .readthedocs.yaml)
# NIE uruchamiaja calej suity testow — buduje je wtedy tylko workflow
# `Docs` (ma komplementarny `paths:`). Dzieki temu commit z sama
# dokumentacja deployuje MkDocs na Pages, ale nie odpala ~10-min testow.
# UWAGA: `[skip ci]` w komunikacie commita ubilby ROWNIEZ `Docs` —
# dlatego selekcja jest po sciezkach, nie po komunikacie. Commit
# mieszany (dokumentacja + kod) i tak odpali testy, bo nie wszystkie
# zmienione pliki sa ignorowane.
on:
push:
branches:
- dev
- master
paths-ignore:
- docs/**
- mkdocs.yml
- .readthedocs.yaml
pull_request:
branches:
- dev
paths-ignore:
- docs/**
- mkdocs.yml
- .readthedocs.yaml
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
# Default minimum permissions; per-job overrides below where extra
# scopes are needed (notably `packages:` for the GHCR push/pull flow).
permissions:
contents: read
jobs:
lint:
# Lint only Python files changed in this push/PR by running the
# pre-commit ruff hooks THEMSELVES (see .pre-commit-config.yaml)
# against the changed range. The ruff version + args live in one
# place, so local pre-commit and CI can never disagree. The repo's
# convention is changed-files-only; pre-commit's --from-ref/--to-ref
# reproduces that scope and historic drift is left alone.
name: Lint changed files
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
# No apt build deps / project sync here: pre-commit builds an
# isolated ruff env from a prebuilt wheel (no native compilation),
# so the heavy toolchain the test jobs need is unnecessary for
# linting. uv is only a fast launcher for pre-commit (via uvx).
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
# Patrz komentarz przy jobie `lock-check` — bez `version` setup-uv
# bierze "latest", co czyni CI niedeterministycznym.
version: "0.11.29"
enable-cache: true
- name: Determine diff base
id: base
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
BEFORE_SHA: ${{ github.event.before }}
run: |
if [ "$EVENT_NAME" = "pull_request" ]; then
git fetch --no-tags --depth=100 origin "$BASE_REF"
echo "ref=origin/$BASE_REF" >> "$GITHUB_OUTPUT"
elif [ -n "$BEFORE_SHA" ] && \
[ "$BEFORE_SHA" != "0000000000000000000000000000000000000000" ]; then
echo "ref=$BEFORE_SHA" >> "$GITHUB_OUTPUT"
else
# First push to a new branch: diff against dev
git fetch --no-tags --depth=100 origin dev
echo "ref=origin/dev" >> "$GITHUB_OUTPUT"
fi
- name: Assert ruff pins agree (pyproject vs .pre-commit-config)
# Krok POPRZEDZA lint, bo ustala CZYM lintujemy. Ruff ma dwa piny:
# `ruff==X` w extrze [dev] (pyproject.toml) i `rev: vX` przy
# astral-sh/ruff-pre-commit (.pre-commit-config.yaml). Krok niżej
# używa tego drugiego, `uv run ruff` u dewelopera — pierwszego.
# To dwa różne binaria, więc rozjazd pinów przywraca dokładnie ten
# tryb awarii, który krok niżej deklaruje jako niemożliwy ("one
# ruff, one config"): patrz komentarz przy rev w pre-commit config
# o układzie I001 przepuszczonym lokalnie i odrzuconym przez CI.
#
# Osobny krok, a nie hook w tamtym wywołaniu, bo krok niżej odpala
# hooki PO NAZWIE (`pre-commit run ruff`), więc lokalny hook
# `ruff-piny-zsynchronizowane` nigdy by się w CI nie uruchomił.
# Skrypt jest stdlib-only → goły `python3`, bez `uv sync`.
run: python3 bin/check-ruff-pin-sync.py
- name: Ruff + ruff-format (via pre-commit — single source of truth)
# Runs the SAME `ruff` and `ruff-format` hooks declared in
# .pre-commit-config.yaml (pinned rev) over the changed range, so
# CI and a local `pre-commit run` execute the byte-identical ruff
# binary + args (--fix --exit-non-zero-on-fix). "passes locally,
# fails CI" becomes impossible — there is one ruff, one config.
# Bumping the ruff `rev` in .pre-commit-config.yaml updates CI
# automatically; no edit here is ever needed for a ruff upgrade.
#
# pre-commit derives the file scope ITSELF from --from-ref/
# --to-ref; we deliberately do NOT hand it a file list. A
# hand-rolled `git diff | tr '\n' ' '` list carries a trailing
# space, and `pre-commit run --files "<path> "` matches no real
# file → "(no files to check) Skipped" → exit 0 → the gate
# silently passes broken code. --from-ref/--to-ref sidesteps that
# entirely. --show-diff-on-failure prints the exact reformat diff
# so the fix is obvious straight from the CI log.
#
# `uvx` runs pre-commit as an isolated tool, so no `uv sync` / apt
# build toolchain is needed merely to lint (pre-commit builds ruff
# from a prebuilt wheel — no native code). pre-commit is left
# UNPINNED on purpose: it is just the runner, and only the ruff
# `rev` in .pre-commit-config.yaml affects results — so there is
# no third version to babysit alongside the ruff rev + pin.
env:
DIFF_BASE: ${{ steps.base.outputs.ref }}
run: |
uvx pre-commit run ruff \
--from-ref "$DIFF_BASE" --to-ref HEAD --show-diff-on-failure
uvx pre-commit run ruff-format \
--from-ref "$DIFF_BASE" --to-ref HEAD --show-diff-on-failure
lockfile:
# Assert uv.lock is resolved against the CURRENT pyproject.toml. The
# test image installs deps with `uv sync --frozen`, which applies
# uv.lock AS-IS without checking it against pyproject.toml — so a dep
# edited without re-running `uv lock` would leave dev CI green while
# testing stale deps, and the release pipeline's own `uv lock` step
# would then silently re-resolve into deps dev never tested. This
# fast standalone gate makes lock consistency a tested invariant on
# dev, turning uv.lock into a trustworthy artifact. `uv lock --check`
# is read-only: it never writes the lockfile, it only fails on drift.
# Mirrors the `lint` job: same pinned uv setup, no project sync.
name: uv.lock in sync
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
# PIN OBOWIAZKOWY. Bez `version` setup-uv loguje "Could not
# determine uv version from uv.toml or pyproject.toml. Falling back
# to latest" i bierze najnowsze uv z releases.astral.sh. `uv lock
# --check` zaczyna wtedy zalezec od DATY przebiegu, nie od tresci
# repo: ten sam, bit-w-bit identyczny uv.lock przechodzil na uv
# 0.11.29 i padal na 0.11.31 ("lockfile needs to be updated"),
# wywracajac gate na branchach, ktore deps w ogole nie ruszaly.
# Wersje podnosimy SWIADOMIE, razem z przegenerowanym uv.lock.
version: "0.11.29"
enable-cache: true
- name: Assert uv.lock matches pyproject.toml
run: uv lock --check
baseline-check:
# Guard repo-state-only: czy baseline-sql/baseline.sql nadaza za migracjami
# (manage.py baseline_check). Wydzielone z prepare-image i puszczone
# ROWNOLEGLE — wczesniej te ~37s (apt + uv sync + check) blokowaly start
# builda obrazu, a wiec i wszystkich shardow, na KAZDYM runie. Drift jest
# rzadki: sporadyczny "zmarnowany" build przy realnym drifcie jest tanszy
# niz 37s na kazdym runie. Drift = ten job czerwony = caly run czerwony.
name: Check baseline freshness
if: |
!(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag'))
&& !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v'))
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install system build deps (for uv sync)
run: |
sudo apt-get update
xargs -a docker/bpp_base/build.txt sudo apt-get install -y
- name: Install uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
# Patrz komentarz przy jobie `lock-check`. Tu dochodzi drugi powod:
# `uv sync --frozen` czyta uv.lock, wiec plywajaca wersja uv czyni
# srodowisko testowe niereprodukowalnym miedzy przebiegami.
version: "0.11.29"
enable-cache: false
- name: Install project deps
run: uv sync --frozen --no-install-project
- name: Check baseline freshness
env:
DJANGO_BPP_SKIP_DOTENV: "1"
run: uv run python src/manage.py baseline_check --max-delta 50
prepare-image:
# Build the test-runner image once and push it to GHCR with two
# tags: `:sha-<commit>` (consumed by downstream test jobs) and
# `:cache` (registry-mode buildx cache, auto-merged across builds).
# All test jobs `needs:` this step so the image is built once per
# workflow run instead of N times per matrix runner.
name: Build test-runner image
if: |
!(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag'))
&& !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v'))
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
# GHCR push needs `packages: write`. GITHUB_TOKEN inherits this
# scope only when the job declares it explicitly.
packages: write
outputs:
image: ${{ steps.tag.outputs.image }}
env:
PYTHON_VERSION: "3.12"
DEBIAN_VERSION: trixie
NODEJS_VERSION: "20"
# Lowercase repo owner, since GHCR rejects mixed-case namespaces.
# GitHub Actions ${{ github.repository_owner }} preserves case.
IMAGE_REPO: ghcr.io/iplweb/bpp-test-runner
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# baseline_check przeniesiony do osobnego joba `baseline-check`, ktory
# biegnie ROWNOLEGLE z tym buildem (wczesniej jego ~37s apt+uv sync+check
# blokowaly start builda obrazu — a wiec i wszystkich shardow). Baseline
# drift jest rzadki, wiec sporadyczny "zmarnowany" build przy realnym
# drifcie jest tanszy niz 37s na kazdym runie.
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
with:
driver: docker-container
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute image tag
id: tag
env:
SHA: ${{ github.sha }}
run: |
echo "image=${IMAGE_REPO}:sha-${SHA}" >> "$GITHUB_OUTPUT"
- name: Build & push test-runner image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: docker/bpp_base/Dockerfile
target: test-runner
# Zstd zmniejsza transfer obrazu pullowanego rownolegle przez 12
# shardow. `outputs: type=registry` jest odpowiednikiem push=true.
outputs: type=registry,compression=zstd,compression-level=3,force-compression=true
# Two tags: immutable sha-pinned (consumed by tests jobs) and
# the registry-cache helper used to seed `cache-from` next time.
tags: |
${{ steps.tag.outputs.image }}
build-args: |
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
DEBIAN_VERSION=${{ env.DEBIAN_VERSION }}
NODEJS_VERSION=${{ env.NODEJS_VERSION }}
# Zmienia się co commit -> busta warstwy COPY src/ + grunt w
# test-runnerze (patrz ARG GIT_SHA w Dockerfile), żeby cache-from
# nie przywrócił nieaktualnego drzewa źródeł.
GIT_SHA=${{ github.sha }}
# Registry-mode cache. mode=max stores all intermediate layers,
# so subsequent builds where uv.lock/Dockerfile/build.txt are
# unchanged finish in seconds. The first run after rolling out
# this workflow has no cache and pays the full ~3 min build —
# warm cache afterwards.
cache-from: type=registry,ref=${{ env.IMAGE_REPO }}:cache
cache-to: type=registry,ref=${{ env.IMAGE_REPO }}:cache,mode=max
tests:
# Single sharded job covering the full suite (playwright +
# non-playwright + serial) under `pytest -n auto`. Earlier the
# workflow split this into three specialised jobs (parallel /
# serial / playwright); shard-then-mix is faster end-to-end because
# the per-shard fixed cost (image pull, PG init + baseline load,
# Redis startup) is paid once instead of three times.
#
# Splitting is duration-based (pytest-split, `--splitting-algorithm
# least_duration`): each group gets tests whose ESTIMATED total
# runtime is roughly equal, read from the committed `.test_durations`
# file (mounted read-only via docker-compose.test.ci.yml). This
# replaced pytest-shard's hash-based split, which balanced test COUNT
# not TIME and so left whichever shard happened to collect the slow
# Playwright tests ~2x slower than the rest — and the gate waits on
# the slowest shard. If `.test_durations` is missing/stale,
# pytest-split degrades gracefully to an even split (tests absent
# from the file are weighted at the average), so CI never breaks on
# a stale file — it just balances less well until `make tests`
# regenerates it. The matrix stays 0-indexed (shard) to keep the job
# names — and the branch-protection required checks — unchanged;
# pytest-split groups are 1-indexed, so the run step maps group=shard+1.
#
# @pytest.mark.serial tests are deliberately mixed in here. They
# don't actually depend on running outside xdist — the marker is
# used by callers that want to opt out of parallelism, but the
# tests themselves pass under -n auto. If that ever changes, gate
# serial tests with @pytest.mark.xdist_group(name="serial") so
# `--dist=loadgroup` keeps them on a single worker.
#
# DB setup: docker-compose.test.yml mounts baseline.sql into
# /docker-entrypoint-initdb.d/ and sets DJANGO_BPP_TEST_TEMPLATE=bpp,
# so xdist worker DBs are created via `CREATE DATABASE … WITH
# TEMPLATE bpp` (instant in-server clone) instead of replaying
# `psql -f baseline.sql` per worker.
name: Tests (sharded)
needs: prepare-image
if: |
!(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag'))
&& !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v'))
runs-on: ubuntu-latest
timeout-minutes: 35
permissions:
contents: read
packages: read
strategy:
fail-fast: false
max-parallel: 12
matrix:
python-version: ["3.12"]
# 0-indexed; MUSI byc spojne z NUM_SHARDS nizej. Duration-split
# (pytest-split, .test_durations) balansuje dowolne N po czasie, wiec
# bump jest tani: 8 -> 12 tnie czas testow najwolniejszego sharda
# (~220s -> ~150s). Powyzej ~12 zysk topnieje, bo czas sharda spada
# ponizej czasu prepare-image (to staje sie podloga krytycznej sciezki).
shard: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
env:
TEST_RUNNER_IMAGE: ${{ needs.prepare-image.outputs.image }}
COMPOSE_FILES: -f docker-compose.test.yml -f docker-compose.test.ci.yml
NUM_SHARDS: "12"
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare ci-output dir
run: mkdir -p ci-output
- name: Pull test-runner image
# Retry na transient network/registry error (timeout GHCR/Docker Hub
# to najczestszy szum CI — nie regresja kodu). 3 proby, 10s backoff;
# dopiero trwala porazka wywala krok.
run: |
n=0
until docker compose $COMPOSE_FILES pull test-runner; do
n=$((n + 1))
if [ "$n" -ge 3 ]; then
echo "docker pull test-runner nie powiodl sie po $n probach" >&2
exit 1
fi
echo "pull test-runner padl (proba $n), ponawiam za 10s..." >&2
sleep 10
done
- name: Pull service images (db, redis)
# Osobny, retry-owany pull obrazow z Docker Huba (iplweb/bpp_dbserver,
# redis) PRZED `up -d`. Wczesniej `up -d` pobieral je implicite bez
# retry — timeout Docker Huba na iplweb/bpp_dbserver wywalal caly shard
# (poz. 3.3 audytu). Teraz `up -d` widzi obrazy juz lokalnie.
run: |
n=0
until docker compose $COMPOSE_FILES pull db redis; do
n=$((n + 1))
if [ "$n" -ge 3 ]; then
echo "docker pull db/redis nie powiodl sie po $n probach" >&2
exit 1
fi
echo "pull db/redis padl (proba $n), ponawiam za 10s..." >&2
sleep 10
done
- name: Start services
run: docker compose $COMPOSE_FILES up -d db redis
- name: Wait for services
timeout-minutes: 2
run: |
until docker compose $COMPOSE_FILES exec db \
pg_isready -U bpp; do
echo "Waiting for PostgreSQL..."
sleep 2
done
until docker compose $COMPOSE_FILES exec redis \
redis-cli ping; do
echo "Waiting for Redis..."
sleep 2
done
- name: Tests (shard ${{ matrix.shard }}/${{ env.NUM_SHARDS }})
timeout-minutes: 25
env:
SHARD_ID: ${{ matrix.shard }}
run: |
# pytest-split groups are 1-indexed; the matrix shard is
# 0-indexed (kept that way so the job names / required checks
# don't change). Map group = shard + 1.
GROUP=$((SHARD_ID + 1))
docker compose $COMPOSE_FILES run --rm \
test-runner uv run pytest \
-n auto \
--splits "$NUM_SHARDS" --group "$GROUP" \
--splitting-algorithm least_duration \
--durations-path .test_durations \
--timeout 300
- name: Cleanup
if: always()
run: docker compose $COMPOSE_FILES down -v
js-tests:
# Jednostkowe testy czystej logiki JS (vitest): budowanie URL-i
# eksploratora powiazan, geometria ukladow grafu, normalizacja
# rozmiarow, formatowanie/lokalizacja DjangoQL. NIE wymaga obrazu
# testowego ani DB — czysty node, ~1 min. notifications.js zyje i
# jest testowany w pakiecie django-channels-broadcast (jego CI).
name: JS unit tests (vitest)
if: |
!(github.ref == 'refs/heads/dev' && contains(github.event.head_commit.message, 'Merge tag'))
&& !(github.ref == 'refs/heads/master' && startsWith(github.event.head_commit.message, 'Release v'))
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "20"
cache: yarn
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Run vitest suite
run: npx vitest run