Laboratorium 02 / Continuous Integration

Od pierwszego pusha do wiarygodnego CI

Kod aplikacji i testy już działają. Zbuduj proces, który sprawdza zmianę, pokazuje przyczynę błędu i nie pozwala scalić niesprawnego kodu. Potem wykorzystaj te same mechanizmy do wydania pracy LaTeX jako PDF.

Cel i sposób pracy

Pracujesz na .NET 10 + Angular 21 + MySQL 8.4. Testy HTTP API uruchamiają MySQL przez Testcontainers, a testy interfejsu używają Vitest i symulowanego HTTP. Nie piszesz testów od zera. Nie potrzebujesz Dockerfile aplikacji, Compose, własnej bazy ani prawdziwych sekretów do jej sprawdzenia.

Zadania 1–13 to rdzeń: własny workflow, build, testy, raporty, ochrona repozytorium i cache. Zadania 14–23 to moduły rozszerzające: przepływ danych, reużywanie, warunkowe sprawdzanie, publikacja PDF i — jako ostatnie zadania — macierze w osobnym, ręcznym workflow. To bank ćwiczeń na kolejne bloki pracy, nie obietnica wykonania całości w 90 minut.

  • Wykonuj zadania po kolei. Każde rozwija Twoją konfigurację, zamiast zastępować ją kompletnym rozwiązaniem prowadzącego.
  • Fragmenty oznaczone jako szkielet wymagają uzupełnienia; nie są całym gotowym workflow.
  • Od zadania 3 używaj krótkich gałęzi, PR-ów i małych commitów. Kontrolowane błędy wprowadzaj na gałęzi, nigdy wprost do main.
  • Zapisuj link do własnego runa, SHA commita i krótką obserwację. Zielony wynik bez sprawdzenia testów nie jest dowodem ukończenia.

Wykład 02 wyjaśnia pojęcia. Ta strona jest oddzielną instrukcją wykonania laboratorium. Materiały Docker z laboratorium 01 dotyczą starszego startera z tagu Z1.

Przygotowanie w domu — zanim uruchomisz CI

Konto i uprawnienia na GitHubie

  • Masz konto GitHub, potwierdzony adres e-mail i działające logowanie. Nie wpisujesz tokenu do adresu repozytorium ani pliku workflow.
  • Potrafisz utworzyć publiczne repozytorium na własnym koncie albo w organizacji zajęć. Publiczne oznacza także publiczne logi i artefakty — używaj wyłącznie danych ćwiczeniowych.
  • Masz prawa właściciela/admina do ustawień tego repozytorium: Actions, rulesets/ochrony gałęzi, Variables i Secrets. Jeśli repozytorium kontroluje Classroom lub organizacja, prowadzący musi zapewnić odpowiednie uprawnienia przed tymi zadaniami.
  • W Settings → Actions → General Actions są włączone, a polityka repozytorium pozwala użyć wymaganych akcji. Nie włączaj globalnie uprawnień zapisu „na zapas”.
Budżet: standardowe runnery GitHub-hosted w publicznych repozytoriach mają bezpłatny czas obliczeniowy Actions — nie jest to stały przydział „darmowych minut”. Większe runnery oraz przechowywanie artefaktów/cache mają osobne zasady i limity. W repozytoriach prywatnych miesięczny przydział zależy od planu. Używaj standardowego ubuntu-24.04, krótkiej retencji i sprawdź ustawienia rozliczeń właściciela repozytorium.

Narzędzia lokalne

  • Git i edytor. VS Code z rozszerzeniem GitHub Actions lub IDE JetBrains pomogą w YAML, podpowiedziach do akcji i expressions; nie zastępują prawdziwego runa.
  • .NET SDK z global.json startera; uruchamiaj CLI z katalogu tego projektu. Sam runtime .NET nie wystarczy.
  • Node.js z .nvmrc startera i npm. Jeżeli masz nvm, użyj nvm install i nvm use w katalogu startera. Nie instaluj globalnego Angular CLI zamiast zależności projektu.
  • Docker Engine z kontenerami Linux. W Rancher Desktop wybierz dockerd (moby), nie containerd; Kubernetes nie jest potrzebny. W Windows przygotuj WSL 2 zgodnie z instrukcją środowiska.
  • Opcjonalnie GitHub CLI (gh): logowanie, PR-y i pobieranie raportów można też obsłużyć w przeglądarce. W zadaniu o wydaniach wykorzystasz CLI na runnerze.

Potrzebujesz Internetu do pobrania SDK, paczek i obrazów. TeX nie musi być zainstalowany na laptopie: zadanie 21 może kompilować dokument wyłącznie na runnerze.

git --version
dotnet --version
node --version
npm --version
docker version
docker info
docker run --rm hello-world

Sprawdź przed zajęciami: wersje SDK i Node pasują do plików startera, docker version pokazuje klienta i serwer, a kontener kończy się komunikatem powodzenia. Po pobraniu startera wykonaj także lokalne polecenia z zadania 6; wygenerowane lockfiles są Twoją pracą, nie plikami do pobrania od prowadzącego.

Pomoc przy edycji workflow i diagnostyce lokalnej
  • Workflow uruchamia się na systemie wskazanym w runs-on, nie na Twoim laptopie. Wszystkie jobs tego laboratorium korzystają ze standardowego ubuntu-24.04, na którym sprawdzono testy i pakiety TeX. Polecenia workflow to Bash; lokalny PowerShell ma inną składnię zmiennych.
  • YAML używa spacji, nie tabulatorów. Sprawdzaj poziom kluczy: jobs, job, steps, krok. Sam poprawny YAML nie gwarantuje poprawnego schematu Actions.
  • Opcjonalny actionlint sprawdza również schemat i expressions GitHub Actions. Zainstaluj go według dokumentacji, zanim użyjesz polecenia actionlint.
  • gh auth status sprawdza logowanie CLI; gh run list i gh run view <ID_RUNA> --log-failed pomagają znaleźć nieudany krok. Nie jest to wymagane do pierwszych zadań.
  • Gdy Docker nie odpowiada, sprawdź uruchomiony silnik i docker context show. Nie wyłączaj testów Testcontainers, aby uzyskać zielony wynik.

Repozytorium i projekt startowy

Przeczytaj README startera Mission Control. Własne repozytorium ma zawierać zawartość startera w korzeniu, a nie całą stronę kursu z katalogiem projects/.

Gotowy starter do pobrania:

Pobierz starter CI (.zip)

Archiwum zawiera czyste pliki aplikacji, bez historii Git, lockfiles i gotowych workflow. Rozpakuj je i zaimportuj do własnego publicznego repozytorium według instrukcji poniżej.

Alternatywnie prowadzący może przekazać link do publicznego template lub zadania Classroom. Nie potrzebujesz dostępu do prywatnego repozytorium prowadzącego ani jego publicznego forka.

  1. Dla pobranego archiwum utwórz nowe, puste publiczne repozytorium, bez automatycznego README. Rozpakuj pliki aplikacji do nowego lokalnego katalogu. Nie kopiuj katalogu .git ani historii prowadzącego, node_modules, bin, obj, raportów czy konfiguracji strony kursu. Zainicjuj własną historię według ściągi poniżej.
  2. Jeżeli prowadzący wskazał zamiast archiwum publiczny template, wybierz Use this template → Create a new repository → Public, bez kopiowania wszystkich gałęzi. W Classroom zaakceptuj wskazane zadanie i sprawdź widoczność oraz uprawnienia utworzonego repozytorium.
  3. Dla template/Classroom sklonuj swoje utworzone repozytorium przez adres z Code → HTTPS lub SSH. Dla archiwum pracuj w katalogu zaimportowanym do nowego repozytorium — nie musisz klonować go ponownie. Po utworzeniu pierwszego commita sprawdź, że main jest gałęzią domyślną. Sam push plików startowych nie uruchomi CI, dopóki nie dodasz workflow.
Import pobranego archiwum bez historii prowadzącego

Wykonaj te polecenia wyłącznie w nowym katalogu z czystym eksportem, którego korzeń zawiera MissionControl.slnx i global.json. Nie wskazuj repozytorium kursu ani prywatnej gałęzi prowadzącego jako origin.

git init -b main
git add .
git commit -m "Start laboratorium CI"
git remote add origin <ADRES_TWOJEGO_NOWEGO_PUBLICZNEGO_REPOZYTORIUM>
git push -u origin main

W GitHubie powinny być widoczne pliki aplikacji w korzeniu i Twój pierwszy commit, bez commitów rozwiązania prowadzącego. Nie zmieniaj widoczności prywatnej kopii ani nie próbuj tworzyć publicznego forka prywatnego repozytorium.

Alternatywa: pobranie własnego repozytorium z template lub Classroom
git clone <ADRES_TWOJEGO_PUBLICZNEGO_REPOZYTORIUM>
cd <NAZWA_TWOJEGO_REPOZYTORIUM>
git status --short | cat
MissionControl.slnx
global.json
NuGet.Config
.nvmrc
backend/          API .NET
backend.tests/    gotowe testy HTTP API z MySQL Testcontainers
frontend/         Angular i gotowe testy Vitest
docs/             kontrakt API

Sprawdź: nie ma jeszcze .github/workflows/ci.yml ani trzech lockfiles aplikacji. Korzeń projektu rozpoznajesz po MissionControl.slnx, global.json i .nvmrc. Starsze materiały z MissionControl.sln nie są punktem startowym tego laboratorium.

Rdzeń

Zadanie 1 — pierwszy push, jeden job, dwa kroki

Narzędzia: Git, edytor, przeglądarka, własne repozytorium z włączonym Actions. SDK aplikacji nie jest jeszcze potrzebne.

Zrób

  1. W korzeniu własnego repozytorium utwórz .github/workflows/ci.yml. Nadaj workflow nazwę Mission Control CI.
  2. Zacznij od push, jednego joba hello na ubuntu-24.04 i dwóch osobnych kroków run. Pierwszy ma wypisać powitanie, drugi katalog roboczy i nazwę systemu. Nazwij oba kroki.
  3. Zapisz plik, commit i pierwszy push konfiguracji. Nie dodawaj jeszcze checkout, cache ani testów.
Szkielet pierwszego pliku

