Skip to content

chore: allow phpunit ^13.0 - #8537

Merged
soyuka merged 1 commit into
api-platform:5.0from
mislavjakopovic:chore/allow-phpunit-13
Sep 22, 2026
Merged

soyuka merged 1 commit into
api-platform:5.0from
mislavjakopovic:chore/allow-phpunit-13

Conversation

@mislavjakopovic

@mislavjakopovic mislavjakopovic commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor
Q A
Branch? 5.0
Tickets none
PRs Depends on #8557, #8558 and #8559 (merge first)
License MIT
Doc PR n/a

Since #7887 api-platform/test split requires phpunit/phpunit ^11.5 || ^12.2, it cannot be installed next to PHPUnit 13, although the classes run unchanged on it.

Before the split those classes shipped inside api-platform/symfony, which lists PHPUnit only in require-dev, a section Composer never enforces for dependencies, so PHPUnit 13 worked - the extraction moved that constraint into require, and it now blocks "bleeding edge" migration to API Platform 5.

This PR widens the constraint to ^11.5 || ^12.2 || ^13.0 in the root composer.json and in every component manifest that pins it, same shape as c0886c1 (^11.5) and #7623 (^12.2).

The serializer component's sebastian/exporter pin (#7663) gains ^8.2 the same way, since PHPUnit 13 requires exporter 8.

After initial commit on this pull request and failed pipelines, the rabbit hole went moderately deep.

In the end several preparation PRs were needed for the suite to stay green under 11, 12 and 13:

PHPUnit 13 requires PHP 8.4.1, so the 8.2 and 8.3 jobs will keep resolving 11.5 and 12, while the 8.4 and 8.5 jobs move to PHPUnit 13 without a workflow change.

The separate pull requests run against the currently supported PHPUnit versions, showing that these changes should remain backwards compatible.

For that very reason I've cherry-picked their commits into this pull request as well, so CI on here runs the same changes together on PHPUnit 13.3.x.

I've chosen this separation into several PRs for cleaner readability and easier reviews from maintainers, hope that's okay.

I would suggest keeping this pull request as a draft until all related preparation PRs are merged; then I will adjust description of it and rebase branch to the dependencies bump commit alone.

