Przejdź do głównej zawartości

Migracja frameworka w Cursorze to etapowy program, który przenosi bazę kodu między frameworkami, bibliotekami lub wersjami głównymi, podczas gdy zespół dalej dostarcza funkcje. Plan Mode inwentaryzuje pracę, codemody załatwiają przypadki mechaniczne, a agent Cursora migruje resztę małymi partiami. Poprawność każdej partii potwierdzają zablokowane testy zachowania i zapadka (ang. ratchet) w CI, która nie pozwala rosnąć licznikom wzorców legacy.

Twoja aplikacja React ma 180 komponentów klasowych, 45 kontenerów podłączonych do Reduksa przez connect(), 30 tras w React Router 5 i ręcznie pisaną konfigurację Webpacka i musi dojść do hooków, Zustanda, aktualnego React Routera i Vite, ale nikt nie zgodzi się na trzy miesiące zamrożenia funkcji. Pojedyncze przekształcenia są dla agenta łatwe; trudne jest utrzymanie poprawności 180 z nich i niedopuszczenie, by baza kodu utknęła w połowie starego i w połowie nowego. Ta strona jest dla programistów, którzy prowadzą taką migrację w Cursorze, oraz dla tech leadów, którzy muszą udowodnić, że się skończyła.

  • Kolejność działań dla migracji w kilku osiach i zależność, która ją wyznacza.
  • Prompt do Plan Mode, który tworzy inwentaryzację z prawdziwych zliczeń, a nie z szacunków modelu.
  • Regułę projektu dla migracji i skrypt zapadki w CI, które nie pozwalają nowemu kodowi używać starych wzorców.
  • Pięć promptów do skopiowania: inwentaryzacja, przypięcie zachowania, partia komponentów, migracja store’u i skok routera.
  • Tabelę weryfikacji, która mówi, co potwierdza poprawność każdej partii i kto ją zatwierdza, oraz sposoby wyjścia z sześciu typowych awarii.

Zakres. Ta strona dotyczy przejść między frameworkami, bibliotekami i wersjami głównymi w obrębie jednej aplikacji. Kod bez testów najpierw potrzebuje testów charakteryzacyjnych i refaktoryzacji legacy. Migracja obejmująca wiele zespołów i serwisów to program; zobacz modernizację legacy z agentami. Zmiany schematu bazy opisują wzorce migracji baz danych. Tę samą pracę w innych narzędziach pokazują migracje z Claude Code i modernizacja bazy kodu z Codexem.

W jakiej kolejności prowadzić migrację w kilku osiach?

Dział zatytułowany „W jakiej kolejności prowadzić migrację w kilku osiach?”

Jedna oś na gałąź, jedna wersja główna na skok, a kolejność wyznaczają zależności. Dla przykładowego stosu poniższa kolejność działa, bo każdy krok zmniejsza następny. Ograniczenia wersji pochodzą z peerDependencies samych pakietów w npm, sprawdzonych 2026-09-26.

KolejnośćOśDlaczego w tym miejscuBramka przed następnym krokiem
1Przypięcie zachowaniaKażdy późniejszy krok potrzebuje testów opisujących zachowanie, nie implementacjęTesty przechodzą na starym kodzie i są zacommitowane
2Webpack na ViteSama konfiguracja; nie dotyka żadnego komponentu, więc może wejść przed jakąkolwiek zmianą kodu (przenieś konfigurację Webpacka na Vite)Build, serwer deweloperski i smoke testy E2E przechodzą na Vite
3Komponenty klasowe na funkcyjneHooki routera (teraz useHistory, po skoku na v6 useNavigate) i hooki store’ów wymagają komponentów funkcyjnych; znikają też string refs i legacy context, które React 19 usunąłLicznik komponentów klasowych w zapadce spada do 0, poza granicami błędów (error boundaries) wymienionymi w plan.md
4Redux na Zustand, po jednym store’zePodłączone kontenery są już funkcyjne, więc hook store’u wchodzi na miejsce connect()Moduł Reduksa każdego store’u jest usunięty
5React 18 na React 19React Router 8 deklaruje react >=19.2.7 jako peerCały zestaw i smoke testy przechodzą na React 19
6React Router 5 na 6, potem 7, potem 8react-router-dom-v5-compat (6.30.6) pozwala trasom v5 i v6 działać obok siebie; v7 wymaga Reacta 18 lub nowszegoKażdy skok jest mergowany osobno

