Read examples/complex/package.json when scaffolding pinned Strapi releases so driver versions stay in sync with the migration fixture. Document that examples/complex is CI test infrastructure (possible future move under tests/migration/).
13 KiB
Migration test fixture (examples/complex)
Canonical Strapi app for v4→v5 migration integration tests and benchmarks. It ships rich schemas (relations, dynamic zones, i18n, draft/publish, stress cases), seed scripts, validate-migration.js, and database tooling used by CI and local workflows.
Location: this directory stays at
examples/complex(Yarn workspace namecomplex) for historical reasons. It is test infrastructure, not a casual demo likegetstarted. Test orchestration lives intests/migration/. Future: we may relocate the app undertests/migration/(e.g.tests/migration/fixture/) so ownership and CI path filters are clearer; until then, treat changes here as changes to the migration-test contract.
Content Types
The project includes 8 content types covering the feature space v4→v5 migrations touch.
Baseline feature combinations
basic— no draft/publish, no i18nbasic-dp— draft/publishbasic-dp-i18n— draft/publish + i18nrelation— relations + morphs + components + DZrelation-dp— + draft/publishrelation-dp-i18n— + i18n
Anti-pattern stress schemas
Intentionally unrealistic; each targets a specific migration code path.
hc-m2m-source/hc-m2m-target— high-cardinality many-to-many. At--multiplier 100produces ~2K sources × ~2K targets × 10 fanout = 20K+ join rows, crossing the 1000-row chunk boundary incopyRelationTableRows.
Supported databases
- PostgreSQL 16 — via podman/docker container on
${POSTGRES_PORT:-5432} - MySQL 8 — via container on
${MYSQL_PORT:-3306} - MariaDB 11 — via container on
${MARIADB_PORT:-3307} - SQLite — file-based at
../complex-v4/.tmp/data.db(override withSQLITE_DATABASE_FILENAME)
Container runtime is auto-detected in this order: podman compose → podman-compose → docker compose → docker-compose. Override with STRAPI_BENCH_RUNTIME=podman|docker on mixed-install hosts.
Migration Testing Workflow
This fixture includes tools for testing migrations between Strapi v4 and v5 by creating an isolated v4 project and managing database snapshots. It ships its own docker-compose.dev.yml so database containers are independent of the monorepo root.
Setup
-
Create/Update the external v4 project:
yarn setup:v4This creates a Strapi v4 project outside the monorepo (default: a sibling directory named
complex-v4). You can override the location viaV4_OUTSIDE_DIR. -
Install v4 deps (one-time):
cd <path-printed-by-setup> yarn install -
Configure the v4 project (only if you need custom DB creds):
cp .env.example .env # Edit .env as needed -
Start the v4 project:
yarn develop:postgres # or :mysql, :mariadb, :sqlite
Database Management
The same per-command pattern applies to postgres, mysql, mariadb, and sqlite:
yarn db:start:<db> # start the DB container (no-op for sqlite)
yarn db:stop:<db> # stop the DB container (no-op for sqlite)
yarn db:snapshot:<db> <name> # snapshot current DB state
yarn db:restore:<db> <name> # restore DB from a named snapshot
yarn db:wipe:<db> # drop + recreate (clean slate)
yarn db:check:<db> # print table row counts (runs ANALYZE first for fresh stats)
Snapshots live in snapshots/ and are gitignored:
- PostgreSQL:
snapshots/postgres-<name>.sql - MySQL:
snapshots/mysql-<name>.sql - MariaDB:
snapshots/mariadb-<name>.sql - SQLite:
snapshots/sqlite-<name>.db(raw file copy; fast)
MariaDB (MySQL-compatible; Strapi uses DATABASE_CLIENT=mysql)
Same UX as MySQL; Compose maps host MARIADB_PORT → container 3306 (default host port 3307 so it can run beside MySQL on 3306).
yarn db:start:mariadb
yarn db:stop:mariadb
yarn db:snapshot:mariadb <name>
yarn db:restore:mariadb <name>
yarn db:wipe:mariadb
yarn db:check:mariadb
Typical Migration Testing Workflow
-
Setup v4 project (if not already done):
yarn setup:v4 -
Wipe the database (ensures v4 format, no v5 schema):
yarn db:wipe:postgres -
Start v4 project (in separate terminal, use the path printed by setup):
cd <path-printed-by-setup> yarn develop:postgres(v4 will automatically start its database if needed)
-
Seed test data in the v4 project:
yarn seed -
Create snapshot:
cd examples/complex yarn db:snapshot:postgres mybackup -
Stop v4 server (Ctrl+C in v4 terminal)
-
Start v5 server with the same database:
yarn develop:postgresMigrations will run automatically on startup.
-
Validate migration (no HTTP server needed):
yarn test:migrationThis includes a document_id backfill check (internal migration
5.0.0-02-created-document-id): every table with adocumentIdattribute, including uploadfiles, must have noNULLdocument_idafter migrations. The v4 seed always creates media files so thefilestable is populated before the v4→v5 upgrade. -
Test and fix bugs as needed
-
Restore snapshot to reset database:
yarn db:restore:postgres mybackup -
Repeat from step 7 to test fixes
Note: The database container stays running even after stopping Strapi, so you can inspect the database or run multiple tests without restarting the container. Manual workflows use Compose project name strapi_complex so they do not collide with other containers (automated yarn test:migrations uses strapi_migration_v5 by default).
Automated migration test (monorepo)
From the repository root (quick smoke, ~1–2 min on a warm tree):
yarn test:migrations:smoke
Full flow after yarn build (or --skip-build when dist is current):
yarn test:migrations --initial 4.26.0 --database sqlite --skip-build
You must pass --initial <semver> (unless you use --scenario): baseline npm version (v4 or v5). Optional --via adds intermediate published Strapi boots. The last step is always workspace (this monorepo). There is no final Strapi version flag.
This wipes examples/complex/.migration-v5/, scaffolds the baseline, seeds, optionally runs --via pinned releases, then validates on the same database. --initial 4.x = v4 scaffold + seed.js; --initial 5.x = pinned v5 + seed-v5.js. Compose project strapi_migration_v5 by default. Instant dry-run: yarn test:migrations:plan --initial 4.26.0.
CI: tests/migration/README.md (migration_v5 job, Node 20, latest v4 from @strapi/strapi@legacy).
Options:
--initial <semver>— required without--scenario(4.x = v4 scaffold; 5.x = pinned v5 + v5 seed)--via <semver>/-v— repeatable pinned boots before workspace (full-ladderwhen any--via;full-v5-originfor 5.x baseline without--via;full-v4-originfor 4.x)--scenario <path>— JSON scenario (overrides CLI flags)--validators— e.g.full-v4-origin,full-v5-origin,full-ladder(validators.js)--initial-node/--workspace-node— optional Node major checks per phase--database sqlite(default locally) |postgres|mysql|mariadb|sqlite--multiplier N,--build,--skip-build
Optional env: tests/migration/v5/.env.example. Strapi v4 scaffold targets Node ≤ 20.
Pass flags after the script name (avoid an extra -- before --database or Yarn may not forward options).
Examples:
yarn test:migrations --initial 4.26.0 --via 5.30.0 --database sqlite— seetests/migration/scenarios/v4-via-5-30-0-to-head.jsonyarn test:migrations --initial 5.7.0 --database sqlite— seetests/migration/scenarios/v5-5-7-to-workspace.jsonyarn test:migrations --initial-node 20— fail fast if Node major ≠ 20yarn test:migrations --scenario tests/migration/scenarios/v4-to-head.json
Checkpoints: tests/migration/CHECKPOINTS.md.
Migration performance benchmark
For reviewing PRs that touch v4→v5 migration code, this project ships a benchmark harness that captures per-migration timings and produces baseline-vs-candidate reports across any combination of databases and multipliers.
Quick start
# One-time setup
yarn setup:v4
cd ../../complex-v4 && yarn install && cd -
# Seed data (one snapshot per DB × multiplier, kept in snapshots/)
yarn bench:seed --db postgres --multiplier 100
# Capture baseline — on develop (or whatever you're comparing against)
yarn bench:run --db postgres --multiplier 100 --label baseline
# Capture candidate — git checkout or cherry-pick the PR, rebuild, then:
yarn workspace @strapi/database run build
yarn workspace @strapi/core run build
yarn bench:run --db postgres --multiplier 100 --label pr-xxxxx
# Generate matrix comparison report
yarn bench:compare --baseline baseline --candidate pr-xxxxx
Reports land in results/:
compare-<timestamp>.md— clipboard-ready markdown, also echoed to stdoutcompare-<timestamp>.html— self-contained single-file HTML with inline SVG charts, sortable tables, and light/dark theme support viaprefers-color-scheme
Bench subcommands
yarn bench:seed --db <db> --multiplier <n>— wipe + boot v4 + seed + snapshot. One-time per (db, multiplier). Runtime scales with multiplier; atm=100expect ~8–10 min per DB depending on hardware.yarn bench:run --db <db> --multiplier <n> --label <label>— restore snapshot + spawn Strapi v5 in migrate-then-exit mode + capture per-migration timings via a Node--requirepreload that subscribes to Umzug's nativemigrating/migratedevents. Emits a result JSON toresults/<db>-<label>-<timestamp>.json. Typically ~15s to several minutes depending on dataset size.yarn bench:compare --baseline <label> --candidate <label>— render a multiplier × database matrix plus per-cell per-migration breakdowns, to both markdown and self-contained HTML. Accepts partial data (missing cells render as—).yarn bench:suite --multiplier <n> [--dbs postgres,mysql,mariadb,sqlite]— chainedbench:runacross DBs for a given multiplier. Runs under whatever Strapi version is currently checked out; label via--label.
Workflow for reviewing a migration-perf PR
- On
develop, seed once per (db, multiplier) you want data for. - Run baselines:
yarn bench:run --db <db> --multiplier <n> --label baseline. - Cherry-pick the PR's commits (or
gh pr checkout), rebuild@strapi/databaseand@strapi/core. - Run candidates with the same
(db, multiplier)combinations,--label pr-xxxxx. - Reset cherry-pick + rebuild.
yarn bench:compare --baseline baseline --candidate pr-xxxxx— paste the markdown into a PR comment; attach the zipped HTML as an upload (GitHub comments don't render.htmldirectly).
Snapshots are reused across bench:run invocations — you only re-seed when the schema itself changes.
Benchmark-specific env vars
STRAPI_BENCH_HOOK_OUTPUT=<path>— enables the timing preload (set automatically bybench.js, exposed for debugging). The hook self-disables when this isn't set, so the--requirecan safely live in other dev configs.STRAPI_BENCH_HOOK_DEBUG=1— verbose preload output (migration attach/record events to stderr).STRAPI_BENCH_RUNTIME=podman|docker— override the auto-detected container runtime.SEED_CONCURRENCY=<n>— how many entity-creation tasks run in parallel duringbench:seed/seed. Default5, which stays under Strapi v4's default knex pool of{min: 2, max: 10}. Tune up only if you've also raised the pool max.
Development Commands
Simplified Database Commands
The easiest way to start Strapi with a specific database:
yarn develop:postgres # PostgreSQL container + Strapi dev server
yarn develop:mysql # MySQL container + Strapi dev server
yarn develop:mariadb # MariaDB container + Strapi dev server
yarn develop:sqlite # SQLite file (no container) + Strapi dev server
These commands:
- ✅ Automatically start the database container if it's not already running (no-op for sqlite)
- ✅ Configure Strapi to use the specified database (no manual config needed)
- ✅ Start the Strapi development server
- ✅ Keep the database container running when you press Ctrl+C (only Strapi stops)
Note: Default ports:
- PostgreSQL:
5432(override withPOSTGRES_PORT) - MySQL:
3306(override withMYSQL_PORT) - MariaDB:
3307(override withMARIADB_PORT; maps to container3306)
Set the override env var if you have a local DB already bound to the default port:
POSTGRES_PORT=5433 yarn develop:postgres
Standard Strapi Commands
yarn develop— Start development server (defaults to PostgreSQL; requires a running DB)yarn build— Build for productionyarn start— Start production serveryarn strapi— Run Strapi CLI commands
V5 Seeding (Large Dataset)
Use the v5 seeder in this project to generate a large dataset for homepage perf testing:
yarn seed:v5
You can scale the volume with a multiplier:
yarn seed:v5 -- --multiplier 20
Or:
SEED_MULTIPLIER=20 yarn seed:v5