Skip to content

feat: upgrade API Platform to 5.0, Mercure to 1.0 and dependencies to latest stable - #691

Open
vincentchalamon wants to merge 9 commits into
mainfrom
fix/go-toolchain
Open

vincentchalamon wants to merge 9 commits into
mainfrom
fix/go-toolchain

Conversation

@vincentchalamon

@vincentchalamon vincentchalamon commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Docker builds have been failing on every branch since Mercure 1.0.0 was released. Rather than pin around it, this upgrades API Platform to 5.0, Mercure to 1.0, and the remaining dependencies to their latest stable, majors included.

Unbreaking the build

dunglas/frankenphp:1.12.7-builder ships Go 1.26.8 and pins GOTOOLCHAIN=local, which forbids downloading a newer toolchain. Mercure Caddy 1.0.0 declares go 1.27, so xcaddy build aborted:

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

1.12.7 is the latest FrankenPHP release, so restoring the Go default is the fix. It also prevents the next module that raises its minimum from breaking the build again.

Mercure 1.0

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 still work, but only in a compatibility mode that, in upstream words, "drops the required exp, the audience check, the at+jwt typ check and the verified-issuer check, and re-accepts the access token in the URL query string", and additionally needs the deprecated_topic and deprecated_claim build tags. That is three switches to import a major and disable most of it, so this migrates instead.

  • Caddyfile: an issuer block with publisher and subscriber verifiers, plus a pinned resource_identifier. The API publishes through the internal MERCURE_URL while tokens are minted for the public URL, so left unpinned the hub derives a different audience per request and rejects them. The value is substituted at parse time, because 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.

API Platform 5.0

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 suite fatals rather than warns, which is also why Rector briefly proposed deleting seven setUp() methods: it could no longer resolve the parent class. Those proposals were a symptom, not a change, and are gone now that the package is installed.

Nothing else in the demo needed touching. PHPStan reports no error and no use of a deprecated API, Rector has nothing to propose, and the 27 unit tests pass with failOnDeprecation active.

Dependencies held back

Three, all blocked by the ecosystem rather than by the demo:

Dependency Held at Reason
phpunit/phpunit 12.x api-platform/test 5.0.0 supports ^11.5 || ^12.2; there is no release supporting 13
typescript 6.x typescript-eslint refuses to load under TS 7, tracking >= 7.1 in typescript-eslint/typescript-eslint#10940
eslint 9.x eslint-plugin-react 7.37.5, pulled transitively by eslint-config-next, uses the context API ESLint 10 removed and crashes on the first React file

PostgreSQL is left alone in the Helm chart as requested. external-dns and Redis are already on their latest. GitHub Actions are handled by Renovate on its monthly schedule.

Verification

Run locally before pushing:

  • FrankenPHP builder stage built, and the resulting binary inspected: all seven Caddy modules present, none silently dropped.
  • frankenphp validate against the real Caddyfile, which provisions the modules rather than only parsing: valid. The same command reproduces the exact CI failure when the issuer block is reverted.
  • Symfony container recompiles against API Platform 5.0, which also validates the new mercure.yaml.
  • phpstan analyse: no errors. rector process --dry-run: nothing to change. phpunit --testsuite Unit: 27 tests, 114 assertions, green.
  • PWA in a clean container: pnpm lint reports no error, pnpm build passes.
  • helm lint passes.

Functional and E2E suites need a database, so CI is the arbiter there.

Closes #692

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.
@vincentchalamon vincentchalamon added the dependencies Pull requests that update a dependency file label Sep 18, 2026
@vincentchalamon vincentchalamon changed the title fix(api): let Go fetch the toolchain required by Caddy modules fix: unbreak the Docker build after Mercure 1.0.0 Sep 18, 2026
@vincentchalamon
vincentchalamon force-pushed the fix/go-toolchain branch 2 times, most recently from 2e48946 to c0ad26c Compare September 18, 2026 14:46
@vincentchalamon vincentchalamon changed the title fix: unbreak the Docker build after Mercure 1.0.0 feat: upgrade API Platform to 5.0, Mercure to 1.0 and dependencies to latest stable Sep 18, 2026
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.
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.
@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.
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.
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/.
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.
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.
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.
@vincentchalamon

Copy link
Copy Markdown
Contributor Author

Status: waiting for api-platform/api-doc-parser#181

Status: waiting for api-platform/core#8546

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate Mercure to modern mode (issuer blocks)

1 participant