Ostatni wiersz jest najważniejszy. Aktualny jest react-router 8.4.0, a react-router-dom kończy się na 7.18.4, więc skok na v8 przenosi też importy do pakietu react-router. React Router 8 wyszedł 2026-06-17, a to może być już po dacie odcięcia danych treningowych twojego modelu. Agent napisze wtedy z pamięci kod v6 lub v7, chyba że wskażesz mu przewodnik aktualizacji.

Jak krok po kroku przeprowadzić migrację frameworka w Cursorze?

Dział zatytułowany „Jak krok po kroku przeprowadzić migrację frameworka w Cursorze?”
  1. Zinwentaryzuj migrację w Plan Mode. Plan Mode najpierw tworzy szczegółowy plan implementacji, a dopiero potem pisze kod (dokumentacja Cursora, sprawdzona 2026-08-28), więc nic się nie zmienia, kiedy przeglądasz zakres. Proś o liczby z poleceń, bo szacunek modelu w stylu „około 95 prostych komponentów” to zgadywanie.

  2. Napisz regułę migracji i plik planu. Reguła mówi, jak ma wyglądać nowy kod. Plik planu przechowuje listę partii i postęp. Nie wpisuj postępu do reguły: reguła ładuje się w każdej sesji, a ręcznie poprawiany licznik szybko się dezaktualizuje.

  3. Dodaj zapadkę do CI. Skrypt liczy pliki, które wciąż używają każdego wzorca legacy, i kończy się błędem, gdy któryś licznik rośnie. To jedyna kontrola, która nie pozwala migracji cofać się.

  4. Przed każdą partią przypnij zachowanie. Przepisz testy powiązane z implementacją na starym kodzie, udowodnij, że tam przechodzą, i je zacommituj.

  5. Uruchom codemody na przypadkach mechanicznych. Codemod jest deterministyczny i przeglądasz go jako jedną regułę, więc jest tańszy niż 95 edycji agenta.

  6. Resztę migruj agentem partiami po pięć–dziesięć plików. Każda partia w nowym czacie, testy tylko do odczytu, każda partia mergowana z własnej gałęzi.

  7. Migruj stan po jednym store’ze, potem przeskakuj router po jednej wersji głównej. Usuwaj każdy most i każdą warstwę kompatybilności, gdy tylko przeniesie się ich ostatni konsument.

Przełącz agenta w Plan Mode i wklej ten prompt. Prosi o polecenie stojące za każdą liczbą, więc możesz je później uruchomić ponownie.

Czytasz plan, nie kod. Sprawdzasz trzy rzeczy: czy każda liczba ma polecenie, czy żadna partia nie dzieli plików z inną i czy złożone komponenty są wymienione z nazwy. To na nie pójdzie później twój czas na przegląd.

Zapisz regułę jako .cursor/rules/migration.mdc, w formacie .mdc, którego używają reguły projektu w Cursorze (dokumentacja reguł Cursora, stan na 2026-10-12; podaje też, że zwykły plik .md w .cursor/rules jest ignorowany), z alwaysApply: true, żeby działała w każdej sesji agenta. Potem sprawdź, czy się załadowała, kontrolą z linią kanarkową opisaną na stronie reguły projektu w Cursorze: uruchom prompt kanarkowy, znajdź w odpowiedzi Canary: migration-3b1e, a potem usuń linię kanarkową: na regule, której nie widziałeś w żadnej odpowiedzi, nie możesz polegać.

Klauzula o routerze odsyła do planu zamiast podawać cel, bo aplikacja zostaje na React Router 5 aż do ostatniego kroku. W v5, v6 i v7 BrowserRouter, Link i NavLink są tylko w react-router-dom, więc stale włączony zakaz importów z react-router-dom łamałaby każda partia komponentów. Ten zakaz należy do promptu skoku na v8 poniżej.

---
description: Framework migration in progress; target patterns for all new and edited code
alwaysApply: true
---
A migration is in progress. Plan and progress: docs/migration/plan.md.
New or edited code MUST use: function components with hooks and Zustand stores
in src/stores/.
New code MUST NOT use: class components, connect() or mapStateToProps.
Exception: keep error boundaries (componentDidCatch / getDerivedStateFromError)
as classes, or use react-error-boundary. React has no hook for them.
Router imports: follow the current hop in docs/migration/plan.md. Until the v8
hop, keep existing react-router-dom imports, and do not add withRouter, <Switch>
or <Redirect> to new code.
When you migrate a file:
- Do not edit test files unless the task says so. A failing test is a finding; report it.
- Do not change behaviour, props, exports or CSS class names.
- Run `npm run typecheck`, `npx vitest run <changed dirs>` and
`node scripts/migration-ratchet.mjs` before you say you are done.
Canary: migration-3b1e (remove after the load check)

