P1: Upgrade to graphql-yoga 5 and Node 22 LTS (#176) - #194
Conversation
Bring the server stack onto current majors: - graphql-yoga 4 → 5, @envelop/core 4 → 5, @envelop/graphql-jit 6 → 11, @envelop/disable-introspection 5 → 9, @envelop/opentelemetry 5 → 9. - Node 20 → 22 LTS across the Dockerfile, Volta pin, and the lint / unit-test / publish / smoke-load workflows. The only code change required is a targeted cast where useOpenTelemetry now types its provider argument against a duplicate @opentelemetry/api copy; the cast targets the exact expected parameter type. Build, unit tests, lint, and prettier all pass; the node:22 image builds and runs v22.x locally. Note: the OpenTelemetry SDK is intentionally not bumped here — the modern OTel packages drop the Jaeger exporter in favour of OTLP, so clearing the remaining @opentelemetry/* audit advisories is a separate Jaeger→OTLP migration. Closes #176. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QSuak9smCHbp4N17xjjLF6
|
Nice upgrade — clean and minimal (great that it came down to a single typed cast for the OTel provider), and thanks for the thorough build/lint/image verification. Since this is the one change in the production-readiness train that could subtly affect the mina-explorer client, I verified the highest-risk axis empirically: yoga 5's GraphQL-over-HTTP status codes. The Explorer's archive client throws on any non-2xx before it reads the GraphQL body ( Good news — it's safe. I ran both 4.0.4 and 5.21.2 with the Explorer's exact request shape (
Both only switch to 400 under One suggestion to keep it safe: the 200-on-validation-error behavior is an implicit content-negotiation default, so a future yoga bump could flip it unnoticed. A tiny integration test alongside the existing ones would pin the Explorer contract: // the exact shape the mina-explorer client sends (no Accept header)
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: '{ __definitelyNotAField }' }),
});
assert.equal(res.status, 200); // must NOT be 400
const body = await res.json();
assert.match(body.errors[0].message, /Cannot query field/); // Explorer fallback keys on thisMinor nit while here: |
|
One more small thing, on the Docker side: this bumps both stages to a floating FROM node:22-alpine@sha256:<digest> AS build
# ...
FROM node:22-alpine@sha256:<digest>(Once #192 lands, its Dependabot |
Pins both Dockerfile stages to node:22-alpine@sha256:16e22a55… (v22.23.1, verified by pull and build). Without this, moving to a floating 22 tag would silently drop the digest pin #189 adds, reverting its reproducible/tamper-evident build hardening depending on merge order. Once #192 lands, Dependabot's docker ecosystem keeps the digest fresh. Bumps @types/node to ^22 so the types match the runtime rather than staying on 20. Adds tests pinning the GraphQL-over-HTTP behaviour the mina-explorer depends on: validation errors return 200 (its client throws on non-2xx before reading the body) and carry the literal "Cannot query field" (its fallbacks key on that string). Both were verified by hand across the 4 → 5 upgrade and are unchanged, but they rest on an implicit content-negotiation default — yoga only returns 400 under an Accept header that client never sends — so a later bump could flip them unnoticed. Now a standing guard rather than a one-time check. Addresses review feedback on #194. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks @SanabriaRusso — and thank you for actually running both versions against the Explorer's request shape rather than reasoning about it. All three done in Digest pin. Both stages now pin
The contract test. Added Your reasoning for why it's worth pinning is in the file: the 200 is an implicit content-negotiation default, so nothing about a future bump would announce a flip. This turns "someone checked once" into a standing guard — and since this PR is the upgrade itself, the guard belongs here rather than downstream. |
Closes #206. ## Problem The `v0.0.9` tag push failed at the **`Update npm`** step — [run 30540591005](https://github.com/o1-labs/Archive-Node-API/actions/runs/30540591005) — before dependencies were installed, before tests, and before `npm publish`: ``` npm error code EBADENGINE npm error Not compatible with your version of node/npm: npm@12.0.2 npm error notsup Required: {"node":"^22.22.2 || ^24.15.0 || >=26.0.0"} npm error notsup Actual: {"npm":"10.8.2","node":"v20.20.2"} ``` `npm@latest` has moved to 12.0.2, which dropped Node 20. The job pins `node-version: '20'`, so the install is refused. Nothing was published — npm still shows only `0.0.6`. This is an upstream change, not a repo regression: it breaks **any** tag pushed from today onward. ## Change ```diff - # Ensure npm 11.5.1 or later is installed - name: Update npm - run: npm install -g npm@latest + run: npm install -g npm@11 ``` `npm@11` is currently 11.19.0, engines `^20.17.0 || >=22.9.0` — satisfied by the job's Node 20.20.2, and well past the 11.5.1 that trusted publishing / OIDC requires. Pinning the major keeps patch updates flowing without another silent engine break; the unqualified `latest` is what made this a time bomb. ## Why not bump Node to 22 More future-proof, but wider: the publish job also runs the full `npm test`, so changing its Node version changes the runtime the release is validated against. #194 (`P1: Upgrade to graphql-yoga 5 and Node 22 LTS`) is already open and is the right place for that. Once it lands, this pin can be revisited. ## Verification This workflow only runs on `v*` tag pushes and `workflow_dispatch`, so PR CI cannot exercise it. The engine claim is checked directly against the registry: ``` $ npm view npm@11 version -> 11.19.0 $ npm view npm@11 engines -> { node: '^20.17.0 || >=22.9.0' } $ npm view npm@12 engines -> { node: '^22.22.2 || ^24.15.0 || >=26.0.0' } ``` Node 20.20.2 satisfies `^20.17.0` and does not satisfy `^22.22.2`, which is exactly the observed pass/fail split. Real proof comes from the retriggered `v0.0.9` publish after this merges. ## Follow-up `v0.0.9` is tagged at `3d254f7` but published nothing. Re-running the failed run will not help — a tag-push event uses the workflow file as it exists at the tagged commit, which still has the broken step. The tag needs to be moved to the commit containing this fix and re-pushed. Since no npm version was consumed, that is a clean operation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
What & why
Part of the production-readiness epic (#163). Closes #176.
Key server deps were a major behind. This brings them current:
@envelop/core4 → 5,@envelop/graphql-jit6 → 11,@envelop/disable-introspection5 → 9,@envelop/opentelemetry5 → 9Code impact
Minimal — a single targeted cast:
@envelop/opentelemetry@9types its provider argument against a duplicate@opentelemetry/apicopy, soprovideris cast to the exact parameter typeuseOpenTelemetryexpects. Everything else compiled unchanged.Verification
npm run build— cleannpm run test:unit— all passnpm run lint/npx prettier --debug-check .— cleanFollow-up (not in this PR)
The OpenTelemetry SDK is intentionally not bumped — modern OTel drops the Jaeger exporter for OTLP, so clearing the remaining
@opentelemetry/*audit highs is a separate Jaeger→OTLP migration. With Yoga on 5, the graphql-armor meta package (#164 currently uses the individual sub-plugins due to the old envelop-4 peer) could also be revisited.🤖 Generated with Claude Code