fix(deps)!: move adapter peers onto the UIKit v3 line and accept Vite 8 - #86
Merged
Conversation
Versions 8.0.0 through 12.0.0 all declared `vite: ^7.0.0`, so no published
version of @openzeppelin/adapters-vite installs against Vite 8 without an
unmet peer.
The package has no runtime coupling to Vite. Its only runtime imports are
node:module, node:fs and node:path; Vite appears solely as
`import type { Plugin, PluginOption, UserConfig }` across config.ts,
integration.ts, registry.ts, resolver.ts and types.ts. All three types still
exist in Vite 8, and the workspace already builds and type-checks against
vite@8.0.5.
Widening a peer range is not breaking, so this releases as a minor.
Every adapter declared UIKit v2 for ui-components, ui-react and ui-utils while already declaring v3 for ui-types. UIKit's current line is components 3.x, react 3.x, utils 4.x, types 3.5.x, so no consumer on current UIKit could install an adapter without unmet peers. ui-components ^2.0.0 -> ^3.9.0 ui-react ^2.0.1 -> ^3.3.2 ui-utils ^2.0.0 -> ^4.0.1 ui-types ^3.2.0 / ^3.5.0 -> ^3.5.2 The floors are exact patch versions rather than ^3.0.0 / ^4.0.0 because the UIKit packages depend on each other with dependencies, not peers. ui-components@3.9.0 depends on ui-utils ^4.0.1 and ui-types ^3.5.2, and ui-react@3.3.2 depends on ui-components ^3.8.3. A major-level floor lets a consumer satisfy our peer with an older patch while UIKit resolves a second nested copy for itself. Two copies of ui-utils split its module-level singletons, appConfigService and Logger.getInstance(), leaving the adapter's copy uninitialized; two copies of ui-components break React context identity; two copies of ui-types make structurally identical types nominally distinct, which is the TS2322/TS2345 failure the ui-types override in pnpm-workspace.yaml already exists to prevent in-repo. v2 is not kept via ^2.0.0 || ^3.0.0. The same internal coupling means a per-package union range advertises mixes UIKit itself forbids, and a range the workspace neither installs nor tests is a claim CI cannot keep. The validatePeerVersions minimums are generated from the declared peer ranges at build time, so the baked floors move with the manifest and a v2 install now throws at module load instead of failing later on a type or prop mismatch. UI_TYPES_FLOOR in the SF-5 release-correctness suite moves to ^3.5.2 with the second reason for the move recorded alongside the initiative 004 rationale. No source changes were needed. Verified against ui-components@3.9.0, ui-react@3.3.2, ui-utils@4.0.1 and ui-types@3.5.2: build, typecheck, lint, format:check and 3197 tests across all eight workspace packages pass. BREAKING CHANGE: the adapters now require the UIKit v3 line. Consumers on UIKit v2 must upgrade @openzeppelin/ui-components, ui-react, ui-utils and ui-types together.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
Two independent install blockers, both manifest-only.
1. Adapters could not be installed on current UIKit. Every adapter package declared UIKit
v2 for
ui-components,ui-reactandui-utilswhile already declaring v3 forui-types. UIKit's current line is components 3.x, react 3.x, utils 4.x, types 3.5.x — so noconsumer on current UIKit could install any adapter without unmet peers. The manifests described
a combination that no longer exists.
2.
adapters-vitecould not be installed on Vite 8. Versions 8.0.0 through 12.0.0 alldeclared
vite: ^7.0.0, so no published version installs against Vite 8.Why the ranges said v2 — this was not neglect
Worth stating, because the obvious reading is that these pins were left to rot. They were not.
They were correct when written, and a release-process bug on the UIKit side moved the goalposts
without changing a line of API.
The capability migration (openzeppelin-ui#113) was released as
2.0.0across all four packages.Its changeset files were then reintroduced by a later merge that restored the branch's copies of
files
mainhad deleted at release, and the next release consumed them a second time as3.0.0. One breaking change, two majors.I verified this against the published CHANGELOGs rather than taking it on report. In
packages/react/CHANGELOG.md, the## 2.0.0and## 3.0.0major-change entries are both 1665characters, both cite PR #113, and carry identical prose — they differ only in the commit SHA
(
52f2823vs6f09f66).packages/components/CHANGELOG.mdshows the same shape at 991characters each. The same bug recurred later: components
3.7.0and3.8.0are 907 characterseach, the first citing #193 and the second #195, the PR that reintroduced it.
So the adapters migrated to the capability API, which landed in
2.0.0, pinned^2.0.0correctly — and were then a full major behind an API that never changed again. That is precisely
why the file-level diff of every consumed source comes back byte-identical and why this PR needs
zero source changes: there was never anything to migrate. Meanwhile
ui-typeskept shipping realminors the adapters needed, which dragged that one range forward to
^3.5.0on its own. Hencethe half-migrated state in the table above, which looked coherent from the inside.
Credit for tracing this to the UIKit side. Two follow-ups are in flight there: a CI guard that
fails when a changeset already published in a CHANGELOG is still sitting in
.changeset/(openzeppelin-ui#230), and a
oz-ui-dev check-peerswarning when a declared range does not admitthe installed version (openzeppelin-ui#229). Once #229 lands, adding
pnpm exec oz-ui-dev check-peers --project "$PWD"to this repo's CI after install is the signalthat was missing here — run against this tree as it stands it passes clean across 20 peer pairs.
What changed
^2.0.0→^3.9.0^2.0.1→^3.3.2^2.0.0→^4.0.1^3.2.0/^3.5.0→^3.5.2Plus
@openzeppelin/adapters-vite:vite^7.0.0→^7.0.0 || ^8.0.0.No source changes. The only files touched are the eight
package.jsonmanifests, thelockfile, the
ui-typesdedupe override inpnpm-workspace.yaml, one test constant, and twochangesets.
Why the floors are exact patch versions and not
^3.0.0/^4.0.0I started with major-level floors and tightened them after review from the UIKit side. The four
UIKit packages depend on each other with
dependencies, not peers:ui-components@3.9.0→ui-utils ^4.0.1,ui-types ^3.5.2ui-react@3.3.2→ui-components ^3.8.3,ui-utils ^4.0.1,ui-types ^3.5.2ui-utils@4.0.1→ui-types ^3.5.2So a floor below those lets a consumer satisfy our peer with an older patch while UIKit resolves
a second, nested copy for itself. That is not cosmetic:
ui-utilssplit its module-level singletons.appConfigServiceandLogger.getInstance()hold init state. The adapter's copy would be permanently uninitializedand silently return defaults behind a "called before initialization" warning.
ui-componentsbreak React context identity.ui-typesmake structurally identical types nominally distinct — theTS2322/TS2345 failure that the
@openzeppelin/ui-typesoverride inpnpm-workspace.yamlalready exists to prevent inside this repo.
Each floor is set to the version UIKit itself requires, which is also the version this change
was verified against. Carets still admit every later 3.x / 4.x release.
Why v2 is not kept via
^2.0.0 || ^3.0.0Considered and rejected. The same internal coupling means a per-package union range advertises
mixes UIKit itself forbids (
ui-components@3can never sit withui-utils@2). More decisively,a range the workspace neither installs nor tests is a claim CI cannot keep — the workspace now
resolves the v3 line, so nothing would signal a v2 regression on any future change.
There is also a mechanical trap:
__OZ_PEER_MINIMUMS__is derived in eachtsdown.config.tsby(range).replace(/^\^/, ''). A union range would bake the literal string"2.0.0 || ^3.0.0"asthe "minimum" and feed it to
compareSemver.Consumers on UIKit v2 must upgrade all four packages together. The baked
validatePeerVersionsfloor rises with the manifest, so a v2 install now throws at module load with an actionable
message rather than failing later on a type or prop mismatch.
Regenerated runtime guard
The minimums are generated from the declared peer ranges at build time, so manifest and runtime
guard cannot drift. Confirmed in the built bundles:
The workspace dedupe override moved with the floor
pnpm-workspace.yamlpins@openzeppelin/ui-typesto force one copy across the workspace.It moved
^3.5.0→^3.5.2along with the peer floors.This is not cosmetic housekeeping. The override exists precisely because
ui-typesresolvingtwice makes the structurally identical
EcosystemRuntime/CapabilityFactoryMapnominallydistinct, producing TS2322/TS2345 in
profiles/shared-state.ts. Now thatui-components,ui-reactandui-utilseach depend onui-types ^3.5.2themselves, leaving the override at^3.5.0would let pnpm satisfy those independently and reintroduce the second copy. The commentabove the pin was rewritten to record that reason, replacing the stale one about
adapter-runtime-utilsstill declaring^3.2.0— it no longer does.Verified after install: all seven adapter packages resolve a single
ui-types@3.5.2, and thelockfile contains no other
ui-typesentry.One test constant moved
UI_TYPES_FLOORinpackages/adapter-evm/test/sf-5-published-release.test.tshard-asserts theui-typesfloor on bothpeerDependenciesanddevDependenciesforadapter-evmandadapter-evm-core. Raising the floor to^3.5.2required moving it. I updated the constant andrecorded the second reason for the move alongside the existing initiative-004 rationale rather
than loosening the assertion. The guard still fires if peer and dev drift apart.
Proof it compiles
This was the part nobody had established — matching export names is not proof that props or
behaviour match across a major. Installed against real UIKit v3 (
ui-components@3.9.0,ui-react@3.3.2,ui-utils@4.0.1,ui-types@3.5.2) and ran the repo's own checks:pnpm installpnpm build(incl.validate:vite-configs,validate:host-peers)pnpm typecheckpnpm testpnpm lintpnpm format:checkPer-package test totals:
pnpm peers checkno longer reports any unmet@openzeppelin/ui-*peer.Nothing broke between UIKit v2 and v3 on the surface these adapters consume. That is
corroborated independently: the UIKit side diffed every consumed source file and its transitive
import closure from the last 2.x tags to HEAD and found the backing sources byte-identical
except
AddressDisplay(purely additive — four new optional props, all new render paths gatedon a
resolvedNameprop or anAddressNameContextprovider) andAppConfigService.ts(whoseonly removal is the WalletConnect project-id special case). I confirmed both are inert here:
AccountDisplay.tsxis the singleAddressDisplaycall site and passes noresolvedName, andno adapter mounts
AddressNameProvideranywhere.Release
Two changesets.
pnpm changeset status --verbosereports:Note
adapters-vitelands at 13.0.0, not 12.1.0. Widening the Vite peer is genuinely aminor and its changeset says
minor, butadapters-vitepeer-depends on the five adapters, sochangesets escalates it to major to ride along with their 6.0.0. That is the tooling behaving
correctly, not the Vite change being breaking.
adapter-evm-coreandadapter-runtime-utilsareprivate: trueand so are not versioned,matching existing repo practice. They carry the same ranges because they are bundled into the
published adapters.
Known follow-up, deliberately not in this PR
adapter-evm-coredeclaresreact: ^18.0.0 || ^19.0.0but rendersui-components, which peersreact ^19.0.0only. An adapter that rendersui-componentscannot honestly claim React 18.I am flagging rather than fixing it: it is pre-existing and unchanged by this PR
(
ui-componentspeeredreact ^19in 2.x too),ui-components/ui-reactare optional peerson
adapter-evm-coreso a headless React 18 consumer is currently legitimate, and tightening to^19is a separate breaking change with its own consumer impact. It deserves its own PR and itsown decision.
Why this drifted through two majors unnoticed
The duplicate-release bug above explains how the ranges fell behind. This explains why nothing
ever said so.
validatePeerVersionsinui-utilsis floor-only — it throws whencompareSemver(installed, minimum) < 0and never wheninstalledis above. Withminimumderived from a
^2.0.0declaration, an installed4.0.1passed silently. There was no runtimesignal that the manifest had gone stale, and no install-time one either, because an over-install
is not an unmet peer.
Adding an upper bound to that runtime helper was considered on the UIKit side and rejected,
correctly: it would convert today's benign over-install into a hard startup throw for consumers
on a newer kit, which is a worse failure than the one it prevents, and it would require passing
the range rather than the baked minimum — a joint API change, not a free option. The warning in
oz-ui-dev check-peers(openzeppelin-ui#229) is the right place for this signal, since itcannot break anyone's CI.