Zapadka liczy pliki dla każdego wzorca legacy i porównuje je z zacommitowaną linią bazową. Licznik może spadać, nigdy rosnąć. Obniżaj linię bazową w tym samym pull requeście, który usuwa stary wzorzec, a postęp stanie się liczbą widoczną w przeglądzie. Linia bazowa i skrypt leżą jednak w tym samym pull requeście co kod, więc agent, który nie potrafi zmniejszyć licznika, może podnieść linię bazową albo zmienić skrypt i zostać na zielono. Obejmij docs/migration/baseline.json i scripts/migration-ratchet.mjs wpisem w CODEOWNERS z wymaganą akceptacją właściciela kodu, a każdy diff, który podnosi liczbę w linii bazowej, traktuj jako znalezisko.

// scripts/migration-ratchet.mjs. Run in CI and locally: node scripts/migration-ratchet.mjs
import { execFileSync } from 'node:child_process';
import { readFileSync } from 'node:fs';
const patterns = {
classComponents: String.raw`extends (React\.)?(Pure)?Component\b`,
reactRedux: String.raw`from ['"]react-redux['"]`,
withRouter: String.raw`\bwithRouter\b`,
routerDom: String.raw`from ['"]react-router-dom['"]`,
};
const include = ['ts', 'tsx', 'js', 'jsx'].map((ext) => `--include=*.${ext}`);
const baseline = JSON.parse(readFileSync('docs/migration/baseline.json', 'utf8'));
let failed = false;
for (const [name, re] of Object.entries(patterns)) {
let out = '';
try {
out = execFileSync('grep', ['-rlE', re, 'src', ...include]).toString().trim();
} catch (err) {
if (err.status !== 1) throw err; // grep exits 1 when nothing matches
}
const count = out ? out.split('\n').length : 0;
if (!(name in baseline)) {
console.log(`${name}: not ratcheted yet (no baseline entry)`);
continue;
}
const allowed = baseline[name];
console.log(`${name}: ${count} files (baseline ${allowed})`);
if (count > allowed) failed = true;
}
process.exit(failed ? 1 : 0);