Uzupełnij listę steps przed wysłaniem — sam komentarz nie tworzy żadnego kroku.

name: Mission Control CI
on: push

jobs:
  hello:
    runs-on: ubuntu-24.04
    steps:
      # Dodaj dwa nazwane kroki run.
git add .github/workflows/ci.yml
git commit -m "ci: uruchom pierwszy workflow"
git push

Czego się uczysz

GitHub wykrywa workflow z repozytorium. Zdarzenie tworzy run, job otrzymuje runner, a kroki wykonują się kolejno. Nazwy pomagają odczytać logi; identyfikator joba jest kluczem konfiguracji.

Sprawdź w GitHub: Actions → Mission Control CI → run → hello. Wskaż commit, zdarzenie push, oba własne kroki i ich logi. Oczekiwany rezultat: zielony run i dwa różne komunikaty. Automatyczne Set up job nie jest jednym z Twoich dwóch kroków.
Rdzeń

Zadanie 2 — pobierz kod i przygotuj SDK

Zrób

  1. Dodaj checkout kodu przed krokiem, który odczytuje pliki projektu.
  2. Dodaj actions/setup-dotnet z global-json-file: global.json i actions/setup-node z node-version-file: .nvmrc. Na tym etapie nie włączaj cache zależności.
  3. W osobnym kroku sprawdź obecność rozwiązania oraz wersje narzędzi. Nie wpisuj innej wersji SDK bezpośrednio w YAML.
test -f MissionControl.slnx
test -f global.json
test -f .nvmrc
dotnet --version
node --version
npm --version
Jak wybrać i zapisać referencję do action
  • Przeczytaj wejścia w dokumentacji: checkout, setup-dotnet, setup-node.
  • Wybierz wspierane wydanie i sprawdź jego pochodzenie. Referencja ma postać owner/repo@ref; w utrwalonej konfiguracji przypnij zweryfikowany pełny SHA i dopisz czytelny komentarz z wersją.
  • W małych przykładach tej instrukcji pojawiają się czytelne tagi akcji. W swoim workflow stosuj konsekwentnie wybrane referencje; tag action nie określa wersji .NET ani Node.
  • Krok ma albo uses i with, albo run. Nie umieszczaj polecenia CLI jako wartości uses.
  • Jeżeli używana wersja setup-node automatycznie włącza cache na podstawie manifestu, wyłącz tę funkcję do zadania 13 zgodnie z README akcji.

Czego się uczysz

Runner nie otrzymuje automatycznie plików aplikacji. uses uruchamia akcję, a with przekazuje jej parametry. Wersje narzędzi należą do projektu, nie do przypadkowego obrazu maszyny.

Sprawdź w GitHub: widać checkout i przygotowanie obu SDK, a kontrola plików przechodzi. Porównaj wersje z plikami startera. Jeśli krok znajduje pliki tylko lokalnie, ustal, czy zostały wysłane do repozytorium.
Rdzeń

Zadanie 3 — feature branch i pull request

Zrób

  1. Utwórz gałąź feature/pr-ci. Zmień triggery: push tylko do main, pull request z bazą main. Na razie nie dodawaj filtrów paths.
  2. Wyślij gałąź i otwórz PR. Dopisz małą zmianę w README, aby zobaczyć aktualizację checks po kolejnym commicie.
  3. Po zielonym CI przejrzyj diff i scal PR. W parze poproś drugą osobę o review; nie zatwierdzaj własnego PR-a jako wymaganej recenzji.
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
git switch main
git pull --ff-only
git switch -c feature/pr-ci
# Po edycji: git add, git commit, a następnie:
git push -u origin feature/pr-ci

Czego się uczysz

