11 KiB
Playbook wdrożenia — ipal-kit
Sztywna procedura dla pracownika albo AI (Antigravity). Mówi CO robić, W JAKIEJ KOLEJNOŚCI, i CZYM SIĘ KIEROWAĆ. Zasady są twarde, przykłady realne — wzięte z faktycznych błędów, które się zdarzyły. Odstępstwa tylko za świadomą decyzją.
Powiązane: standardy-kodu.md (dobre praktyki senior), publishing.md (cykl publikacji), getting-started.md (nowy projekt), ../ANTIGRAVITY-ZASADY-AGENT.md (zasady dla AI).
ZŁOTE ZASADY (łam tylko świadomie)
- Nic na sztywno. Tekst, obraz, link, dane firmy → panel/baza, nie kod.
- Logika w pluginie, projekt podłącza. Jeśli piszesz w projekcie coś, co robi już plugin — zatrzymaj się, użyj pluginu.
- Next 16 = proxy.ts. NIGDY middleware.ts. Jeśli istnieje — usuń.
- Weryfikuj każdy etap grepem. Nie zakładaj, że zadziałało. Sprawdź.
- Napraw u źródła, nie łataj. Bez
as any,@ts-ignore, kopii logiki. - Zmiana w pluginie nie działa, dopóki nie: build → publish → wciągnięcie.
- Zmieniłeś API → zaktualizuj docs w tym samym commicie. Docs jadą w pakiecie; rozjazd kod↔docs = agent dostaje złą mapę.
CZĘŚĆ A — ŁAŃCUCH ZMIANY W PLUGINIE (najważniejsze)
Najczęstsze źródło frustracji tej sesji: „zmieniłem kod, a nie działa". Prawie zawsze przyczyna: przerwany łańcuch. Zmiana w pluginie przechodzi przez PIĘĆ etapów. Pominięcie któregokolwiek = stara wersja w projekcie.
źródła (src) → build (dist) → publish (rejestr) → wciągnięcie (node_modules) → restart
Sztywna procedura zmiany w pluginie
cd ~/payload-cms/ipal-kit
# 1. ŹRÓDŁA — nanieś zmianę, ZWERYFIKUJ że jest
grep -c "<symbol-zmiany>" src/<ścieżka> # MUSI być >0
# 1b. DOCS — jeśli zmiana dotyka API/zachowania, ZAKTUALIZUJ docs/
# (nowa funkcja, zmiana sygnatury, nowe pole panelu, nowy adapter...).
# Docs jadą w pakiecie (files: dist, docs) — nieaktualne docs = agent
# dostaje złą mapę. Kod i docs publikuj RAZEM.
# 2. BUILD — zbuduj, ZWERYFIKUJ że dist ma zmianę
pnpm build
grep -c "<symbol-zmiany>" dist/<ścieżka> # MUSI być >0
# 3. COMMIT (PRZED version — inaczej "working directory not clean")
git add -A && git commit -m "opis"
# 4. VERSION + PUBLISH
npm version patch # czyste repo wymagane
npm publish
# 5. PUSH
git push && git push --tags
# 6. PROJEKT — wciągnij, ZWERYFIKUJ że node_modules ma zmianę
cd ~/<projekt>
pnpm add @intecion/ipal-kit@<nowa-wersja>
grep -c "<symbol-zmiany>" node_modules/@intecion/ipal-kit/dist/<ścieżka> # MUSI być >0
# 7. RESTART dev (Payload buduje adaptery/config przy starcie!)
pnpm dev
TRZY punkty kontrolne grep (nie pomijaj żadnego)
| Etap | Grep | Jeśli 0 |
|---|---|---|
| po edycji | src/... |
zmiana nie zapisana / zły plik |
| po build | dist/... |
build nie złapał / błąd typów |
| po pnpm add | node_modules/... |
projekt ma starą wersję |
Realny przykład (z tej sesji): buildSecurityHeaders is not a function.
Przyczyna: moduł istniał w src, ale NIE był wyeksportowany w src/index.ts
→ dist go nie miał → import w projekcie = undefined. Grep dist/index.js
pokazał 0. Naprawa: dodać eksport, przejść łańcuch od nowa.
Pułapki kolejności (realne błędy sesji)
npm versionprzed commitem → "Git working directory not clean". ZAWSZE commit przed version.npm publishbezpnpm build→ publikujesz STARY dist. ZAWSZE build przed publish, grep dist po buildzie.pnpm addprzy działającym dev → proces ma stary adapter w pamięci. Payload czyta email/config przy starcie. ZAWSZE restart po wciągnięciu.- Publikacja bez aktualizacji docs → agent (Antigravity) po
pnpm addczytanode_modules/@intecion/ipal-kit/docs/z NIEAKTUALNĄ mapą. Jeśli zmieniłeś API — docs w tym samym commicie.
CZĘŚĆ B — GREP JAKO NARZĘDZIE (jak weryfikować dobrze)
Grep był w tej sesji głównym narzędziem diagnozy. Ale trzeba go używać mądrze.
Reguła: grepuj TOKENY, nie całe frazy z kolejnością
Realny błąd: grep "env.sender, name: senderName" dał 0, choć kod był OK —
bo plik miał odwróconą kolejność kluczy (name: senderName, address: env.sender).
Obiekt JS ignoruje kolejność, ale grep nie.
# ŹLE — zależny od kolejności/formatowania:
grep -c "env.sender, name: senderName" plik.ts # 0 mimo poprawnego kodu
# DOBRZE — pojedynczy token, odporny:
grep -c "senderName" plik.ts # 3 ✓
Grepuj nazwę symbolu (funkcja, zmienna, eksport), nie całą linię z interpunkcją.
CZĘŚĆ C — DIAGNOSTYKA „KOD DOBRY, ZACHOWANIE ZŁE"
Gdy grep potwierdza kod, wersja nowa, a zachowanie stare — przejdź listę:
- Dev nie zrestartowany? Payload buduje adaptery/config przy starcie.
Ctrl+C +
pnpm dev. (Najczęstsza przyczyna.) - Zmiana zapisana w panelu? Endpointy czytają z BAZY, nie z pola na ekranie. Kliknij Save.
- Zdublowana zależność?
@payloadcms/uiw node_modules pluginu = dwie instancje = hooki bez kontekstu. Sprawdź:ls node_modules/@intecion/ipal-kit/node_modules/@payloadcms/uiJest? → peerDependency problem (patrz Część D). - Cache klienta? Np. klient pocztowy pokazuje zapamiętaną nazwę nadawcy mimo poprawnych nagłówków. Sprawdź surowe źródło (View Source), wyślij na inny adres.
- Import map nieaktualny? Custom komponenty Payload:
npx payload generate:importmap.
Realny przykład: MaskedField rzucał "Cannot destructure property 'config'".
Kod OK. Przyczyna: dublet @payloadcms/ui (plugin miał własną kopię) →
useField z jednej instancji nie widział kontekstu z drugiej. Naprawa w Część D.
CZĘŚĆ D — peerDependencies (dublety zależności)
Zasada: wszystko, co dostarcza PROJEKT, jest peerDependency w pluginie,
NIE dependency. Inaczej menedżer instaluje własną kopię dla pluginu → dublet
→ React/Payload context się rozjeżdża (dwie instancje nie widzą się nawzajem).
Peer (projekt dostarcza): payload, @payloadcms/ui, @payloadcms/next,
@payloadcms/plugin-*, react, react-dom, next.
Realny błąd: @payloadcms/ui był tylko w devDependencies (brak w peer) →
pnpm dołożył kopię pluginowi → MaskedField/TestEmailButton/CookieBanner
wszystkie się psuły (hooki bez kontekstu). Naprawa: dodać do peerDependencies,
opublikować, w projekcie rm -rf node_modules/@intecion/ipal-kit && pnpm add.
Weryfikacja braku dubletu:
ls node_modules/@intecion/ipal-kit/node_modules/@payloadcms/ui 2>/dev/null \
&& echo "DUBLET ✗" || echo "OK ✓"
CZĘŚĆ E — NOWY PROJEKT KLIENCKI (kolejność)
Pełne szczegóły: getting-started.md. Tu skrót kolejności.
- Szkielet Payload 3 + Next 16, pnpm, Node 22
.npmrc—legacy-peer-deps=true+ rejestr@intecionpnpm add @intecion/ipal-kit+ zależności peer- build script z
--webpack(Next 16 + Payload; Turbopack konfliktuje) - i18n.config.ts — jedno źródło locale
- payload.config.ts — ipalKit({...}),
email: mailAdapter() - Kolekcje/globale — wszystko localized/upload (nic na sztywno)
- lib/content.ts + lib/payload.ts — helpery, jedno źródło getCachedPayload
- proxy.ts (NIE middleware.ts) — routing locale, obsługa roota
- Bloki — dane przez enhanceProps, nie import lib (cykl)
- buildSlugField zamiast ręcznego slug
- getLocalizedSlugs zamiast zaszytej mapy ścieżek
- buildSecurityHeaders w next.config
- Test: root
/przekierowuje, formularz wysyła, panel działa
CZĘŚĆ F — EMAIL (SMTP vs Graph)
Pełne szczegóły: email.md. Decyzja transportu:
- Klient na M365/Exchange → Graph (SMTP AUTH na M365 często wyłączony)
- Klient z własnym SMTP / Gmail → SMTP
- Przełącznik: panel → Site Integrations → SMTP → Email Transport
- Dyspozytor:
email: mailAdapter()czyta wybór przy każdej wysyłce
Graph — checklist wdrożenia (Wasza strona, jednorazowo)
- Azure: App registration → tenantId, clientId, clientSecret
- Azure: Mail.Send APPLICATION permission + Grant admin consent
.envprojektu: GRAPH_TENANT_ID, GRAPH_CLIENT_ID, GRAPH_CLIENT_SECRET, GRAPH_SENDER- Panel: From Name (nazwa nadawcy), From Address (→ reply-to)
Realne pułapki Graph (wszystkie zdarzyły się w sesji)
| Błąd | Przyczyna | Naprawa |
|---|---|---|
ErrorSendAsDenied |
from ≠ sender |
from.address = GRAPH_SENDER, klient w replyTo |
| nazwa „Noreply" mimo panelu | Exchange nadpisuje / cache klienta | display name skrzynki / sprawdź nagłówki |
Insufficient privileges |
brak admin consent | Grant admin consent w Azure |
AADSTS1002012 |
zły scope | scope = .../.default, nie Mail.Send |
Zasada from/replyTo: from.address ZAWSZE = GRAPH_SENDER (wspólna skrzynka,
zero Send-As). Nazwa (from.name) z panelu — różna per projekt. Adres klienta
→ replyTo (odpowiedzi trafiają do klienta).
CZĘŚĆ G — CO NALEŻY DO PLUGINU, A CO DO PROJEKTU
Powtarzalne pytanie. Reguła: jeśli zależy od danych/domen konkretnego projektu → projekt. Jeśli identyczne wszędzie → plugin.
| Rzecz | Gdzie | Dlaczego |
|---|---|---|
| i18n, SEO meta, forms, consent, blog | plugin | uniwersalne |
| Powiadomienia (teksty wyników) | plugin | uniwersalne, per język z panelu |
| Zgoda RODO (enforcement) | plugin | uniwersalne, server-side |
| Nagłówki bezpieczeństwa (HSTS...) | plugin | identyczne wszędzie |
| Email (SMTP + Graph) | plugin | uniwersalne, konfiguracja z panelu/env |
| CSP | projekt | zależy od domen projektu |
| schema.org / JSON-LD | projekt | zależy od danych firmy |
| Breadcrumbs | projekt | render z danych routingu projektu |
| Dane rejestrowe firmy | projekt | różne per typ firmy |
CZĘŚĆ H — CHECKLIST PRZED „GOTOWE"
Nie mów „działa", dopóki:
pnpm build --webpackprzechodzi lokalnie (nie tylko dev)- root
/przekierowuje na locale (bez middleware.ts) - formularz wysyła (test przez panel: Send test)
- panel: wszystkie teksty/obrazy edytowalne (nic na sztywno)
- brak dubletu @payloadcms/ui (Część D)
- grep potwierdza wersję pluginu w node_modules
- sekrety w .env (nie w repo), maskowane w panelu
- brak plików middleware.ts, brak zaszytej mapy slugów
- strona 404 (not-found.tsx) — edytowalna, per język, link powrotu
- formularze z buildera w panelu (NIE własne hardkodowane)
- compliance: polityki, baner cookies, zgoda RODO w formularzach (patrz wymagania-prawne.md)