docs/migration/baseline.json zaczyna się od liczb plików, które zapadka wypisze przy pierwszym uruchomieniu: uruchom ją raz i wklej te liczby. Zapadka liczy pliki, a nie komponenty, więc plik z dwiema klasami liczy się raz i liczby mogą być niższe niż w inwentaryzacji. Przykładowa linia bazowa (liczby poglądowe) wygląda tak: {"classComponents": 172, "reactRedux": 46, "withRouter": 22}. reactRedux liczy pliki importujące z react-redux, a nie wywołania o nazwie connect(, więc nowe socket.connect() czy db.connect() go nie podbije; licznik obejmuje też plik z głównym <Provider>, dlatego startuje o jeden wyżej niż 45 kontenerów. classComponents kończy na liczbie granic błędów (error boundaries), na przykład 3, a nie na 0, bo React wciąż nie ma hooka odpowiadającego componentDidCatch i getDerivedStateFromError; wypisz te pliki w plan.md. Wzorzec bez wpisu w linii bazowej jest raportowany, ale nie egzekwowany. Nie dodawaj routerDom aż do skoku na v8, bo wcześniej nowe komponenty zgodnie z planem importują Link z react-router-dom; pull request skoku na v8 dodaje "routerDom": 0. Dodaj skrypt jako wymagany krok CI obok sprawdzania typów i testów; workflow pokazuje strona CI/CD z Cursorem.

Krok z Webpacka na Vite to praca konfiguracyjna i nie dotyka żadnego komponentu, więc idzie jako drugi, zaraz po przypięciu testów. Poproś agenta o przeniesienie resolve.alias, devServer.proxy i zmiennych środowiskowych (domyślnie Vite udostępnia przez import.meta.env tylko zmienne z prefiksem VITE_). Zachowaj konfigurację Webpacka, dopóki build Vite nie przejdzie smoke testów, i usuń ją w osobnym commicie.

Testy pisane w Enzyme albo oparte na .instance() i .state() testują klasę, a nie zachowanie, i psują się przy pierwszym komponencie funkcyjnym. Jeśli agent przepisze je w tej samej zmianie co komponent, testy niczego nie dowodzą. Dlatego przepisz je najpierw, na starym kodzie.

Przejrzyj testy, zacommituj je jako osobny commit, otaguj go i wypchnij tag, na przykład git tag batch-7-pinned && git push origin batch-7-pinned. Bez wypchnięcia CI nie zna tego refa i kontrola diffu testów opisana niżej kończy się błędem, zamiast cokolwiek dowieść; checkout w CI też musi pobrać tag, na przykład przez fetch-depth: 0 w actions/checkout. Od tej chwili testy są kontraktem partii. Dla kodu bez żadnych testów użyj technik approval i golden master z testów charakteryzacyjnych.

Dla komponentów klasowych, które tylko renderują, react-codemod od zespołu Reacta ma transformację pure-component. Zamienia klasy, które mają wyłącznie metodę render, pola statyczne i propsy, a nie mają refów, i pomija wszystko inne.

Okno terminala
# Terminal, katalog główny repozytorium. react-codemod 5.4.4 w npm, sprawdzone 2026-09-26.
npx react-codemod pure-component src/components --parser tsx --dry --print # podgląd
npx react-codemod pure-component src/components --parser tsx # zastosowanie
npm run typecheck && npx vitest run && node scripts/migration-ratchet.mjs

--parser tsx pomija pytanie o dialekt, ale polecenie nadal zadaje dwa pytania o styl (funkcje strzałkowe, destrukturyzacja propsów), więc nie działa bez nadzoru. Uruchomienie bez --dry odmawia startu, gdy w repozytorium są niezacommitowane zmiany, więc najpierw zacommituj przypięte testy. Sprawdź, czy wynik z --dry zachowuje typ Props w każdym przekonwertowanym komponencie.

Przeglądasz jedną regułę transformacji zamiast 95 diffów agenta, a wynik jest identyczny przy każdym uruchomieniu. Zacommituj wynik codemoda osobno od pracy agenta, żeby recenzent widział, które zmiany były mechaniczne.

Każdą partię zaczynaj w nowym czacie, żeby agent nie był zakotwiczony w decyzjach z poprzedniej. Wskaż pliki przez @, żeby kontekst zawierał tylko to, czego partia potrzebuje.

Ostatnia linia promptu to twoja lista do przeglądu. Czytasz mapowania, które nie były jeden do jednego, a nie cały diff. Żeby prowadzić kilka partii naraz, daj każdej własny worktree; worktree w Cursorze pozwalają agentowi pracować w izolowanych checkoutach Gita (dokumentacja Cursora, sprawdzona 2026-08-28), a okno Agents pokazuje, jak je nadzorować. Partie muszą być rozłączne, inaczej merge skończy się konfliktami we wspólnych plikach store’ów.

Stan to najbardziej ryzykowna oś, bo jeden store zasila wiele komponentów. Przenoś jeden store na gałąź, z jednokierunkowym mostem na czas, gdy stare i nowe komponenty działają obok siebie.

Krok 3 używa testów reducera jako wyroczni dla nowego store’u: ta sama sekwencja akcji musi dać ten sam stan. Gdy lista z kroku 5 jest pusta, usuń most i moduł Reduksa w jednym commicie i obniż linię bazową reactRedux.

Każdy skok rób na osobnej gałęzi: v5 na v6 przez pakiet kompatybilności, potem v6 na v7, potem v7 na v8. Nie pozwalaj agentowi pracować z pamięci nad wersją główną, która może być nowsza niż jego dane treningowe.

Pakiet react-router 8.4.0 zawiera przewodniki aktualizacji w docs/upgrading/, a ten dla skoku z v7 na v8 to docs/upgrading/v7.md („Upgrading from v7”; sprawdzone przez npm pack react-router@8.4.0 2026-10-02), więc agent czyta przewodnik dla zainstalowanej wersji bez dodatkowego kroku z kontekstem. Ten przewodnik mówi też, że RouterProvider trafia do react-router/dom, a nie do react-router. Aktualną dokumentację innych bibliotek zamiast pamięci modelu możesz też dać agentowi przez serwer MCP Context7 (@upstash/context7-mcp, 4.1.1 w npm, sprawdzone 2026-09-26); jak go dodać, pokazuje konfiguracja MCP w Cursorze. Przy skoku z v5 na v6 zainstaluj react-router-dom-v5-compat i przenoś trasy pojedynczo wewnątrz działającej aplikacji v5, a pakiet usuń, gdy nie zostanie żadna trasa v5.

Jak udowodnić partię migracji bez czytania każdej linii?

Dział zatytułowany „Jak udowodnić partię migracji bez czytania każdej linii?”

Poprawność każdej partii potwierdzają kontrole, których agent nie może edytować, a człowiek czyta tylko te miejsca, gdzie mapowanie nie było mechaniczne.

KontrolaCzego dowodziKto za nią odpowiada
Testy zachowania przechodzą na starym kodzie przed partiąTesty opisują zachowanie, a nie klasęProgramista prowadzący partię
git diff --exit-code batch-7-pinned -- '*.test.ts' '*.test.tsx' po partiiAgent nie zmienił kontraktu, żeby testy przeszłyProgramista lokalnie, CI na pull requeście
npm run typecheck, lint, npx vitest runPartia jest spójna z resztą bazy koduCI, jako wymagane kontrole
node scripts/migration-ratchet.mjsŻaden licznik wzorców legacy nie wzrósłCI, jako wymagana kontrola
Smoke testy Playwright z toHaveScreenshot() na najważniejszych trasachStrony renderują się i nawigują tak jak wcześniejCI; trasy wybiera tech lead
Bugbot na pull requeścieDrugi czytelnik dla błędów, których nie złapały testy (Bugbot)Recenzent segreguje zgłoszenia
Czytanie przez człowiekaMosty, kształt store’ów, zabezpieczenia tras (route guards) i przekierowania auth oraz lista mapowań agenta, które nie były jeden do jednegoTech lead zatwierdza każdy store i każdy skok routera

Wzorcowe zrzuty ekranu do smoke testów zrób na starym buildzie przed partią i porównuj z nimi po niej; konfigurację opisują testy E2E. Dlaczego człowiek czyta mosty i przekierowania auth, a nie ciała komponentów, uzasadnia strona czytanie dowodów zamiast kodu. Całą partię wycofujesz, cofając jej commit scalający (merge commit); pojedynczą turę agenta w partii cofasz checkpointem, jak opisują checkpointy i gałęzie.

Co psuje się w migracji frameworka w Cursorze i jak z tego wyjść?

Dział zatytułowany „Co psuje się w migracji frameworka w Cursorze i jak z tego wyjść?”

Testy są zielone, bo agent je przepisał. Komponent się zmienił, a razem z nim jego test, w tym samym diffie. Wyjście: przywróć testy z przypiętego taga poleceniem git checkout batch-7-pinned -- '*.test.ts' '*.test.tsx', uruchom prompt partii ponownie i zrób z kontroli diffu testów wymagany krok CI. Ochrona wyroczni pokazuje hook, który blokuje edycję testów podczas implementacji.

Nowy kod wciąż używa starego wzorca. Agent kopiuje to, co widzi najczęściej, a przy 25% migracji to wciąż stary wzorzec. Wyjście: sprawdź, czy reguła leży w .cursor/rules/ jako .mdc z alwaysApply: true a potem dodaj z powrotem linię kanarkową i uruchom ponownie prompt kanarkowy ze strony reguły projektu w Cursorze; brak kanarka w odpowiedzi oznacza, że reguła się nie załadowała. Zapadka łapie to, co przepuści reguła, i dlatego istnieją obie.

Agent pisze API z niewłaściwej wersji głównej. Tworzy kod routingu v6 w bazie na v8 albo wzorzec Zustanda ze starszej wersji głównej. Wyjście: niech „najpierw przeczytaj zainstalowany CHANGELOG i podsumuj zmiany łamiące” będzie pierwszym krokiem każdego skoku, jak w prompcie routera, i dodaj zapytanie do Context7.

Most Redux–Zustand powoduje nadmiarowe rendery albo pętle. Synchronizacja w obie strony zapisuje tam i z powrotem przy każdej zmianie. Wyjście: most tylko w jedną stronę, liczba mostów w pliku planu i usuwanie każdego, gdy tylko zmigruje się jego ostatni konsument z Reduksa.

Migracja utyka na złożonym ogonie. Ostatnie komponenty mają HOC-i, refy i render propsy, i nikt się za nie nie bierze. Wyjście: zapadka pokazuje ogon jako liczbę w każdym pull requeście. Każdy złożony komponent dostaje własną sesję Plan Mode i własną gałąź, a prace nad nimi planujesz, zamiast zostawiać je na wolną chwilę.

Partia jest za duża, żeby ją zweryfikować. Partia z 40 plików daje diff i listę błędów, których nikt nie ogarnie, a agent traci orientację we własnych zmianach. Wyjście: wycofaj partię, podziel ją na pięć–dziesięć plików według listy zależności z planu i każdą część zacznij w nowym czacie.