PR sprawdza proponowaną integrację, a push na main sprawdza stan po scaleniu. Domyślny checkout dla pull_request korzysta z przygotowanego przez GitHub merge ref. branches przy PR oznacza gałąź docelową, nie nazwę feature/*. pull --ff-only odmawia automatycznego merge przy rozbieżnej historii; nie zastępuje zapisywania lokalnej pracy.

Sprawdź w GitHub: PR pokazuje check dla najnowszego commita. Po merge jest osobny run typu push na main. Kolejny push do otwartego PR-a nie tworzy dodatkowo runa push na feature, ponieważ ten trigger jest ograniczony do main.
Rdzeń

Zadanie 4 — ręczny start i typowane wejścia

Zrób

  1. Na nowej gałęzi dodaj workflow_dispatch z wejściem configuration typu choice: Release / Debug, domyślnie Release. Dodaj extra_diagnostics typu boolean, domyślnie false.
  2. Dodaj run-name zawierające zdarzenie i wybraną konfigurację. Przekaż konfigurację do kroku przez env. Dla push i PR wybieraj Release, bo te zdarzenia nie dostarczają formularza inputs.
  3. Dodaj krok z dotnet --info i docker version, wykonywany tylko przy ręcznym starcie z włączoną diagnostyką. Nie wypisuj całego środowiska ani kontekstów.
  4. Najpierw bootstrap: wyślij gałąź, otwórz PR, sprawdź zwykły run PR i scal definicję do main. Dopiero wtedy otwórz Actions → Mission Control CI → Run workflow.
  5. Uruchom raz Release bez diagnostyki i raz Debug z diagnostyką. Po obecności definicji na main możesz w formularzu wybrać także inną gałąź zawierającą ten workflow.
Mały fragment: wejście jako wartość i warunek

Deklaracje inputs i pozostałe kroki dopisz samodzielnie. choice daje tekst, a boolean w kontekście inputs jest wartością logiczną.

env:
  BUILD_CONFIGURATION: ${{ inputs.configuration || 'Release' }}

# W kroku diagnostycznym:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.extra_diagnostics }}

Czego się uczysz

Formularz, nazwa runa, warunek kroku i środowisko powłoki mają różne role. Wyrażenie GitHub wybiera wartość, a Bash odczytuje ją np. przez "$BUILD_CONFIGURATION".

Sprawdź w GitHub: dwa ręczne runy mają właściwe nazwy i konfiguracje; dodatkowy krok raz jest skipped, raz wykonany. Run PR nadal działa z Release. Sam plik na gałęzi feature nie wystarcza do pojawienia się przycisku ręcznego startu.
Rdzeń

Zadanie 5 — kontrolowany błąd i adnotacje

Zrób

  1. Na gałęzi PR dodaj krok z ostrzeżeniem ::warning i błędem ::error, wskazując istniejący plik i linię. Zakończ krok poleceniem exit 1.
  2. Wyślij commit i znajdź pierwszy czerwony krok, jego adnotację oraz kod zakończenia. Sprawdź, co stało się z późniejszymi krokami.
  3. Nowym commitem usuń kontrolowany błąd. Nie dodawaj continue-on-error ani || true.
printf '::error file=README.md,line=1::Kontrolowany blad konfiguracji CI\n'
exit 1

Czego się uczysz

Workflow commands tworzą czytelne adnotacje. O niepowodzeniu decyduje także kod zakończenia procesu — samo wypisanie komunikatu nie zastępuje poprawnej propagacji błędu.

Sprawdź w GitHub: zachowaj linki do czerwonego i zielonego runa tego PR-a. Pokaż adnotację przy podsumowaniu/checku oraz miejsce błędu w logu. Oba wyniki muszą dotyczyć Twojej rzeczywistej konfiguracji, nie ręcznie zmienionego statusu.
Rdzeń

Zadanie 6 — wygeneruj trzy lockfiles i uruchom gotowe testy

Narzędzia: od tego zadania lokalnie wymagane są SDK z global.json, Node/npm z .nvmrc oraz działający Docker Linux Engine.

Zrób

  1. Porównaj PackageReference w projektach .NET i zależności w frontend/package.json. Starter zawiera deklaracje, ale celowo nie dostarcza rozwiązanych lockfiles.
  2. Wygeneruj je lokalnie poniższymi poleceniami. Obejrzyj zależności bezpośrednie i przechodnie; nie przepisuj JSON ręcznie.
  3. Sprawdź locked restore, build i testy obu komponentów. Testcontainers sam tworzy odizolowany MySQL 8.4 i zwalnia go po testach; nie uruchamiaj dodatkowego serwisu bazy.
  4. Dodaj do Gita dokładnie trzy lockfiles i commit. Nie dodawaj zależności, wyników budowania ani raportów.
dotnet restore MissionControl.slnx --use-lock-file
cd frontend
npm install
cd ..
git status --short | cat
dotnet restore --locked-mode
dotnet build --configuration Release
dotnet test --configuration Release
cd frontend
npm ci
npm run build
npm test
cd ..
git add backend/packages.lock.json backend.tests/packages.lock.json frontend/package-lock.json
git commit -m "build: zapisz lockfiles aplikacji"

Czego się uczysz

Manifest określa wymagania, lockfile utrwala rozwiązany zestaw zależności. Pierwsze generowanie należy do autora zmiany, odtwarzanie w CI ma wykrywać brak lub niezgodność lockfile. Testy i ich konfiguracja są częścią startera.

Sprawdź lokalnie i w GitHub: build/test zwracają sukces, w commicie są backend/packages.lock.json, backend.tests/packages.lock.json i frontend/package-lock.json. Raporty nie trafiają do Gita. Niepowodzenie połączenia z Dockerem jest błędem środowiska, nie zgodą na pominięcie testów.
Rdzeń

Zadanie 7 — prawdziwe CI backendu

Zrób

  1. Zastąp demonstracyjny job jobem backend z czytelnym name, np. Backend. Zachowaj triggery i ręczne inputs.
  2. Na standardowym ubuntu-24.04 wykonaj checkout, konfigurację .NET i kontrolę docker version. Przed restore sprawdź oba lockfiles .NET.
  3. Dodaj osobne kroki: dotnet restore --locked-mode, dotnet build --configuration "$BUILD_CONFIGURATION", dotnet test --configuration "$BUILD_CONFIGURATION". Pracuj w korzeniu z rozwiązaniem .slnx.
  4. Wykorzystaj dostarczoną konfigurację startera: raporty ustawia backend.tests/test.runsettings, a Directory.Build.props przy CI=true włącza locked restore również dla zwykłego build/test. Directory.Build.targets odrzuca brak lockfile .NET. Nie obchodź tych reguł przez usunięcie konfiguracji projektu ani nadpisanie właściwości restore.
test -f backend/packages.lock.json
test -f backend.tests/packages.lock.json
docker version
Dlaczego nie konfigurujemy MySQL w YAML?

Fixture Testcontainers już wybiera obraz MySQL 8.4, port i połączenie dla testów HTTP API. Standardowy runner ubuntu-24.04 udostępnia Docker. Druga baza w services, Compose albo sekret z hasłem do zewnętrznej bazy nie jest tu potrzebna.

Czego się uczysz

Osobne kroki odróżniają błąd zależności, kompilacji i zachowania aplikacji. Taka sama konfiguracja projektu upraszcza polecenia lokalne i CI bez ukrywania nieudanego restore.

Sprawdź w GitHub: log zawiera rzeczywiście wykonane testy API, nie zero testów ani same skipped. Widać uruchomienie MySQL przez Testcontainers. Build i test używają wybranej konfiguracji; przy PR jest to Release.
Rdzeń

Zadanie 8 — frontend jako niezależny, równoległy job

Zrób

  1. Dodaj job frontend, np. z nazwą Frontend i runs-on: ubuntu-24.04. Nie dodawaj needs: backend: testy UI nie potrzebują działającego API.
  2. W nowym jobie ponownie wykonaj checkout i setup-node z .nvmrc. Sprawdź obecność frontend/package-lock.json.
  3. Uruchom w frontend/ osobno npm ci, npm run build i npm test. Ten ostatni skrypt jest już nieinteraktywny i używa skonfigurowanego Vitest.
  4. Użyj defaults.run.working-directory na poziomie joba albo jawnych katalogów przy krokach. Nie zmieniaj katalogu backendu przez globalny default.
# Fragment joba frontend:
defaults:
  run:
    shell: bash
    working-directory: frontend

Czego się uczysz

Niezależne jobs mogą działać równolegle; kolejność wewnątrz każdego joba pozostaje zachowana. defaults.run dotyczy run, nie ścieżek w with akcji setup lub upload.

Sprawdź w GitHub: graf pokazuje Backend i Frontend bez strzałki zależności między nimi. Ich wykonania mogą się pokrywać w czasie; kolejka runnerów może je opóźnić. Testy UI przechodzą bez uruchamiania backendu i bazy w tym jobie.
Rdzeń

Zadanie 9 — regresja aplikacji i nieaktualny lockfile

Zrób

  1. Regresja: na gałęzi PR zmień kod API tak, aby POST przestał odrzucać pustą nazwę misji. Znajdź walidację w aplikacji i odpowiadający jej gotowy test RejectsABlankMissionName. Nie edytuj testu.
  2. Potwierdź błąd lokalnie, wyślij commit i zachowaj czerwony run. Przywróć poprawną walidację nowym commitem i sprawdź zielone testy.
  3. Niezgodne zależności: w oddzielnej zmianie ustaw w frontend/package.json inną, opublikowaną i zgodną wersję jednej zależności. Wybierz wersję inną niż rozwiązana w lockfile, najlepiej dokładną wersję kompatybilnej poprawki. Nie aktualizuj jeszcze lockfile.
  4. Wyślij manifest bez zmienionego lockfile. Po udokumentowaniu błędu npm ci wykonaj lokalne npm install, przejrzyj diff, uruchom build/test i zapisz poprawiony lockfile w następnym commicie.
Jak wybrać zmianę zależności bez losowego psucia projektu
  • Sprawdź opublikowane wersje przez npm view <NAZWA_PACZKI> versions --json. Nie wymyślaj nieistniejącego numeru i nie aktualizuj całego Angulara na potrzeby tej próby.
  • Zmiana zakresu, który nadal obejmuje wersję z lockfile, nie zawsze powoduje niezgodność. Porównaj rzeczywiście zablokowaną wersję z nową deklaracją.
  • Odpowiednik w .NET: zmiana PackageReference bez aktualizacji packages.lock.json ma zostać odrzucona przez locked restore. Naprawiasz ją lokalnym restore w trybie aktualizacji i commitem odpowiednich lockfiles.
  • Nie „naprawiaj” CI przez zamianę npm ci na npm install, usunięcie lockfiles czy dodanie skip do testu.

Czego się uczysz

Czerwony test i czerwony restore wskazują różne problemy. Naprawa zachowania aplikacji nie jest zmianą oczekiwań testu, a aktualizacja zależności jest świadomą, recenzowaną zmianą.

Sprawdź w GitHub: masz dwie pary dowodów: test regresyjny fail → pass oraz instalacja zależności fail → pass. Wskaż inne pierwsze nieudane kroki. Testy nie zostały osłabione, a przejściowo zepsuty kod nie trafił do main.
Rdzeń

Zadanie 10 — zachowaj raport także po nieudanym teście

Zrób

  1. Sprawdź pliki generowane przez starter: TestResults/backend.trx, frontend/test-results/junit.xml i frontend/test-results/results.json. Nie zmieniaj test runnera tylko po to, by uzyskać raport.
  2. Na końcu obu jobs dodaj upload odpowiednich raportów z warunkiem always(). Nadaj różne nazwy backendowi i frontendowi, uwzględnij run oraz attempt i ustaw krótką retencję, np. 7 dni.
  3. W kontrolowanym PR ponownie wywołaj regresję z zadania 9, pobierz raport nieudanego testu, a następnie napraw kod.
Fragment jednego uploadu — drugi skonfiguruj samodzielnie
- name: Zachowaj raporty UI
  if: ${{ always() }}
  uses: actions/upload-artifact@v4
  with:
    name: frontend-tests-${{ github.run_id }}-${{ github.run_attempt }}
    path: frontend/test-results/
    retention-days: 7
    if-no-files-found: warn

warn uwzględnia sytuację, w której wcześniejszy restore/build nie pozwolił nawet uruchomić testów. Udany test ma jednak utworzyć rzeczywisty raport — sprawdź to. Upload nie może zmienić nieudanego joba na udany.

Czego się uczysz

Raport jest artefaktem konkretnego uruchomienia, nie plikiem źródłowym ani cache. Warunek zbierania dowodów po błędzie nie jest zgodą na ignorowanie błędu.

Sprawdź w GitHub: w podsumowaniu czerwonego runa jest artefakt z raportem rzeczywiście nieudanego testu. Po pobraniu znajdź nazwę i przyczynę błędu. Przy wcześniejszym błędzie kompilacji uczciwie odnotuj brak raportu, zamiast tworzyć pusty „wynik”.
Rdzeń

Zadanie 11 — jawna konfiguracja, fikcyjny sekret, minimalny token

Uprawnienia: dostęp do Settings → Secrets and variables → Actions.

Zrób

  1. Utwórz repository variable LAB_LABEL z nazwą grupy lub projektu. Przekaż vars.LAB_LABEL do env i użyj jej w czytelnym komunikacie.
  2. Utwórz repository secret LAB_DEMO_TOKEN z wymyśloną wartością wyłącznie do ćwiczenia. To nie ma być prawdziwy token GitHub ani hasło.
  3. Przekaż sekret do env tylko kroku sprawdzającego obecność. Wypisz jedynie „skonfigurowany” lub „brak”; nigdy wartość, długość, hash ani fragment sekretu.
  4. Ustaw workflow permissions: contents: read w postaci mapy YAML. Zweryfikuj testy także bez sekretu. Jobs zapisujące komentarz lub wydanie otrzymają osobne, wąskie uprawnienia dopiero w późniejszych zadaniach.
if [ -n "$LAB_DEMO_TOKEN" ]; then
  printf 'Sekret cwiczeniowy skonfigurowany\n'
else
  printf 'Brak sekretu; testy nie wymagaja go do pracy\n'
fi

Czego się uczysz

vars to jawna konfiguracja, secrets to chronione dane, a permissions ogranicza token danego joba. Nie odwołuj się bezpośrednio do secrets w if; kontrolę obecności wykonaj przez środowisko kroku. PR z forka zwykle nie otrzymuje sekretów i ma token tylko do odczytu.

Sprawdź w GitHub: log pokazuje etykietę projektu i wyłącznie informację o obecności sekretu. Zwykły build/test pozostaje zielony po usunięciu fikcyjnego sekretu. Nie ma dumpa env, toJSON(github) ani set -x przy danych chronionych.
Rdzeń

Zadanie 12 — wymagany PR i rzeczywiste checks

Uprawnienia: właściciel/admin repozytorium; do wymaganej recenzji potrzebna jest druga osoba z odpowiednim dostępem.

Zrób

  1. Po rzeczywistym zielonym runie otwórz PR i zapisz dokładne nazwy checks backendu i frontendu. Nie zgaduj ich na podstawie nazwy pliku YAML.
  2. Utwórz aktywny ruleset dla main albo równoważną ochronę gałęzi. Wymagaj PR i tych istniejących checks; zablokuj force push oraz usuwanie gałęzi. Nie dodawaj siebie do bypass list.
  3. W parze wymagaj jednego zatwierdzenia review. W pracy samodzielnej nie ustawiaj warunku niemożliwego do spełnienia własnym kontem — required PR i checks nadal obowiązują.
  4. Utwórz gałąź z niegroźną zmianą README i commitem. Spróbuj git push origin HEAD:main: push ma zostać odrzucony. Następnie wyślij tę samą gałąź normalnie i otwórz PR; nie zmieniasz lokalnego main ani historii zdalnej.
  5. Na PR-ie z kontrolowanym błędem sprawdź blokadę merge. Napraw przyczynę nowym commitem i scal dopiero po spełnieniu wszystkich reguł.
Gdzie sprawdzić, czy ochrona naprawdę działa

Użyj Settings → Rules → Rulesets albo Settings → Branches, zależnie od dostępnego interfejsu. Reguła musi być aktywna, obejmować main i obowiązywać także konto użyte do próby. W klasycznej ochronie uwzględnij administratorów. Jeśli push przechodzi lub widzisz możliwość bypass, popraw konfigurację i powtórz próbę — nie przedstawiaj jej jako sukcesu.

Po późniejszym refaktorze do reusable odczytasz nazwy na nowo. Dopiero w zadaniu 19 zastąpisz wymagane checks jednym sprawdzonym gate'em. Zmiany ustawień są częścią migracji, a nie powodem do wymuszonego merge.

Czego się uczysz

Zielony run sam nie chroni repozytorium. Ochrona jest polityką na gałęzi i wyniku dla właściwego SHA. Brak checka oraz check zakończony niepowodzeniem to różne przyczyny blokady.

Sprawdź w GitHub: zachowaj widok aktywnej reguły, odrzucenie bezpośredniego pushu i PR z zablokowanym merge przy czerwonym CI. Po poprawce blokada znika bez przycisku „bypass”, wyłączania reguły ani ręcznego nadawania zielonego statusu.
Rdzeń

Zadanie 13 — cache zależności: cold, warm i nowy lockfile

Zrób

  1. Zacznij od działającego CI bez cache. Dla npm dodaj actions/cache obejmujące ~/.npm, nie node_modules. Nie włączaj równocześnie drugiego cache tej samej ścieżki w setup-node.
  2. Przed krokiem cache sprawdź obecność .nvmrc i lockfile frontendu. Klucz ma uwzględniać własny namespace, system, architekturę, wersję Node i hash lockfile. Opcjonalny restore-keys może pominąć hash zależności, ale nie tożsamość środowiska.
  3. Uruchom cold run z nowym namespace klucza, potem drugi run tego samego kodu i ref. Odczytaj klucz, log przywracania, cache-hit i czas npm ci.
  4. Wprowadź zgodną zmianę zależności wraz z lockfile. Potwierdź nowy klucz i odróżnij exact hit od odtworzenia po prefiksie.
  5. Powtórz pomiar bez dostępu do poprzedniego cache, np. zmieniając wyłącznie swój namespace. Build i testy nadal mają przejść. Dla NuGet zastosuj tę samą zasadę do ~/.nuget/packages, wersji SDK i obu lockfiles .NET.
Przykładowy klucz, nie kompletna konfiguracja cache
key: ci-npm-v1-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.nvmrc') }}-${{ hashFiles('frontend/package-lock.json') }}

cache-hit jest tekstem: exact hit sprawdzasz przez == 'true'. Wartość inna niż 'true' nie dowodzi, że nie przywrócono niczego — przeczytaj log fallbacku. Brak pliku może dać pusty hash, dlatego potrzebna jest wcześniejsza kontrola plików.

Czego się uczysz

Cache przyspiesza ponowne pobranie paczek, ale może wygasnąć, zostać usunięty lub być niedostępny dla innej gałęzi. Nie zapisuj w nim bin, obj, raportów, sekretów ani konfiguracji prywatnych rejestrów.

Sprawdź w GitHub: w dzienniku masz trzy porównywalne pomiary: cold, warm, nowy lockfile, wraz z kluczami i wynikiem instalacji. npm ci, locked restore, build i testy wykonują się także po hit. Krótszy czas całego runa nie jest gwarantowany — istotne są logi i poprawność.
Rozszerzenie · przepływ danych

Zadanie 14 — output kroku do run i uses, obok GITHUB_ENV

Zrób

  1. W backendzie dodaj wczesny krok z id: metadata. Wylicz nazwę raportu zawierającą run i attempt i wystaw ją przez GITHUB_OUTPUT.
  2. W kolejnym kroku run przekaż output do env i wypisz nazwę. W kroku uses użyj tego samego outputu jako with.name uploadu raportu.
  3. W kroku producenta zapisz też jawną etykietę REPORT_COMPONENT=backend do GITHUB_ENV. Odczytaj ją w następnym kroku. Nie nadawaj jej ponownie przez YAML env, bo wtedy nie zbadasz tego mechanizmu.
  4. Powtórz ten kontrakt dla frontendu z odrębną nazwą artefaktu.
printf 'report_name=backend-tests-%s-%s\n' "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" >> "$GITHUB_OUTPUT"
printf 'REPORT_COMPONENT=backend\n' >> "$GITHUB_ENV"
Dwa różne miejsca odczytu tego samego outputu
# Krok run:
env:
  REPORT_NAME: ${{ steps.metadata.outputs.report_name }}
shell: bash
run: |
  printf 'Raport: %s\n' "$REPORT_NAME"

# Fragment with w kroku uploadu:
name: ${{ steps.metadata.outputs.report_name }}

Zachowaj id producenta i odwołuj się do poprzedniego kroku. Zapis do GITHUB_ENV nie zmienia już uruchomionej powłoki; wartość dostają kolejne kroki tego samego joba.

Czego się uczysz

Output jest nazwanym, tekstowym wynikiem operacji, użytecznym zarówno w poleceniu, jak i parametrach action. GITHUB_ENV ustawia środowisko następnych kroków, ale nie jest interfejsem joba ani sposobem przesłania pliku.

Sprawdź w GitHub: nazwa wypisana przez run jest identyczna z nazwą pobieranego artefaktu. Następny krok widzi etykietę z GITHUB_ENV. W żadnym z tych kanałów nie ma sekretu ani raportu zakodowanego jako wielki tekst.
Rozszerzenie · przepływ danych

Zadanie 15 — metadane i pliki między runnerami

Zrób

  1. Wystaw report_name jako output joba backendu i analogiczny output frontendu. Mapowanie musi prowadzić do wyniku kroku z zadania 14.
  2. Dodaj job podsumowania na ubuntu-24.04 z needs: [backend, frontend]. Na razie uruchamiaj go po sukcesie obu producentów; nie zastępuje on wymaganych testów.
  3. Przed pobraniem sprawdź, że nowy workspace nie ma raportów z poprzednich jobs. Użyj actions/download-artifact dwa razy: nazwy weź z needs, a ścieżki ustaw osobno, np. incoming/backend i incoming/frontend.
  4. Wyświetl listę otrzymanych plików i krótkie podsumowanie. Nie uruchamiaj pobranego pliku jako skryptu. Backend i frontend nadal nie mają zależeć od siebie.
# W jobie backend:
outputs:
  report_name: ${{ steps.metadata.outputs.report_name }}

# W with akcji download w jobie podsumowania:
name: ${{ needs.backend.outputs.report_name }}
path: incoming/backend

Czego się uczysz

needs porządkuje jobs i udostępnia zadeklarowane outputs. Przesłanie nazwy lub ścieżki pliku nie przesyła jego zawartości — temu służy artefakt. Bez always() zależny job zwykle nie ruszy po niepowodzeniu producenta.

Sprawdź w GitHub: podsumowanie zaczyna się po obu producentach, odczytuje ich nazwy i znajduje oba raporty dopiero po download. Graf nadal pokazuje równoległe sprawdzanie aplikacji.
Rozszerzenie · GitHub API

Zadanie 16 — zmienione pliki PR-a i krótki komentarz

Narzędzia: actions/github-script na runnerze. Do odczytu plików PR-a nadaj pull-requests: read; osobny job komentarza potrzebuje pull-requests: write.

Zrób

  1. Dodaj job changes. Dla PR-a pobierz wszystkie strony listy zmienionych plików przez github.paginate i github.rest.pulls.listFiles, nie tylko pierwsze 30 lub 100 wyników.
  2. Uwzględnij filename i, przy zmianie nazwy, previous_filename. Wylicz flagi backend i frontend; wystaw je oraz liczbę plików jako outputs kroku i joba.
  3. Zmiany w backend/, backend.tests/, rozwiązaniu i konfiguracji .NET dotyczą backendu; frontend/ i .nvmrc dotyczą frontendu. Zmiany automatyzacji w .github/ konserwatywnie uruchamiają oba. Jawnie sklasyfikuj wspólne pliki startera, także Directory.Build.* i NuGet.Config.
  4. Dla push i ręcznego startu, bez numeru PR-a, ustaw oba komponenty jako wymagające sprawdzenia. Awaria API, niekompletna lista lub nieznany format nie mogą po cichu dać dwóch false.
  5. Dodaj osobny job komentarza: tylko dla pull_request, gdy head.repo.full_name jest równe github.repository. Bez checkout kodu PR-a opublikuj przez github.rest.issues.createComment liczbę plików i obie flagi.
  6. Na razie obserwuj outputs, a nie pomijaj buildu. Warunkowe jobs wprowadzisz w zadaniu 19.
Punkt wyjścia do skryptu — klasyfikację i outputs dopisz samodzielnie
const files = await github.paginate(github.rest.pulls.listFiles, {
  ...context.repo,
  pull_number: context.issue.number,
  per_page: 100
});
const paths = files.flatMap(file =>
  [file.filename, file.previous_filename].filter(Boolean)
);
// Sklasyfikuj paths i wystaw wyniki przez core.setOutput.

API listy plików ma limit 3000 pozycji. Jeżeli liczba zmian w PR przekracza limit lub lista nie odpowiada deklarowanej liczbie, przerwij detekcję z błędem albo bezpiecznie zażądaj testów obu komponentów. Nie raportuj pozornego „braku zmian”.

core.setOutput serializuje wynik do tekstu. Konsument ma sprawdzać == 'true', a nie samo istnienie 'false'. Nie wstawiaj tytułu PR-a ani nazw plików bezpośrednio do kodu JavaScript przez expressions — korzystaj z danych API/context.

Krótki komentarz bez nadmiarowych uprawnień

Na poziomie joba komentarza zadeklaruj wyłącznie niezbędne pull-requests: write. Użyj numeru PR-a z payloadu i tekstu z wyliczonych metadanych. Stały znacznik komentarza pozwala odnaleźć i zaktualizować własny poprzedni komentarz zamiast spamować przy rerun; wyszukiwanie komentarzy również wymaga paginacji.

Job odczytu i zwykłe testy pozostają bez praw zapisu. Przy PR-ze z forka komentarz ma zostać pominięty, a odczyt i testy nadal mogą działać. Nie używaj pull_request_target do obejścia ograniczeń.

Czego się uczysz

API służy do obliczeń na metadanych GitHuba, Bash do poleceń, a expressions do przekazania wyniku i warunków. Prawo do odczytu plików PR-a nie jest prawem do opublikowania komentarza.

Sprawdź w GitHub: PR zmieniający tylko backend daje backend=true, frontend=false; README-only daje dwa false. Zmiana workflow daje dwa true. Komentarz na PR-ze z tego repozytorium zgadza się z logiem detekcji i nie zawiera sekretów. Wariant fork nie otrzymuje joba zapisu.
Rozszerzenie · reużywanie

Zadanie 17 — procedura backendu jako composite action

Zrób

  1. Utwórz .github/actions/backend-check/action.yml z runs.using: composite. Przenieś do niego istniejące kroki kontroli lockfiles, locked restore, build/test i wyliczenia metadanych raportu.
  2. Dodaj input configuration. Wszystkie inputs action są tekstowe, więc zaakceptuj tylko Debug lub Release, a nie dowolny ciąg przekazany do powłoki.
  3. Wystaw output report_name z wewnętrznego kroku. Wywołaj lokalną action w steps po checkout i setup-dotnet, nadaj wywołaniu id i popraw odwołania do wyniku.
  4. Ustaw shell: bash przy każdym wewnętrznym run. Upload raportów może pozostać w callerze z always(); nie ukrywaj niepowodzenia composite.
# Fragment action.yml, wewnętrzny krok ma id: metadata:
outputs:
  report_name:
    description: Nazwa artefaktu z raportem backendu
    value: ${{ steps.metadata.outputs.report_name }}
Na co uważać przy lokalnej action

Wywołanie to uses: ./.github/actions/backend-check, bez @ref. Action korzysta z runnera i workspace callera — dlatego lokalny checkout musi już istnieć. Jeżeli przeniesiesz skrypt pomocniczy obok action.yml, odwołuj się do niego przez github.action_path; katalog kodu aplikacji to osobna rzecz.

Wartość wejścia przekaż do env, zweryfikuj i cytuj w Bash. Id wewnętrznego kroku nie jest widoczne w callerze: caller widzi tylko output wywołanej action.

Czego się uczysz

Composite jest reużywalną procedurą z wejściem i wynikiem, nie nowym jobem. Zamiana listy kroków na action nie powinna zmienić tego, co CI faktycznie sprawdza.

Sprawdź w GitHub: backend wykonuje te same testy, a upload używa outputu composite. Zła konfiguracja daje czytelny błąd, a kontrolowana regresja nadal czerwony job i możliwy do pobrania raport.
Rozszerzenie · reużywanie

Zadanie 18 — backend jako reusable workflow

Zrób

  1. Utwórz .github/workflows/backend.yml z workflow_call. Przenieś odpowiedzialność za cały job backendu: runner, checkout, SDK, wywołanie composite, testy i raport.
  2. Zdefiniuj sensowne typowane inputs: configuration jako string, publish_reports jako boolean oraz report_retention_days jako number. Raporty mogą być opcją, ale testy zawsze muszą się wykonać. W głównym CI zostaw publikację włączoną.
  3. Zweryfikuj wartość konfiguracji i dodatnią, dozwoloną retencję. W boolean warunku użyj inputs.publish_reports, a nie porównania z tekstem.
  4. Wystaw wynik przez pełny łańcuch: krok/action → output joba → on.workflow_call.outputs. Caller ma móc odczytać nazwę raportu przez needs.backend.outputs.report_name.
  5. W ci.yml wywołaj reusable na poziomie jobs.backend.uses z lokalną ścieżką. Ten job callera nie ma własnych runs-on ani steps. Przekaż inputs jawnie; nie dodawaj secrets: inherit, bo proces nie potrzebuje sekretów.
  6. Po pierwszym runie PR odczytaj nowe nazwy checks. Zaktualizuj ochronę main do rzeczywistych nazw przed merge, gdy nowy backend przeszedł pełne sprawdzenie. Nie rób bypass starego brakującego checka.
Kontrakt do narysowania przed edycją
wewnętrzny krok metadata
  → output composite report_name
  → output joba w backend.yml
  → workflow_call.outputs.report_name
  → needs.backend.outputs.report_name w ci.yml

Workflow env callera nie jest automatycznie kontraktem callee. Przekaż konfigurację przez with. Boolean i number są typowane na wejściu workflow, ale wyjściowa nazwa artefaktu pozostaje tekstem. Uprawnienia w łańcuchu wywołań nie mogą się samodzielnie zwiększać.

Czego się uczysz

Reusable workflow przejmuje job i jego środowisko, composite tylko procedurę kroków. Publiczny interfejs wymaga jawnych inputs/outputs. Refaktor CI obejmuje także konfigurację required checks.

Sprawdź w GitHub: caller wywołuje backend, odbiera jego nazwę raportu i pobiera plik. Retencja pochodzi z liczbowego inputu. Po błędzie testu PR nadal nie daje się scalić; po poprawce nowa konfiguracja checks nie zostawia fałszywego Expected.
Rozszerzenie · quality gate

Zadanie 19 — warunkowe jobs i jeden zawsze obecny gate

Zrób

  1. Zostaw ci.yml uruchamiane dla każdego PR do main, bez workflow-level paths. Wykorzystaj job changes i jego kontrakt z zadania 16.
  2. Porównaj poniższy wariant dwóch workflow ze wspólną wymaganą nazwą checka. Następnie w rozwiązaniu docelowym uruchamiaj backend i frontend według flag == 'true' w jednym workflow. Dla push/main i ręcznego runa zachowaj sprawdzanie obu.
  3. Dodaj job z unikalną nazwą, np. ci-required, zależny od detekcji i obu komponentów. Ustaw if: ${{ always() }}, aby mógł ocenić także failure i skipped.
  4. Zaimplementuj regułę z tabeli. Nie wystarczy bezwarunkowe echo OK: niepoprawne flagi, błąd detekcji lub nieoczekiwany skipped mają dać błąd gate'a.
  5. Przećwicz opisane przypadki na PR-ach. Dopiero po rzeczywistych runach ustaw ten jeden gate jako wymagany zamiast warunkowych jobs i wcześniejszych nazw reusable.
Dwa opcjonalne workflow, jedna nazwa required — działający trik i jego granica

Można utworzyć dwa workflow z odrębnymi filtrami paths i nadać ich jobs tę samą jawną name: required, nawet przy różnych identyfikatorach jobów. W regule wybierasz rzeczywistą nazwę checka required; sam identyczny job ID nie jest właściwym kryterium konfiguracji.

Poniższy wynik sprawdzono z ochroną obowiązującą także administratora i bez bypass. Dotyczy wyników dla tej samej sprawdzanej zmiany, przy spełnionych pozostałych regułach.

Wymagany check name: required
Uruchomione workflowWyniki jobs o nazwie requiredWynik próby merge
0Brak wyniku obu filtrów pathsBLOCKED
1successCLEAN
2success + in progressBLOCKED
2success + successCLEAN
2success + failureBLOCKED

Nie jest więc prawdą, że pierwszy zielony job przykrywa trwający lub czerwony drugi. Trik wymaga jednak świadomego pokrycia przypadku zero workflow, np. przez zawsze uruchamiany jeden z tych workflow. Sam niezwiązany check o innej nazwie nie wypełni brakującego wymaganego wyniku.

Jeżeli odtwarzasz porównanie we własnym repozytorium, sprawdź wszystkie pięć przypadków i aktualne nazwy dla danego SHA. Nie zaliczaj zerowego przypadku przez wymuszony merge. W tym laboratorium docelowo wybieramy niżej opisany unikalny gate: jawniej określa politykę, obsługuje brak pracy i nie zależy od współdzielenia nazwy.

Warunki sukcesu gate'a
Sprawdzana rzeczDopuszczalny wynikKażdy inny wynik
Detekcja zmiansuccess, obie flagi dokładnie 'true' albo 'false'Błąd gate'a
Komponent wymagany: trueneeds.<job>.result == 'success'Błąd, także przy skipped/cancelled
Komponent niewymagany: falseskipped zgodnie z warunkiem jobaBłąd kontraktu; wyjaśnij rozbieżność
Obowiązkowe przypadki do sprawdzenia
  • 0 komponentów: zmiana tylko README/docs → dwa świadome skipped i zielony gate.
  • 1 komponent: zmiana tylko backendu, potem tylko frontendu → uruchomiony właściwy komponent, drugi skipped.
  • 2 komponenty: zmiana obu lub wspólnego workflow → obydwa muszą przejść. Sukces pierwszego, gdy drugi jeszcze pracuje, nie wystarcza.
  • Błąd: regresja w wymaganym komponencie → czerwony gate i blokada merge, nawet gdy drugi jest zielony/skipped.
  • Uszkodzona detekcja: brak outputu lub nieudany job changes → czerwony gate, nie dwa uznane za sukces pominięcia.

Potem usuń kontrolowane błędy. Gate ma być zwykłym sprawdzeniem bieżącego SHA, nie ręcznie nadanym statusem. Przerwane lub brakujące uruchomienie nie jest dowodem sukcesu.

Czego się uczysz

Optymalizacja zakresu nie może zmieniać znaczenia „CI przeszło”. Zawsze obecny, jednoznaczny wynik oddziela politykę merge od liczby uruchomionych jobs.

Sprawdź w GitHub: dla 0/1/2 komponentów pojawia się dokładnie właściwy gate, a każdy czerwony scenariusz blokuje merge. Brak pracy aplikacyjnej jest udokumentowanym zielonym wynikiem, nie Expected i nie okazją do force merge.
Rozszerzenie · osobne uruchomienia

Zadanie 20 — workflow_run i artefakt właściwego producenta

Zrób

  1. Do Mission Control CI dodaj mały artefakt metadanych, np. ci-metadata-<run_id>-<run_attempt>. JSON ma zawierać wersję schematu, SHA sprawdzanego kodu, ID runa i attempt — nie sekret, skrypt ani pełen kontekst GitHub.
  2. Utwórz osobny .github/workflows/inspect-ci.yml z workflow_run dla nazwy producenta i typu completed. Najpierw wprowadź ten plik na main przez zielony PR; podobnie jak przy dispatch definicja konsumenta musi istnieć w gałęzi domyślnej.
  3. Ogranicz konsumenta do udanego push na main z tego repozytorium. Sprawdź w payloadzie producenta conclusion, event, head_branch, head_repository.full_name oraz oczekiwany workflow. PR, fork i nieudany run nie mogą przejść tej granicy.
  4. Nadaj konsumentowi tylko actions: read i, jeśli potrzebne, contents: read. Pobierz artefakt przez jawne run-id producenta i token do odczytu. Nie szukaj „ostatniego zielonego runa”.
  5. Odczytaj JSON jako dane. Sprawdź schemat i zgodność SHA/ID/attempt z payloadem producenta. Wyświetl krótkie podsumowanie, bez checkout lub wykonywania kodu ze źródłowego PR-a.
Kluczowe parametry download z innego runa
# Fragment with akcji actions/download-artifact:
github-token: ${{ github.token }}
run-id: ${{ github.event.workflow_run.id }}
name: ci-metadata-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
path: received

github.run_id w konsumencie oznacza jego własny run, więc nie zastępuje github.event.workflow_run.id. Przy push porównujesz SHA zapisane przez producenta z workflow_run.head_sha, nie z późniejszym czubkiem main.

workflow_run może działać z uprawnieniami niedostępnymi pierwotnemu PR-owi. Samo filtrowanie nazwy nie jest kontrolą zaufania. Tutaj celowo nie ma zapisu, sekretów, wykonywania pobranych plików ani przywracania cache wytworzonego przez PR.

Czego się uczysz

Niezależne runy nie mają wspólnego needs ani globalnych outputs. Łączysz je przez identyfikowalny artefakt i jawny kontrakt pochodzenia. Taki konsument nie zastępuje required checka na PR-ze.

Sprawdź w GitHub: po zielonym push/main drugi run wskazuje dokładne ID producenta i zgodny SHA. ID obu runów są różne. Po PR lub nieudanym CI konsument nie pobiera danych w zaufanej ścieżce. Zły schemat metadanych daje błąd, a nie wykonanie ich treści.
Rozszerzenie · CI poza aplikacją

Zadanie 21 — prawdziwa praca LaTeX, prerelease i pełne wydanie PDF

Narzędzia: publiczny szablon pantadeusz/pjatk_latex, pdfLaTeX uruchamiany przez latexmk, Biber dla bibliografii i GitHub CLI do publikacji. Lokalna instalacja LaTeX jest opcjonalna.

Ustalony kontrakt:

Upstream w commicie e660b9ee1e637a843c9084774abdc9b3ecdaae4c używa pliku engineer.tex, lokalnej klasy sprz.cls i wyniku engineer.pdf. Budujemy przez latexmk + pdfLaTeX; bibliografię obsługuje Biber. To nie jest projekt z main.tex ani minted.

Publiczny kod nie oznacza licencji na dowolne kopiowanie:

Ta rewizja upstream nie zawiera wyraźnej licencji. Nie przenoś przykładowej pracy, logo ani klasy do publicznego startera/kursu i nie nadaj im własnej licencji. W tym wariancie ćwiczenia thesis/ zawiera Twoje własne źródła, a zależność szablonu jest pobierana osobno na runnerze. PDF ma zawierać własny tekst, bez przykładowej pracy i cudzego logo.

Możesz także użyć funkcji Fork w GitHubie dla publicznego upstream i pisać tam własną treść; nie jest to zgoda na dowolny eksport lub zmianę licencji cudzych materiałów. Zachowaj informację o autorze szablonu i odnotuj prawa do używanych ilustracji oraz innych zewnętrznych treści. Nie myl tego publicznego upstream z prywatnym repozytorium prowadzącego.

Zrób

  1. Przeczytaj przypięty przykład engineer.tex, aby rozpoznać interfejs klasy. Napisz własny, niewielki dokument thesis/engineer.tex używający sprz, np. opis swojego eksperymentu CI. Nie kopiuj przykładowej pracy ani logo; pomiń makra \maketitle, \makeprojectcard, \makedeclaration i \makethesisattachments, które dodają branding lub gotowe treści.
  2. W thesis/README.md zapisz autora/adres upstream, pełny SHA, informację o braku wyraźnej licencji, własne pliki źródłowe oraz polecenie budowania. Jeżeli dodajesz cytowania lub ilustracje, użyj własnej bibliografii i materiałów, do których masz odpowiednie prawa.
  3. Utwórz .github/workflows/thesis.yml dla PR do main oraz push na main. W jobie kompilacji pobierz kod studenta i osobno publiczny pantadeusz/pjatk_latex: drugi checkout ma wskazywać pełny SHA powyżej i osobny path: upstream-latex, nie ruchomy main.
  4. Oba checkouts mają nie utrwalać poświadczeń (persist-credentials: false). Kompilacja ma wyłącznie odczyt i nie dostaje własnych sekretów. Dla tego zadania PR kompiluje konkretny pull_request.head.sha, a push github.sha; zapisz źródłowy SHA. To celowo inny wybór niż domyślny merge ref aplikacyjnego CI.
  5. Na runnerze ubuntu-24.04 zainstaluj podane niżej pakiety. Wyłącznie na czas pracy runnera skopiuj niezmienioną sprz.cls obok własnego dokumentu w thesis/, zachowując nagłówek z autorstwem. Buduj własny dokument z wyłączonym shell escape; minimalny wariant nie potrzebuje bibliografii ani ilustracji upstream.
  6. Sprawdź niepusty thesis/engineer.pdf i udostępnij dokładnie ten plik jako artefakt runa tylko po udanej kompilacji, nie przez bezwarunkowe always() z zadania o raportach testów. Nie wysyłaj całego katalogu upstream ani nie commituj pobranej klasy, przykładów, logo czy wyników TeX. Przy lokalnym powtórzeniu ignoruj roboczą thesis/sprz.cls, katalog upstream-latex/ i wyniki kompilacji.
  7. Dodaj osobne jobs publikacji, bez checkout, które tylko pobierają PDF z tego samego runa i tworzą wydanie. Tylko one otrzymują contents: write; GH_TOKEN jest dostępny jedynie w kroku CLI. SHA docelowego taga pochodzi ze zdarzenia, nie z dowolnego pliku w artefakcie.
  8. Najpierw bootstrap na main: PR dodający lub zmieniający workflow ma kompilować i zachowywać artefakt, ale pomijać prerelease z czytelnym komunikatem. Sprawdź go i scal definicję na main. GITHUB_TOKEN nie może utworzyć wydania wskazującego commit PR-a z inną zawartością .github/workflows niż gałąź domyślna.
  9. Od aktualnego main utwórz nowy PR zmieniający tylko własne pliki TeX. Dla PR-a z tego samego repozytorium publikuj prerelease, np. z tagiem thesis-pr-<numer-PR>-run-<run_id>-attempt-<run_attempt>. Dla push/main publikuj pełne wydanie, np. thesis-v1.<run_id>.<run_attempt>. Oba muszą zawierać PDF jako asset w Releases.
  10. Pobierz PDF z prerelease, popraw własny tekst i sprawdź kolejne odrębne wydanie. Po zielonym PR i merge pobierz PDF pełnego wydania. Sprawdź także błąd kompilacji oraz PR zmieniający workflow: pierwszy ma być czerwony bez wydania, drugi może być zielony, ale artefakt-only z wyjaśnieniem.
Minimalny własny dokument — bez cudzej pracy i stron z brandingiem

To autorski punkt wyjścia dla thesis/engineer.tex. Zastąp treść swoim opisem; nie jest to fragment przykładowej pracy upstream. Sama klasa nie dodaje automatycznie stron tytułowych, deklaracji ani załączników.

\documentclass{sprz}
\begin{document}
\chapter{Raport z laboratorium CI}
\section{Cel}
Sprawdzam, czy zmiana mojego tekstu daje nowe wydanie PDF.
\section{Wynik}
Tutaj opiszę własne uruchomienie oraz wnioski z eksperymentu.
\end{document}

W tym wariancie nie wywołuj makr bibliografii ani nie dodawaj obrazów upstream. Brak logo i gotowej pracy w PDF nie oznacza nadania klasie swobodnej licencji — zachowaj opis pochodzenia i ograniczeń w swoim README.

Pakiety Ubuntu 24.04 i dokładne polecenie kompilacji

To komendy joba na standardowym ubuntu-24.04, nie polecenia do uruchomienia w Windows. Pakiety obejmują polski skład, klasę i bibliografię tego konkretnego szablonu.

sudo apt-get update
sudo apt-get install -y latexmk biber texlive-latex-extra \
  texlive-bibtex-extra texlive-fonts-recommended \
  texlive-lang-polish texlive-science

Po dwóch checkoutach, z korzenia workspace; dokument studenta musi już istnieć. Kopia klasy powstaje tylko w roboczym workspace, nie w historii kursu ani startera.

cp upstream-latex/sprz.cls thesis/sprz.cls
cd thesis
rm -f engineer.pdf
latexmk -norc -pdf -interaction=nonstopmode -halt-on-error -file-line-error -no-shell-escape engineer.tex
test -s engineer.pdf

Usuwasz wyłącznie ewentualny poprzedni engineer.pdf, aby sprawdzić nowy wynik. -norc pomija pliki konfiguracyjne latexmk, -halt-on-error zatrzymuje błędną kompilację, a -no-shell-escape nie pozwala dokumentowi uruchamiać poleceń powłoki. Nie usuwaj tych ograniczeń jako „naprawy” CI. Biber jest narzędziem bibliografii tego szablonu, choć minimalny dokument bez cytowań go nie uruchomi. Nie zastępuj komendy samym make: upstream wywołuje niejawny domyślny tryb latexmk, a tutaj wymagamy PDF.

Mały przykład operacji publikacji, nie całego workflow

Zmienne pochodzą z uprzednio zweryfikowanych metadanych. PDF_PATH to znana ścieżka do odebranego PDF, a nie plik skryptu. Dla pełnego wydania użyj odrębnego taga i wywołania bez --prerelease oraz bez --latest=false.

gh release create "$TAG" "$PDF_PATH#Praca dyplomowa PDF" \
  --repo "$GITHUB_REPOSITORY" --target "$SOURCE_SHA" \
  --title "$RELEASE_TITLE" --notes "$RELEASE_NOTES" --prerelease --latest=false

--target wskazuje dokładny commit Twojego dokumentu, nie aktualny main ani pin zewnętrznego szablonu. SHA upstream opisuje zależność i nie musi istnieć w repozytorium studenta. Nie przenoś istniejących tagów i nie nadpisuj wydań/assetów: run ID i attempt zapewniają nowe nazwy także przy immutable releases.

Wydanie utworzone za pomocą GITHUB_TOKEN nie uruchamia kolejnych workflow przez on: release. Nie opieraj na takim zdarzeniu dowodu działania ćwiczenia.

Warunki zapisu: TeX-only, workflow-editing i fork PR
  • Prerelease: udana kompilacja, zdarzenie pull_request, baza main, github.event.pull_request.head.repo.full_name == github.repository oraz zgodna z main zawartość .github/workflows w źródłowym commicie. Samo head.repo.fork == false nie sprawdza tożsamości repozytorium.
  • Kontrolę zmian workflow wykonaj bez uprawnień zapisu. Wykorzystaj paginację z zadania 16; pamiętaj także o PR-ze starszym niż aktualny main. Potwierdź zgodność drzew .github/workflows dla obu commitów, nie tylko brak tej ścieżki na liście zmian od początku PR-a. Niepotwierdzona zgodność oznacza artefakt-only, nigdy próbę eskalacji tokenu.
  • PR zmieniający workflow: pomiń publikację i dodaj jawny komunikat, np. przez core.notice. Po merge do main utwórz świeży TeX-only PR. Nie obchodź ograniczenia wydawania przez PAT z szerszymi uprawnieniami ani tag wskazujący inne źródło niż PDF.
  • Pełne wydanie: udana kompilacja, zdarzenie push i dokładnie refs/heads/main. Push samego taga nie jest tym zdarzeniem.
  • Fork PR: kompilacja i artefakt mogą działać bez sekretów; oba jobs publikacji mają być skipped. Przy zewnętrznym współtwórcy GitHub może wymagać zatwierdzenia uruchomienia przez opiekuna repozytorium.
  • PR Dependabota także pozostaje artefakt-only ze względu na ograniczony token. Sprawdź autora PR-a (github.event.pull_request.user.login), nie tylko osobę klikającą rerun.
  • Nie używaj pull_request_target do kompilacji PR-a z tokenem zapisu. Nie wykonuj skryptów ani kodu z artefaktu w jobie publikacji.
  • Zachowaj osobne nazwy artefaktów dla run/attempt. Błędny build nie może opublikować PDF pozostałego po innej kompilacji; pobieraj wyłącznie dokładny artefakt tego udanego joba.

Czego się uczysz

Continuous Integration może sprawdzać pracę dyplomową, a wydanie może publikować dokument. Kompilacja, przechowanie raportu i utworzenie wersjonowanego assetu to różne operacje z różnymi uprawnieniami. Pełny release PDF nie wdraża aplikacji.

Sprawdź w GitHub: w Releases jest prerelease z TeX-only PR-a i odrębne pełne wydanie po push/main; oba zawierają otwierający się PDF własnego tekstu i właściwy tag/SHA. Rerun nie koliduje z poprzednim tagiem. PR z forka oraz PR zmieniający workflow kończą się bez zapisu wydania, z artefaktem po udanej kompilacji i czytelną przyczyną pominięcia. W repozytorium nie ma skopiowanej pracy ani logo upstream. Sam link do artefaktu runa nie zalicza publikacji wydania.
Finał · ręczny eksperyment

Zadanie 22 — statyczna macierz Debug / Release

Macierze zostawiamy na koniec: w tym projekcie są rzadziej potrzebnym narzędziem porównania wariantów, nie powodem do przebudowy zwykłego CI aplikacji.

Osobny proces: zadania 22–23 wykonujesz w ręcznym .github/workflows/matrix.yml, np. nazwanym Matrix lab. Nie dodawaj jego jobs ani podsumowania do required checks PR-a. Wymagany gate i ochrona main z zadania 19 pozostają aktywne i działają niezależnie.

Zrób

  1. Utwórz matrix.yml wyzwalane tylko przez workflow_dispatch. Dodaj job static_tests ze statyczną macierzą Debug/Release, fail-fast: false i max-parallel: 2. Każdy wariant używa ubuntu-24.04 i SDK z global.json, bez dodatkowych systemów ani wersji SDK.
  2. Po checkout i setup-dotnet wykorzystaj procedurę backendu z zadania 17, przekazując matrix.configuration. Każdy wariant wykonuje locked restore, build i wszystkie testy Testcontainers. Nie uzależniaj testów od tego, czy chcesz pobrać raport.
  3. Uploaduj raporty z unikalnymi nazwami, np. static-<konfiguracja>-<run_id>-<run_attempt>, także po nieudanym teście. Lokalna ścieżka TRX może być wspólna dla oddzielnych runnerów; nazwy artefaktów w jednym runie nie mogą kolidować.
  4. Dodaj job matrix_summary z nazwą Matrix summary, needs: static_tests i if: ${{ always() }}. Ma zakończyć się sukcesem tylko wtedy, gdy wynik całej grupy static_tests jest success; failure, cancelled lub nieoczekiwany skipped to nie sukces.
  5. Wprowadź definicję workflow na main przez PR z działającym CI z zadania 19. Dopiero potem użyj Actions → Matrix lab → Run workflow, zgodnie z bootstrapem z zadania 4.
  6. Na osobnej gałęzi ćwiczeniowej dodaj po testach kontrolowany błąd wyłącznie w wariancie Debug. Ręcznie uruchom workflow z tej gałęzi, sprawdź wynik Release, podsumowanie i czerwony run. Usuń błąd kolejnym commitem i powtórz ręczne uruchomienie. Nie przedstawiaj wyniku manualnego jako dowodu blokady merge PR-a.
strategy:
  fail-fast: false
  max-parallel: 2
  matrix:
    configuration: [Debug, Release]
Kontrolowany błąd tylko w Debug — usuń po eksperymencie

Dołóż ten krok po rzeczywistych testach na gałęzi ćwiczeniowej, nie w stabilnej wersji main. Nie wyłączaj testów ani propagacji błędów.

- name: Kontrolowany błąd Debug
  if: ${{ matrix.configuration == 'Debug' }}
  shell: bash
  run: exit 1
Wariant porównawczy: include/exclude i zbieranie raportów

Zamiast prostej dwuelementowej macierzy możesz porównać poniższe trzy warianty. collect_reports steruje wyłącznie uploadem, a diagnostics dodatkowym krokiem, np. dotnet --info. Wszystkie warianty nadal uruchamiają komplet testów.

strategy:
  fail-fast: false
  max-parallel: 2
  matrix:
    configuration: [Debug, Release]
    collect_reports: [true, false]
    exclude:
      - configuration: Debug
        collect_reports: false
    include:
      - configuration: Release
        collect_reports: true
        diagnostics: true

Sprawdź: pojawiają się Debug/z raportem, Release/z raportem i Release/bez uploadu. Nie ma Debug/bez raportu, a diagnostyka działa tylko dla Release/z raportem. Dodaj wybór raportowania do nazw jobs; nie oznaczaj braku uploadu jako braku wykonanych testów. To porównanie w ręcznym workflow, nie kolejny obowiązkowy proces dla każdego PR-a.

Czego się uczysz

Matrix powiela job, a nie dysk. fail-fast: false pozwala zebrać wyniki pozostałych wariantów po błędzie; max-parallel ogranicza równoległość tej grupy. Include może dopisać metadane pasującej kombinacji, a exclude usuwa wybrane kombinacje.

Sprawdź w GitHub: Debug i Release wykonują prawdziwe testy i mają odrębne wyniki. Kontrolowany błąd Debug daje czerwone Matrix summary i cały ręczny workflow, mimo sukcesu Release; po poprawce wynik jest zielony. Required CI z zadania 19 nadal chroni PR-y, ale ręczna macierz nie jest wymaganym checkiem.
Finał · ostatnie zadanie

Zadanie 23 — dynamiczna macierz z outputu JSON

Zrób

  1. W tym samym matrix.yml dodaj do workflow_dispatch input include-debug typu boolean, domyślnie false. Wprowadź zmianę definicji na main przez zwykły PR, aby formularz ręcznego uruchomienia miał aktualne wejście.
  2. Dodaj job plan. W kroku github-script z id: matrix przekaż input przez env, np. INCLUDE_DEBUG: ${{ inputs['include-debug'] }}. W środowisku to już tekst: zaakceptuj wyłącznie 'true' lub 'false', a inną wartość odrzuć.
  3. Zbuduj obiekt z tablicą include. Zawsze dodaj {configuration: 'Release', lane: 'release'}; przy true dodaj {configuration: 'Debug', lane: 'debug'}. Zweryfikuj liczbę wpisów, dozwolone konfiguracje i unikalne, zgodne z nimi lane. Nie potrzebujesz pustego planu: Release jest zawsze obecny.
  4. Zapisz wynik przez core.setOutput('matrix', JSON.stringify(plan)) i wystaw go jako output joba. Waliduj plan przed publikacją outputu, zamiast przekazywać niepoprawny JSON do planowania GitHuba.
  5. Dodaj job dynamic_tests z needs: plan i macierzą z fromJSON. Uruchamiaj go tylko po sukcesie planu. Zachowaj ubuntu-24.04, fail-fast: false, max-parallel: 2 i rzeczywiste testy backendu. Nadaj raportom nazwy dynamic-<lane>-<run_id>-<run_attempt>.
  6. Rozszerz matrix_summary: ma zawsze się uruchamiać, zależeć od plan i dynamic_tests oraz kończyć sukcesem tylko po sukcesie obu. Jeżeli pozostawiasz grupę statyczną do porównania, uwzględnij także jej wynik. To nadal podsumowanie ręcznego workflow, nie wymagany gate PR.
  7. Porównaj ręczne uruchomienia z false i true. Na gałęzi ćwiczeniowej sprawdź też niedozwolony wpis planu, np. konfigurację Preview, oraz kontrolowany błąd wariantu Debug przy true. Obie próby mają dać czerwone podsumowanie. Usuń kontrolowane błędy i pozostaw działający wariant.
Kontrakt planu przy include-debug=true

Przy false tablica zawiera wyłącznie pierwszy wpis. Input naprawdę wybiera dodatkowe wykonanie testów; nie jest sztuczną flagą służącą tylko do wypisania typu.

{
  "include": [
    {"configuration": "Release", "lane": "release"},
    {"configuration": "Debug", "lane": "debug"}
  ]
}

Dozwolone są dokładnie te pary. Brak Release, powtórzone lane albo inna konfiguracja to błąd producenta. fromJSON konwertuje tekst do obiektu, ale nie zastępuje tej walidacji.

Dwa miejsca mapowania: output planu i strategy konsumenta

Najpierw wystaw wynik kroku na poziomie joba plan:

outputs:
  matrix: ${{ steps.matrix.outputs.matrix }}

Następnie użyj go w jobie dynamic_tests. Warunek po sukcesie planu zapobiega rozwijaniu macierzy z brakującego outputu:

needs: plan
if: ${{ needs.plan.result == 'success' }}
strategy:
  fail-fast: false
  max-parallel: 2
  matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}

W odrębnym jobie matrix_summary pozostaw if: ${{ always() }} i sprawdź wyniki jego zależności. Gdy walidacja planu zawiedzie, dynamic_tests będzie skipped, ale podsumowanie ma być czerwone z powodu błędu planu.

Jak pogrupować porównanie bez powielania CI dla każdego PR-a

Możesz pozostawić static_tests i dynamic_tests obok siebie w jednym ręcznym workflow. Wtedy podsumowanie sprawdza wszystkie skonfigurowane grupy, a max-parallel: 2 dotyczy każdej grupy osobno. Prefiksy static- i dynamic- rozdzielają raporty.

Jeśli zadanie 22 jest już udokumentowane, możesz w ćwiczeniu 23 zastąpić grupę statyczną dynamiczną i odpowiednio uprościć needs podsumowania. Nie zmieniaj zwykłego ci.yml, detekcji zmian ani reguł ochrony PR z zadania 19.

Czego się uczysz

Typowany input wybiera warianty, poprzedni job publikuje zweryfikowany plan jako tekst, a fromJSON odtwarza strukturę macierzy. Jawne lane porządkują raporty, a zawsze obecne podsumowanie pokazuje także błąd planowania.

Sprawdź w GitHub: przy false grupa dynamiczna ma tylko Release; przy true ma Release i Debug. Zły plan oraz nieudany wariant dają czerwone Matrix summary i ręczny workflow. Raporty są rozróżnialne, a ochrona PR nadal korzysta wyłącznie ze zwykłego CI. To ostatnie zadanie implementacyjne — przejdź do kryteriów ukończenia.

Oddanie i kryteria ukończenia

W README swojego repozytorium prowadź krótki dziennik: numer zadania, PR/run, SHA, oczekiwany i zaobserwowany rezultat. Nie kopiuj logów z sekretami ani całych raportów do Gita. Poniższa lista podsumowuje wykonane zadania; nie dodaje nowych etapów implementacji.

Kryteria rdzenia — zadania 1–13
  • Pierwszy własny workflow ma czytelne kroki; odróżniasz zdarzenie, run, job i step.
  • Działają PR do main, push na main i ręczne uruchomienie z konfiguracją oraz diagnostyką.
  • W repozytorium są trzy samodzielnie wygenerowane lockfiles; lokalny i zdalny build/test przechodzą bez osłabiania testów.
  • Backend i frontend są osobnymi równoległymi jobs; widać regresję aplikacji i osobno błąd nieaktualnego lockfile, a potem poprawki.
  • Raport nieudanego testu można pobrać; workflow pozostaje czerwony.
  • Main wymaga PR i rzeczywistych checks. Masz dowód blokady, bez użycia bypass.
  • Cache ma udokumentowane cold/warm/zmianę lockfile; instalacja i testy nie zależą od trafienia w cache.
  • Brak prawdziwych sekretów w kodzie, logach, outputs i artefaktach; testy API działają także bez sekretu ćwiczeniowego.
Kryteria rozszerzenia — zadania 14–23
  • Umiesz prześledzić metadane i pliki przez krok, job, composite, reusable i osobny run.
  • Zmiany PR-a są pobierane z paginacją, a komentarz zapisuje tylko uprawniony job dla PR-a z tego repozytorium.
  • Wymagany gate z zadania 19 obsługuje 0/1/2 komponenty i nie akceptuje błędu ani nieoczekiwanego pominięcia.
  • Konsument workflow_run pobiera artefakt właściwego producenta i odrzuca nieuprawnione źródło.
  • Przypięty szablon LaTeX z własną treścią daje PDF, prerelease z PR-a oraz pełne wydanie z main; pochodzenie i ograniczenia praw do materiałów upstream są opisane, bez kopiowania cudzej pracy ani logo.
  • Ręczne macierze sprawdzają rzeczywiste konfiguracje backendu. Masz porównanie include-debug=false/true, odrębne raporty i czerwone podsumowanie dla błędu wariantu oraz błędnego planu.
  • W wybranym wariancie include/exclude potrafisz wskazać usuniętą kombinację i dodane metadane; wyłączenie uploadu nie pomija testów.
  • Manualne jobs macierzy nie są required checks PR-a. Ochrona main z zadania 19 pozostaje aktywna.

Ściąga diagnostyczna

Run nie istnieje, jest czerwony albo wymagany check czeka
  • Brak runa: sprawdź ścieżkę .github/workflows/*.yml, poprawność YAML, włączenie Actions, zdarzenie i filtr gałęzi. W PR filtr dotyczy bazy. Unikaj znaczników typu [skip ci] w commitach wymagających CI.
  • Brak Run workflow: definicja z workflow_dispatch musi najpierw znaleźć się w gałęzi domyślnej; patrz zadanie 4. Po aktualizacji formularza macierzy sprawdź także obecność nowego inputu na main.
  • Nie ma plików: checkout musi działać w każdym jobie, który potrzebuje kodu. Katalog z poprzedniego runnera nie jest współdzielony.
  • Nie ma raportu: przy błędzie restore/build test nie został uruchomiony. always() nie tworzy brakującego pliku. Sprawdź też ścieżkę od korzenia workspace, nie od defaults.run.working-directory. W porównawczej macierzy sprawdź, czy świadomie wyłączono upload.
  • Cache hit, a instalacja nie działa: sprawdź manifest, lockfile, wersję narzędzia i rejestr. Cache to paczki do ponownego użycia, nie dowód poprawności zależności.
  • Resource not accessible by integration: sprawdź permissions, pochodzenie PR-a i politykę organizacji. Nie rozwiązuj tego przez pull_request_target wykonujący kod PR-a.
  • Expected/Pending na PR: porównaj wymaganą nazwę z checkiem dla bieżącego SHA. Po refaktorze reusable odczytaj nowe nazwy. Nie dodawaj ręcznego Matrix summary jako warunku merge i nie myl brakującego workflow z jobem zakończonym jako skipped.
  • Dynamiczna grupa jest skipped: sprawdź log i walidację joba plan. Nie próbuj naprawiać błędnego outputu przez bezwarunkowy sukces podsumowania.
  • Pierwszy czerwony krok: przeczytaj komunikat przed poprawką. Rerun jest przydatny przy problemie przejściowym; błąd kodu lub konfiguracji naprawia nowy commit.

Pytania do samokontroli

Nie są dodatkowymi zadaniami do implementacji. Użyj ich podczas przygotowania krótkiego wyjaśnienia swojego rozwiązania.

  1. Dlaczego checkout, SDK i zależności przygotowujesz ponownie w drugim jobie?
    Sprawdź rozumowanie

    Job otrzymuje osobny runner i workspace. Zależność needs przenosi kolejność i wystawione outputs, ale nie pliki ani procesy.

  2. Czy cache pozwala pominąć locked restore lub testy?
    Sprawdź rozumowanie

    Nie. Cache może zawierać tylko część paczek, pochodzić ze starszego klucza albo nie istnieć. Lockfile i instalacja weryfikują zależności, a testy zachowanie aplikacji.

  3. Dlaczego adnotacja błędu i pobrany raport nie wystarczają do zablokowania merge?
    Sprawdź rozumowanie

    Proces musi zwrócić niezerowy kod, a odpowiedni check musi być wymagany przez aktywną regułę. Artefakt jest dowodem, nie polityką ochrony. Ręczne ćwiczenie macierzy nie jest takim wymaganym checkiem.

  4. Co oznacza output o wartości 'false'?
    Sprawdź rozumowanie

    To tekst. Sprawdzaj go jawnie, np. == 'true', albo konwertuj zweryfikowany JSON. Nie traktuj niepustego tekstu jako flagi logicznej.

  5. Po co unikalny gate, skoro dwa workflow mogą używać tej samej wymaganej nazwy checka?
    Sprawdź rozumowanie

    W sprawdzonym wariancie wspólnej nazwy jeden sukces wystarcza, gdy ruszył tylko jeden workflow; gdy ruszyły oba, pending albo failure drugiego nadal blokuje merge. Problemem pozostaje zero uruchomionych workflow: wymagany wynik nie istnieje. Stały, unikalny gate potrafi jawnie zaakceptować świadomy brak pracy oraz odrzucić błąd detekcji i nieoczekiwany skipped.

  6. Czym różnią się artefakt Actions i asset GitHub Release?
    Sprawdź rozumowanie

    Artefakt należy do runa i ma retencję. Asset wydania jest dostępny przy konkretnym tagu w Releases. Publikacja PDF nie oznacza wdrożenia aplikacji.

  7. Dlaczego kompilator TeX nie powinien otrzymać tokenu zapisu?
    Sprawdź rozumowanie

    Źródło PR-a jest kodem wejściowym dla narzędzia, nie zaufaną instrukcją administracyjną. Kompilacja i publikacja mają osobne jobs; zapis jest dozwolony tylko dla zatwierdzonego rodzaju zdarzenia i repozytorium.

  8. Co identyfikuje raport z macierzy?
    Sprawdź rozumowanie

    Grupa static/dynamic, konfiguracja lub lane oraz run/attempt. Jeden wspólny output matrix joba nie jest listą wyników wszystkich wariantów.

  9. Dlaczego ręcznego Matrix summary nie dodajemy do wymaganych checks PR-a?
    Sprawdź rozumowanie

    Manualny eksperyment nie uruchamia się automatycznie dla każdej zmiany PR-a, więc mógłby pozostawić brakujący wymagany wynik. Przed merge kod sprawdza zwykłe CI i gate z zadania 19; podsumowanie macierzy ocenia tylko swój ręczny run.