One last note which might be of concern - the root conflict entry <9.5, phpunit.baseline.xml and docs/composer.json (^10, only reachable via require-dev since #8527) are left untouched.

@mislavjakopovic
mislavjakopovic marked this pull request as draft September 16, 2026 13:45
vincentchalamon added a commit to vincentchalamon/bike-trip-planner that referenced this pull request Sep 21, 2026
… surfaced

Three behaviours change on the wire, all accepted rather than worked around
since nothing is deployed yet:

- the JSON-LD content type loses its "; charset=utf-8" suffix (27 assertions);
- an operation declaring an output DTO now reports the resource short name in
  @type ("Stage", not "StageResponse"); @context still points at the DTO. No
  client keys on the value — it is an untyped string in core/schema.d.ts;
- responses carry the LDP Allow / Accept-Post headers.

ApiTestCase::$alwaysBootKernel flipped from null (boot on every createClient) to
false (reuse a booted kernel). Reusing it keeps the Doctrine connection open
across tests, so Foundry's per-test migrate reset has to terminate that live
connection to drop the database — which kills the migration run itself and
leaves every table missing. There was no shared base class to fix this in, so
this adds App\Tests\ApiTestCase; the 45 test files move onto it in the same pass
as the api-platform/test namespace split.

PHPUnit stays at ^12.5 in api/ only: api-platform/test caps it at ^12.2 even
though its classes run unchanged on 13 (api-platform/core#8537). provisioner/ is
an independent Composer project with no API Platform dependency and keeps 13.
See ADR-070.

Flipping ignoreIndirectDeprecations to false is what made the rest visible —
vendor deprecations were simply not being counted before:

- doctrine/orm wants SortDirection instead of "ASC"/"DESC", in one #[ORM\OrderBy]
  and three QueryBuilder::orderBy() calls;
- zenstruck/foundry 2.9 deprecated the Factories/ResetDatabase traits in favour
  of the PHPUnit extension (already registered) plus #[ResetDatabase]: 62 files;
- framework.profiler.collect_serializer_data and
  doctrine.orm.controller_resolver.auto_mapping are removed in the next majors
  and already equalled the only accepted value.

Dropping collect_serializer_data also dropped the only framework.profiler
declaration in dev, which silently disabled the profiler and every service it
brings (PHPStan caught it via a missing mailer.message_logger_listener). The key
is restored as `profiler: null` with a comment saying it is load-bearing.

Rector 2.6 removes the version-numbered PHPUnit sets; withComposerBased(phpunit)
was already configured and derives them from composer.lock.

The two undocumented pins — rector/rector ~2.5.9 and an exact playwright-core
override — are lifted; nothing broke, which is the usual fate of a pin nobody
can explain.
vincentchalamon added a commit to vincentchalamon/bike-trip-planner that referenced this pull request Sep 21, 2026
…cure 1.0) (#1298)

* ci: pin every floating image and close the Dependabot gaps

Four images ran with no tag at all (hadolint, markdownlint-cli2, alpine,
oskarstark/php-cs-fixer-ga), so two runs of the same commit could use different
tool versions — and the Makefile drifted from CI, which pins hadolint via the
action. All are now pinned.

The third-party `esoledad/php:8.5` backing all ten PHP jobs is digest-pinned
rather than replaced: swapping the job container is a CI change no local run can
validate, so it stays a follow-up. Freezing the digest is the part that is safe
to do now.

docker/bake-action moves to v7.4.0 for its workflow-command injection fix.

Dependabot was watching neither the compose files (the `docker` ecosystem only
reads Dockerfiles, which is how traefik and cloudflared aged unnoticed), nor
`provisioner/`, `core/`, `mobile/`, the root npm manifest holding the security
overrides, nor the vendor-bin namespaces where php-cs-fixer lives.

* build(deps): bump the ops images

Traefik v3.3 -> v3.7.13 closes five security advisories; cloudflared was over a
year behind at 2025.7.0.

The provisioner Dockerfile was the only one pinned by tag alone, so Dependabot
could not see it while php and pwa were tracked: php and composer are now
digest-pinned, and install-php-extensions goes 2.11.12 -> 2.11.27 with its
checksum recomputed.

Side stacks: glitchtip 6.1.6 -> 6.2.6, its redis:7-alpine aligned onto the
redis:8-alpine the app stack already runs, and uptime-kuma 1.23.16 -> 2.5.5.

Note that uptime-kuma 1.x -> 2.x is a major with its own data migration; it is
deployed out of band (.docker/uptime-kuma/README.md), so the bump lands here but
the migration is a manual step.

* feat(mercure)!: speak the Mercure 1.0 protocol end to end

The hub is embedded in the FrankenPHP binary (ADR-037) and upstream pins
dunglas/mercure/caddy 0.24.2, which only speaks 0.x. A frankenphp_builder stage
now recompiles it at v1.0.0. Two facts make that cheap: mercure/caddy v1.0.0
wants Caddy v2.11.4, exactly what the upstream image embeds, and frankenphp is
built from the sources vendored in the builder image so it links against its own
libphp. The only obstacle was the toolchain — the module declares go 1.27 while
the builder ships Go 1.26 under GOTOOLCHAIN=local — which GOTOOLCHAIN=auto
resolves. Runtime and builder images must now be bumped together.

Protocol 1.0 is a breaking change on three axes, each of which fails silently
(the stream simply never delivers):

- subscriptions move from ?topic= to ?match=, verified against a live hub:
  ?match= returns 200 text/event-stream, ?topic= returns 400.
- tokens become RFC 9068 access tokens (typ: at+jwt, authorization_details).
  MercureTokenIssuer is rewritten on top of symfony/mercure 0.8 instead of
  hand-rolling the claim with lcobucci, which also removes the risk of it
  drifting from the tokens the bundle mints when publishing.
- the subscription API gains a match-type segment: /subscriptions/exact/<topic>
  answers 401 (route present, token required) while the 0.x path is now 404.

The cookie is renamed to __Secure-mercure_access_token. Its Secure flag is
load-bearing, not defensive: browsers reject the prefix without it.

aud is pinned on both sides rather than derived, because the backend publishes
through the internal MERCURE_URL while browsers subscribe on the public one, and
an unpinned hub expects a different audience per origin.

compose.dev.yaml passed the "demo" directive, renamed "playground" in 1.0 — and
an unknown directive is now fatal rather than a silent no-op, so this alone
stopped the dev hub from booting.

The mocked E2E suite fakes SSE with a CustomEvent and never touches the hub, so
it stays green whatever the protocol does. The integration smoke test now asserts
?match= and the cookie name explicitly.

* feat(api)!: upgrade to API Platform 5.0 and purge the deprecations it surfaced

Three behaviours change on the wire, all accepted rather than worked around
since nothing is deployed yet:

- the JSON-LD content type loses its "; charset=utf-8" suffix (27 assertions);
- an operation declaring an output DTO now reports the resource short name in
  @type ("Stage", not "StageResponse"); @context still points at the DTO. No
  client keys on the value — it is an untyped string in core/schema.d.ts;
- responses carry the LDP Allow / Accept-Post headers.

ApiTestCase::$alwaysBootKernel flipped from null (boot on every createClient) to
false (reuse a booted kernel). Reusing it keeps the Doctrine connection open
across tests, so Foundry's per-test migrate reset has to terminate that live
connection to drop the database — which kills the migration run itself and
leaves every table missing. There was no shared base class to fix this in, so
this adds App\Tests\ApiTestCase; the 45 test files move onto it in the same pass
as the api-platform/test namespace split.

PHPUnit stays at ^12.5 in api/ only: api-platform/test caps it at ^12.2 even
though its classes run unchanged on 13 (api-platform/core#8537). provisioner/ is
an independent Composer project with no API Platform dependency and keeps 13.
See ADR-070.

Flipping ignoreIndirectDeprecations to false is what made the rest visible —
vendor deprecations were simply not being counted before:

- doctrine/orm wants SortDirection instead of "ASC"/"DESC", in one #[ORM\OrderBy]
  and three QueryBuilder::orderBy() calls;
- zenstruck/foundry 2.9 deprecated the Factories/ResetDatabase traits in favour
  of the PHPUnit extension (already registered) plus #[ResetDatabase]: 62 files;
- framework.profiler.collect_serializer_data and
  doctrine.orm.controller_resolver.auto_mapping are removed in the next majors
  and already equalled the only accepted value.

Dropping collect_serializer_data also dropped the only framework.profiler
declaration in dev, which silently disabled the profiler and every service it
brings (PHPStan caught it via a missing mailer.message_logger_listener). The key
is restored as `profiler: null` with a comment saying it is load-bearing.

Rector 2.6 removes the version-numbered PHPUnit sets; withComposerBased(phpunit)
was already configured and derives them from composer.lock.

The two undocumented pins — rector/rector ~2.5.9 and an exact playwright-core
override — are lifted; nothing broke, which is the usual fate of a pin nobody
can explain.

* chore(deps): bump the web and mobile toolchains

Web: Next 16.3.5, Sentry 10.75, maplibre-gl 6.10, next-intl 4.14.5, vite 8.3,
vitest 5.0.1, zod 4.6.5 (pwa and core), prettier 3.9.8 — whose reformatting of
nine pre-existing files is the bulk of the diff.

Mobile stays on Expo SDK 57 / RN 0.86 (SDK 58 is preview-only), with every
expo-* patch, RN 0.86.3 and maplibre-react-native 11.4.

Three upgrades were tried and backed out; all three are recorded in ADR-070
rather than left as folklore:

- ESLint 10: eslint-plugin-react, pulled in by eslint-config-next, still calls
  context.getFilename(), removed in v10. The peer ranges allow ESLint 10 — they
  are simply wrong.
- TypeScript 7 on mobile: typescript-eslint refuses it outright, and npm hoists a
  single TypeScript for the whole workspace, so mobile's choice decided what
  eslint-config-next parsed with in pwa.
- React 19.3: Expo 57 pins React 19.2 and jest-expo ships react-test-renderer
  19.2.3, so 19.3 broke every mobile render test. The constraint is tightened to
  ~19.2.8 — ^19.2.8 admitted 19.3, and only the lockfile was holding it back.

Jest 30 needed two fixes its types surfaced: `global` is no longer re-exported
(use globalThis) and setImmediate is typed as taking no argument.

lucide-react-native 1.x moved the per-icon files to .mjs and gated dist/ behind
its exports map, so the deep imports the tree-shaking relies on (#1176) had to
become the public icons/* subpath. jest-expo's resolver picks the react-native
condition there, i.e. ESM, so the module mapper stays — retargeted at the CJS
build through a resolved filesystem path, which exports cannot refuse. `trash-2`
is gone from the package (though lucide-react still ships it as an alias of
`trash`); the two have identical path data, so Trash2 now maps to trash.

* docs: record the dependency version policy and the ceilings we hit

There was no ADR on versioning at all, which is how two pins ended up with no
explanation behind them and how four images ended up untagged. ADR-070 states
the rule (a pin without an ADR is a pin to lift) and tabulates each ceiling with
the condition that lifts it, so the next pass re-attempts on a fact rather than
on a hunch.

Three of those ceilings share a shape worth naming: the constraint that bites is
not the one you declare. npm hoists one React and one TypeScript for the whole
workspace, so a bump in mobile silently changes what pwa builds against.

ADR-037 gets an addendum: the PHP image is no longer the upstream FrankenPHP
image but a recompilation of it, because the Mercure hub lives inside that
binary.

* feat(mercure): add the MERCURE_ISSUER dev default

Mercure 1.0 access tokens are RFC 9068, so the backend has to mint an iss that
matches the Caddyfile issuer block. compose.yaml derives it from DOMAIN for both
php and worker, but anything running outside Compose — bin/console, PHPStan's
cache warmup, a throwaway PHPUnit container — reads api/.env instead and would
die on a missing environment variable.

Verified by running the Mercure functional test with no MERCURE_ISSUER in the
environment: it still mints a valid at+jwt, so the dotenv value is what the
configured hub picks up.

* chore(core): regenerate schema.d.ts after the Mercure docblock change

An #[ApiResource] class docblock becomes the resource description in the exported
OpenAPI, so renaming the cookie in MercureToken's PHPDoc drifts the generated
types and fails the OpenAPI -> TS drift job.

Worth recording that this two-line docblock is the *only* drift: API Platform 5
changed nothing in the exported schema, even though it now emits OpenAPI 3.2.

* fix(ci): declare eslint and typescript at the root, and repair three pins

Four red jobs, three causes.

**ESLint and typegen** shared one: when two workspaces ask for different versions
of a package, npm stops hoisting it and nests a copy in each — and anything that
*is* hoisted can then no longer resolve it. `typescript` lived only in pwa (5.9)
and mobile (6.0), so a clean `npm ci` left the root without one and the hoisted
`openapi-typescript` died with `Cannot find package 'typescript'`. Same story for
`eslint`, declared only in pwa, which the hoisted `eslint-plugin-react` could not
find.

This is unstable rather than plainly broken: whether npm hoists one of the two or
neither depends on resolution order, which is why an incremental `npm install`
locally and a clean `npm ci` in CI disagreed. Both are now declared in the root
devDependencies at the version the hoisted tooling should see; mobile keeps its
nested TypeScript 6. Verified by reproducing the CI layout (`npm ci` from a
removed node_modules), not by an incremental install.

**PHP-CS-Fixer** was my own regression: pinning `oskarstark/php-cs-fixer-ga` to
its newest *tag* (3.26.0) downgraded the bundled PHP below what the config file
needs, so it died on a parse error at the `new Config()->...` chain. `latest`
carries PHP 8.5.10; it is now pinned by digest, which keeps the reproducibility
the tag was meant to buy without the downgrade.

**hadolint** flagged the new builder stage: xcaddy is pinned to v0.4.7 (DL3062),
and the build-info verification writes to a temp file instead of piping into grep
(DL4006). Rebuilt to confirm the binary still reports mercure/caddy v1.0.0.

ADR-070 gains the hoisting rule: if a package is consumed by something npm
hoists, declare it at the root, and reproduce with `npm ci` rather than an
incremental install.

* fix(tests): give each test class its own $alwaysBootKernel slot

A static property a subclass does not redeclare is the ancestor's storage. The
23 Functional classes that want kernel reuse assigned
`self::$alwaysBootKernel = false` in setUpBeforeClass(), so every one of those
writes landed in App\Tests\ApiTestCase's single slot — and since PHPUnit runs the
suite in one process with no reset, whichever class ran first flipped the flag
off for every other Functional test for the rest of the run.

That is order-dependent rather than reliably broken, which is the worst shape:
it silently re-introduces, suite-wide, the kernel-reuse/Foundry-migrate failure
the base class exists to prevent. Redeclaring the property in each class gives it
real per-class storage.

Also from review:

- docker/bake-action goes back to a full commit SHA. Replacing the previous SHA
  pin with a floating tag contradicted the very commit that introduced it ("pin
  every floating image"), and a tag can be re-pointed by the action owner.
- the Caddyfile keeps its `log` directive. Removing the redaction filter was
  right — the `authorization` query parameter no longer exists in Mercure 1.0 —
  but removing the whole block turned access logging off, taking the
  X-Request-Id correlation trail with it.

* build(deps): require symfony/polyfill-php86 explicitly

Four files name \SortDirection directly — PHP's native global enum, which ships
in 8.6, so on this project's 8.5 it comes from symfony/polyfill-php86. That
polyfill was only present transitively, via doctrine/orm and doctrine/collections.
Relying on a transitive dependency for a class we reference by name is a latent
break: the day Doctrine stops needing it, four call sites fatal with nothing in
composer.json to explain why.

Declaring it also settles a review finding that has now been raised twice.
\SortDirection resolves under the production autoloader — verified in the built
prod image (classmap-authoritative, --no-dev, PHP 8.5.10), where it loads from
vendor/symfony/polyfill-php86/Resources/stubs/SortDirection.php, and where
doctrine:mapping:info reports [OK] App\ApiResource\TripRequest, i.e. Doctrine
reads the #[ORM\OrderBy] attribute the finding predicted would be fatal.

The suggested alternative, Doctrine\Common\Collections\Order, belongs to the
Collections criteria API; Doctrine's own deprecation message asks for
SortDirection by name, and doctrine/orm imports that same global enum in
ClassMetadata.

A comment at the TripRequest call site now records all of this, so the next
reader does not re-litigate it.

* docs: renumber the version-policy ADR to 071

main landed its own ADR-070 (freshness is dispatch completeness) while this
branch was open, so the dependency-version ADR takes the next free number.

* docs: drop a sentence duplicated into the compose-layout paragraph

Inserting the Mercure 1.0 note ended on the same sentence the original text
resumed with, so "in prod that edge serves plain HTTP behind Traefik +
Cloudflare Tunnel" appeared twice back to back. CLAUDE.md is loaded as project
instructions for every session, so the redundancy is not free.
@mislavjakopovic
mislavjakopovic changed the base branch from main to 5.0 September 21, 2026 23:04
@mislavjakopovic

mislavjakopovic commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

Hey guys,

As CONTRIBUTING.md says, pinging you here now that this should be ready for review (desc).

Please keep in mind that this is my first contribution to API Platform - I've tried my best and hopefully there will not be many problems.

As such I would like to thank all the maintainers (and @soyuka @dunglas) for amazing work they have done over the years and let you know that if there are any issues, I will be monitoring these PRs closely until merged.

If something doesn't makes sense, if for example Mercure failed pipelines get fixed upstream - I would be happy to rebase all of them (so we're 100% nothing is breaking), the same about discussing / fixing if anything else is found or recommended.

I appreciate any suggestions, critique etc - I currently have several large production systems running 4.3 "bleeding edge" with PHPUnit 13 and am more than looking forward to 5.0 soon, so any feedback is more than welcome!

Thank you,
Mislav

@vincentchalamon
vincentchalamon marked this pull request as ready for review September 22, 2026 07:28
@vincentchalamon vincentchalamon added [RFR] Ready for Review dependencies Pull requests that update a dependency file labels Sep 22, 2026
@soyuka
soyuka changed the base branch from 5.0 to 4.4 September 22, 2026 09:02
@soyuka
soyuka force-pushed the chore/allow-phpunit-13 branch from 74b812d to 1af3b2b Compare September 22, 2026 09:28
@soyuka
soyuka changed the base branch from 4.4 to 5.0 September 22, 2026 13:01
@soyuka
soyuka force-pushed the chore/allow-phpunit-13 branch from 1af3b2b to 37f1dc8 Compare September 22, 2026 13:53
@soyuka
soyuka force-pushed the chore/allow-phpunit-13 branch from 37f1dc8 to b5e7af2 Compare September 22, 2026 14:17
@soyuka
soyuka merged commit e3fabf1 into api-platform:5.0 Sep 22, 2026
2 checks passed
@soyuka

soyuka commented Sep 22, 2026

Copy link
Copy Markdown
Member

Thanks @mislavjakopovic for the patches!

vincentchalamon added a commit to api-platform/demo that referenced this pull request Sep 22, 2026
api-platform/core#8537 widened api-platform/test to
"^11.5 || ^12.2 || ^13.0", but no release carries it yet: Packagist still
serves v5.0.0 with "^11.5 || ^12.2", which caps the demo at PHPUnit 12.

Widening the constraint here changes nothing today, the lock stays on 12.5.35.
It only means the next `composer update` picks PHPUnit 13 on its own once
api-platform/test ships the change, instead of needing another manual bump.
vincentchalamon added a commit to api-platform/demo that referenced this pull request Sep 26, 2026
5.0.1 carries the two fixes this branch was waiting on:

- api-platform/core#8546 resolves the "{{ type }}" placeholder, so the four
  validation failures are gone and the suite is green.
- api-platform/core#8537 widens api-platform/test to allow phpunit ^13.0. The
  constraint here was already widened in anticipation, so composer picked
  13.3.5 on its own.

Mercure 1.0.2 also landed, and it now allows only one *unnamed* hub per
configuration. SERVER_NAME holds two addresses (the public one and php:80 for
internal calls), so Caddy builds two servers and instantiates the handler
twice, which made the container fail to boot. Naming the hub is enough; the
name only feeds the health check endpoint and the metrics.
vincentchalamon added a commit to api-platform/demo that referenced this pull request Sep 30, 2026
… latest stable (#691)

* fix(api): let Go fetch the toolchain required by Caddy modules

The FrankenPHP builder image ships Go 1.26.8 and pins GOTOOLCHAIN=local,
which forbids downloading a newer toolchain. github.com/dunglas/mercure/caddy
v1.0.0 declares `go 1.27` in its go.mod, so xcaddy fails:

    go: github.com/dunglas/mercure/caddy@v1.0.0 requires go >= 1.27
        (running go 1.26.8; GOTOOLCHAIN=local)

This breaks every Docker build on every branch since Mercure 1.0.0 was
released. Bumping the builder image is not an option, 1.12.7 is the latest
FrankenPHP release and it is the one shipping Go 1.26.8.

Restore the Go default so the toolchain a module asks for is fetched on
demand.

* feat: upgrade Mercure to 1.0

Mercure 1.0 replaces the flat publisher_jwt/subscriber_jwt directives with
issuer blocks and expects RFC 9068 access tokens: iss, aud, exp and a typ of
at+jwt, verified against a declared issuer. The old directives now only work
in a compatibility mode that drops those checks and re-accepts the token in
the query string, so migrate rather than opt into it.

- Caddyfile: declare an issuer with publisher and subscriber verifiers, and
  pin resource_identifier. The API publishes through the internal MERCURE_URL
  while tokens are minted for the public URL, so without pinning, the hub
  derives a different audience per request and rejects them. The value is
  substituted at parse time, since the hub validates it as a URL before
  runtime placeholders resolve.
- mercure.yaml: protocol_version 1.0, which makes symfony/mercure-bundle mint
  RFC 9068 tokens, plus the iss, sub and client_id claims it then requires.
- The demo directive is now playground, and its UI moved from
  /.well-known/mercure/ui/ to /.well-known/mercure/debug/.

Subscribers are unaffected: the PWA subscribes anonymously, with no token.

* feat(api): upgrade API Platform to 5.0

The test helpers moved: ApiPlatform\Symfony\Bundle\Test\{ApiTestCase,Client}
are deprecated in favour of ApiPlatform\Test\{ApiTestCase,Client}, now shipped
by a separate api-platform/test package. The deprecated shims extend classes
from that package, so without it the test suite fatals rather than warns.

That package supports phpunit ^11.5 || ^12.2, so PHPUnit moves from 13 to 12.
It is the one dependency that cannot be on its latest major while API Platform
5.0 is.

Nothing else in the demo needed changing: PHPStan reports no error and no use
of a deprecated API, and Rector has nothing to propose.

* chore(pwa): update dependencies

@types/node moves to 26, the rest are patch and minor bumps.

Two majors are held back, both blocked by the ecosystem rather than by the
demo:

- typescript stays on 6.x. typescript-eslint refuses to load under TS 7 and
  tracks support for >= 7.1 in typescript-eslint/typescript-eslint#10940.
- eslint stays on 9.x. eslint-plugin-react 7.37.5, pulled in transitively by
  eslint-config-next, still uses the context API that ESLint 10 removed, and
  crashes on the first React file.

Verified in a clean container: pnpm lint reports no error, pnpm build passes.

* fix: subscribe to Mercure with the match parameter

Mercure 1.0 renamed the subscribe query parameter: "topic" is rejected with

    unknown topic matcher query parameter: "topic" (use "match" or "match_urlpattern")

Publishing is unaffected, the publish endpoint still reads "topic" form fields.

This was only caught because the CI reachability check happens to subscribe.
The PWA builds its EventSource URL the same way, so real-time updates would
have shipped broken.

* test: adapt to the API Platform 5.0 response headers and Mercure UI path

API Platform 5.0 no longer appends "; charset=utf-8" to JSON media types.
Per RFC 8259 and the RFC 6839 "+json" suffix, JSON defines no charset and is
always UTF-8, so sending the parameter can break strict clients; only text/*
and application/xml keep it. That accounted for all 80 functional failures.

The Mercure debugger also moved from /.well-known/mercure/ui/ to
/.well-known/mercure/debug/.

* fix(api): drop the unset JWT algorithm argument from the Mercure issuer

MERCURE_PUBLISHER_JWT_ALG and MERCURE_SUBSCRIBER_JWT_ALG are set nowhere in
the project. The hub defaults a raw secret to HS256, which is what
symfony/mercure-bundle signs with, so the argument only carried an empty
placeholder.

* test: adapt to the API Platform 5.0 error payloads

Two deliberate changes in the major:

- An unauthenticated request now reports "Access Denied." as its description
  rather than the Symfony entry point message, mirroring "detail".
- Hydra "range" is a plain IRI string for most properties and only a list for
  those carrying an owl:equivalentClass restriction, so the documentation test
  has to accept both shapes.

Four failures remain, all on the same validation message template not being
interpolated; they are tracked separately rather than aligned to the buggy
output.

* fix: enable the Mercure debugger, not the playground

Mercure 1.0 split the old "demo" directive in two. "playground" does more than
serve the debugger UI: it also redirects the site root to it.

    if m.Playground && r.URL.Path == "/" {
        http.Redirect(w, r, defaultHubURL+"/debug/", http.StatusFound)

The hub is mounted inside the API, which owns "/", so the entrypoint answered
with the debugger HTML instead of the JSON-LD entrypoint, and every client
introspecting the API broke.

"debugger" serves the same UI at /.well-known/mercure/debug/ without touching
the root.

* chore(api): allow phpunit 13 ahead of the api-platform/test release

api-platform/core#8537 widened api-platform/test to
"^11.5 || ^12.2 || ^13.0", but no release carries it yet: Packagist still
serves v5.0.0 with "^11.5 || ^12.2", which caps the demo at PHPUnit 12.

Widening the constraint here changes nothing today, the lock stays on 12.5.35.
It only means the next `composer update` picks PHPUnit 13 on its own once
api-platform/test ships the change, instead of needing another manual bump.

* chore(api): upgrade to API Platform 5.0.1 and PHPUnit 13

5.0.1 carries the two fixes this branch was waiting on:

- api-platform/core#8546 resolves the "{{ type }}" placeholder, so the four
  validation failures are gone and the suite is green.
- api-platform/core#8537 widens api-platform/test to allow phpunit ^13.0. The
  constraint here was already widened in anticipation, so composer picked
  13.3.5 on its own.

Mercure 1.0.2 also landed, and it now allows only one *unnamed* hub per
configuration. SERVER_NAME holds two addresses (the public one and php:80 for
internal calls), so Caddy builds two servers and instantiates the handler
twice, which made the container fail to boot. Naming the hub is enough; the
name only feeds the health check endpoint and the metrics.

* chore(pwa): update dependencies

Patch bumps across the board: next 16.3.6, better-auth 1.7.6, react-query
5.103.2, @types/node 26.6.2.

The three majors stay held back, still by the ecosystem:

- eslint 9.x and @eslint/js 9.x, because eslint-plugin-react 7.37.5 (pulled
  transitively by eslint-config-next) declares eslint "^3 || ... || ^9.7".
- typescript 6.0.x, because typescript-eslint 8.70.1 declares
  typescript ">=4.8.4 <6.1.0", which rules out 7 and even 6.1.

Verified in a clean container: pnpm lint reports no error, pnpm build passes.

* chore: bump the displayed API Platform version to 5.0.1

* chore(pwa): update dependencies, api-doc-parser 0.16.12

api-doc-parser 0.16.12 resolves the collection member type of the entrypoint
from hydra:memberAssertion, which API Platform 5 emits instead of an
owl:equivalentClass restriction. Without it React Admin cannot introspect the
API and the admin end-to-end tests fail. @api-platform/admin needs no release:
its "^0.16.8" range already covers 0.16.12, and both share the one instance.

Also react-admin 5.15.4, react-query 5.104, react-hook-form 7.89, sharp 0.35.5.

eslint 9 and typescript 6 stay held back, see the previous update.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* chore(api): update API dependencies

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file [RFR] Ready for Review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants