Repository navigation
DLS-997 chore(deps): add syncpack to enforce one version per package - #940
Merged
Merged
Conversation
- .syncpackrc.json: one version per package across all manifests, identical peer ranges across published libs, lockstep families (nx, storybook web, react, react-native, vitest, tailwind, svgr) treated as one package, and explicit labeled exceptions (ui-rnative react ^19 peer, sandbox `*`). - repo-tools peerDeps validator: every lib peer is installed in the root devDependencies at a version its range accepts. - New `dependency-alignment` CI job running both checks. - Align existing drift: storybook web family on 10.2.14 (10.2.13 and 10.2.14 were both installed), sandbox prettier/jest/@types/jest/@babel/core, ui-rnative @types/react, drop unused jest-util/jest-environment-node 30, add expo/expo-haptics peers to root devDependencies.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The peer validator checks declared range minima rather than installed versions, allowing incompatible dependencies to pass.
1 open issue
What changed in this PR
This PR adds dependency-alignment checks across Lumen’s workspace manifests to prevent version drift in shared tooling and published libraries.
Changes:
- Configures syncpack with package-family alignment and explicit exceptions.
- Adds a peer-dependency validator and CI checks.
- Aligns existing dependency declarations and updates the lockfile, documentation, and React Native version plan.
| File | Description |
|---|---|
package.json |
Adds validation commands and tools; aligns shared dependencies. |
package-lock.json |
Records aligned versions and updated dependency resolution. |
libs/ui-rnative/package.json |
Aligns the React types development dependency. |
internals/repo-tools/src/ci/validators/peerDeps.mjs |
Adds library peer compatibility checks. |
internals/repo-tools/README.md |
Documents the validator and its CI job. |
apps/app-sandbox-rnative/package.json |
Aligns Jest, Jest types, and Prettier. |
AGENTS.md |
Documents dependency-alignment commands and exceptions. |
.syncpackrc.json |
Defines version alignment, package families, and range rules. |
.nx/version-plans/version-plan-1793033726214-ui-rnative.md |
Adds a React Native package patch plan. |
.github/workflows/pr.yml |
Runs dependency-alignment and peer checks in CI. |
Review effort: Balanced
The root range floor can sit inside a peer range while the version CI installs does not (e.g. root ~5.6.0 installs 5.6.2, peer 5.6.0), so read the installed version from package-lock.json before calling satisfies.
… design-core Stories, tests, Figma Code Connect, Storybook and tool configs are ignored, as is the SVGR icon template that only internals/sync-figma codegen reads. Remaining entries, decided per lib: - @types/d3-scale stays a dependency: the public NumericScale/CategoricalScale types in the emitted .d.ts import from d3-scale, which ships no types. - @types/d3-shape moves to root devDependencies: no emitted .d.ts references it. - ui-rnative keeps the @types/react peer (public props use React 19 ref-as-prop) and the expo peer (expo-haptics peers on it; v15 needs SDK 54+). The rule is scoped to package.json: **/*.json would also put the unformatted tsconfig/figma JSON files under prettier.
…ettier ui-rnative peers now stop at the tested major, with floors at the tested minor and never above what Ledger Live mobile pins today: - expo >=54.0.0 -> ^54.0.0 (expo-haptics ~15.0.8 only supports SDK 54) - react-native-gesture-handler >=2.0.0 -> ^2.28.0 - react-native-safe-area-context ^4 || ^5 -> ^5.6.0 (v4 was never installed) - react-native-svg ^15.0.0 -> ^15.12.0 - @gorhom/bottom-sheet ^5.0.0 -> ^5.2.0 @types/react stays ^19.0.0: Ledger Live is on 19.0.14. The peer validator now rejects ranges with no upper bound. @storybook/jest, @storybook/testing-library and @storybook/addon-react-native-web are referenced nowhere; prettier is pinned to 3.8.3 because its output changes between minors.
ui-react and ui-rnative ship the same dependencies and peers as on main, so this PR needs no release. The root devDependencies adapt instead: @types/react takes the lib's ^19.0.0 range (the lockfile still installs 19.1.17), and @types/d3-shape stays a lib dependency, ignored by @nx/dependency-checks. The peer validator keeps its no-upper-bound guard, with the two ui-rnative peers already published unbounded listed as explicit exceptions.
Scripts were split by kind (ci/, ci/validators/, lib/), so finding the code behind a CI job meant knowing how it was classified. Each folder is now named after the npm script or workflow that runs it: check-agent-docs, check-package-exports, check-peer-deps, fix-lcov-paths and publish-dev-packages, with shared helpers in shared/. The README also had check:package-exports in the static-analysis job; it runs in bundle-checks.
Moves the per-peer decision into checkPeer() so it can be tested without a workspace on disk, like fixLcovPaths. The tests pin each failure: missing from the root devDependencies, only installed nested, installed outside the range, and no upper bound. An unbounded-peer exception now fails once the range has an upper bound, so the list can't keep names after the peer is fixed.
@storybook/react-native and the addon-ondevice-* packages release together but were not grouped, so bumping one of them alone passed the check. @storybook/jest, @storybook/testing-library and @storybook/addon-react-native-web were removed from the root as unused. Banning them stops them coming back.
syncpack fix rewrites the manifests but not package-lock.json, so npm ci then failed in CI until someone ran npm install by hand.
A glob with no wildcard (package.json) is its own static prefix, so the drift check tried to walk it as a folder and reported a dead trigger for a file that exists.
Where a package belongs (root devDependencies, a lib's dependencies, or a peer), which range to write, how to declare an exception and how to fix each check:deps / check:peer-deps / @nx/dependency-checks failure. Its Review checks table replaces the one-line dependency tip in pr-review, which now routes package.json changes to it. release-plan now says a lib's package.json is not exempt from plan:check; it only mentioned libs/*/src/.
Entries were bare package names. Each is now { name, reason }, the same way
every syncpack exception carries a label, and a test fails on an empty
reason.
The sandbox lints with the workspace's shared config; nothing imports this preset. It brought ESLint 8 era plugins whose peers our ESLint 9 and @typescript-eslint 8 failed: 36 packages fewer, 2 of the 3 invalid peers gone.
Nx inferred a test-storybook target from it, but no CI job, script or doc ran it, Playwright was never installed for it, and Chromatic already renders every story and runs its play functions. Storybook 10 points Vite projects to @storybook/addon-vitest for story tests. It shipped its own jest 30 next to our jest 29: 173 packages fewer, one jest, and no invalid peer left in npm ls.
22.23.4 was set in .mise.toml, as the setup action's default, and again in the agent-drift job. The action said it defaulted to .mise.toml but hardcoded the value. It now reads it from there, and agent-drift uses the action with install off, so a Node bump edits one file.
…libs Each published lib repeated the same block: files, parser, severity and the dev-only files to skip. defineDependencyChecks() in eslint.shared.mjs now holds that, and a lib passes only its own extra files and ignored dependencies, with their reasons. A helper rather than a prodConfig entry because ESLint replaces rule options instead of merging them. utils-shared and lint-plugin now match package.json like the others instead of **/*.json; their other JSON files were already prettier-clean.
Every use-case folder runs in CI, so they now sit under ci/, leaving room for other groups (local codegen, release helpers) next to it. shared/ stays at the top since any group may import it.
zel-kass
approved these changes
Oct 28, 2026
aquelemiguel
approved these changes
Oct 28, 2026
|
This branch was successfully deployed
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.




Context
Our 11
package.jsonfiles declared the same packages at different versions, and nothing caught it. For example, two Storybook versions were installed side by side. This PR enforces one version per package across dependencies, devDependencies and peers. A second version needs an explicit, labeled exception.No published package changes. The
libs/*manifests are identical tomain, so there is no version plan and no release.Closes DLS-997
Technical details
New checks (CI job
dependency-alignment)npm run check:depsnpm run check:peer-deps@nx/dependency-checksFix drift with
npm run fix:deps, which also refreshes the lockfile.Config:
.syncpackrc.jsondependenciesmust use^.*for the local libs.@storybook/jest,@storybook/testing-library,@storybook/addon-react-native-web.The peer validator lists the two ui-rnative peers already published without an upper bound (
react-native-gesture-handler,expo), each with a reason. An entry fails once its range is bounded.Cleanup
@types/jestand@babel/corematch the root.@storybook/test-runner: nothing ran it, and Chromatic already renders every story.@react-native/eslint-config.npm lsreports no invalid peer.Maintenance
internals/repo-toolsis split into one folder per use case, named after its npm script or workflow, grouped underci/:check-agent-docs/,check-package-exports/,check-peer-deps/,fix-lcov-paths/,publish-dev-packages/. Helpers sit inshared/.@nx/dependency-checksconfig is shared throughdefineDependencyChecks()ineslint.shared.mjs. Each lib passes only its own exceptions, with their reasons..mise.tomlinstead of hardcoding it in two more places.dependency-managementskill: where a package goes, which range to write, how to add an exception, and how to fix each failing check.pr-reviewroutespackage.jsonchanges to it.Not in this PR
legacy-peer-deps: no peer conflict is left, but npm would then install missing peers, includingdetox(about 318 packages). Separate PR.How to test
npm ci, thennpm run check:depsandnpm run check:peer-deps: both pass.react-native-svgpeer →^14.0.0): the matching check fails.🤖 Generated with Claude Code