Description
Self-service
Describe the bug
➜ yarn explain peer-requirements p77551
Package @services/fx-account@workspace:services/fx-account is requested to provide webpack by its descendants
@services/fx-account@workspace:services/fx-account
├─ @sentry/nextjs@npm:7.81.1 [3b802] (via >= 4.0.0)
└─ swc-loader@npm:0.2.6 [dde53] (via >=2)
✘ Package @services/fx-account@workspace:services/fx-account provides webpack with version 5.89.0, which does not satisfy all requests.
As shown above, running yarn explain peer-requirements p77551
produces a warning (✘) even though all peer requirements are actually satisfied.
This happens because PeerRequirementNodes and PeerWarnings are using the same hash (e.g., p77551).
This hash is not unique enough because it only uses the first 5 characters of the generated hash.
As a result, a warning for one node can be incorrectly matched and be displayed for a completely unrelated node.
example:
In Yarn, the peerDependency warning system is handled in the file:
packages/plugin-essentials/sources/commands/explain/peerRequirements.ts
In this file, Yarn checks if there is a warning with the following code:
const warning = project.peerWarnings.find(warning => {
return warning.hash === peerRequirement.hash;
});
To investigate incorrect warnings from Yarn, I checked both peerWarnings and peerRequirementNodes.
Actual warning object, stored in Project.peerWarnings
{
"type": 0,
"subject": {
"scope": "storybook",
"name": "addon-controls",
"locatorHash": "61b34ed9e34c479741834adb88dbc551dbd47391cfd8d80d10ba95c2058f8311e20efd820309a53a01268a901f61187bd9565794d7deba3c2ee5b4935afdd04c",
"reference": "npm:7.6.20"
// ... (truncated)
},
"requested": {
"name": "react-dom",
"range": "^16.8.0 || ^17.0.0 || ^18.0.0"
// ... (truncated)
},
"requester": {
"scope": "radix-ui",
"name": "react-dismissable-layer",
"locatorHash": "469ff45924bb5edad51a5930bb49d282566ee6e260fe7e60f34bb5f6724a6a0eb214b2addbebe9e84bfc0b0aab2d58dd9f0812026efded53f14f5eae297ab5f5"
// ... (truncated)
},
"hash": "p77551"
}
Actual requirement node object, stored in Project.peerRequirementNodes
{
"subject": {
"scope": "services",
"name": "fx-account",
"locatorHash": "3b802f815f60042a4f3e4d808eeb3c7b3ae7d5c5940768b7e4a6a00c2f6639212318dc44dbd8a5695124638eb65d7a2c6fe8096289e4a6f55d8b817f5d1aeac1",
"reference": "workspace:services/fx-account"
// ... (truncated)
},
"ident": {
"name": "webpack",
"range": ">= 4.0.0"
// ... (truncated)
},
"provided": {
"name": "webpack",
"range": "virtual:3b802f815f60042a4f3e4d808eeb3c7b3ae7d5c5940768b7e4a6a00c2f6639212318dc44dbd8a5695124638eb65d7a2c6fe8096289e4a6f55d8b817f5d1aeac1#npm:^5.89.0"
// ... (truncated)
},
"root": true,
"requests": {},
"hash": "p77551"
}
As shown in the result above, peerWarnings and peerRequirementNodes are completely different, but they have the same hash(p77551).
So I looked into the hash generator code.
peerWarnings hash generation:
const hash = `p${hashUtils.makeHash(requirement.subject.locatorHash, structUtils.stringifyIdent(requirement.ident), peerRequest.requester.locatorHash).slice(0, 5)}`;
If you plug the peerWarnings object given in the example above into this, the following result comes out.
hashUtils.makeHash(`61b34ed9e34c479741834adb88dbc551dbd47391cfd8d80d10ba95c2058f8311e20efd820309a53a01268a901f61187bd9565794d7deba3c2ee5b4935afdd04c`, `react-dom`, `469ff45924bb5edad51a5930bb49d282566ee6e260fe7e60f34bb5f6724a6a0eb214b2addbebe9e84bfc0b0aab2d58dd9f0812026efded53f14f5eae297ab5f5`)
// 775516f799063a97356f1eb0dd1b0077c8bdffea2a866e0ec48164f635398f91ceda3883a9a54fca8e35cccb87dfcc2b665408853318a12bf0fe7d482ef3f7ed
peerRequirementNodes hash generation:
hash: `p${hashUtils.makeHash(parentLocator.locatorHash, peerDescriptor.identHash).slice(0, 5)}`,
If you plug the peerRequirementNodes object given in the example above into this, the following result comes out.
hashUtils.makeHash(`3b802f815f60042a4f3e4d808eeb3c7b3ae7d5c5940768b7e4a6a00c2f6639212318dc44dbd8a5695124638eb65d7a2c6fe8096289e4a6f55d8b817f5d1aeac1`, `89d6e678e21f2dae30fdf22225647a7491eb218ab40d455b12b1520c7e9e7f77c3eb39e01237d2b6262f8ab528b9d395bcfea571ed877b179e31e5b19fac3983`)
// 7755104b482df1039842fb4892b0de5c130e520ca85ae0bf4f1691f552011859ed0dc3a564ff4261b9b81613279e03a39dd7fdb349bc85df8a865930b6047bfd
- peerWarnings hash: 775516f799063a97356f1eb0dd1b0077c8bdffea2a866e0ec48164f635398f91ceda3883a9a54fca8e35cccb87dfcc2b665408853318a12bf0fe7d482ef3f7ed
- peerRequirementNodes hash : 7755104b482df1039842fb4892b0de5c130e520ca85ae0bf4f1691f552011859ed0dc3a564ff4261b9b81613279e03a39dd7fdb349bc85df8a865930b6047bfd
The result is completely different, but if you slice only the first 5 characters, it gives the same value. Because of this, an incorrect peer warning gets matched.
To reproduce
- In a project with many peer dependencies, run
yarn explain peer-requirements
and look for hashes that appear more than once. - Run
yarn explain peer-requirements <hash>
for one of these hashes where the requirement is actually satisfied. - Observe that a warning (✘) is incorrectly displayed.
Environment
System:
OS: macOS 14.3.1
CPU: (10) arm64 Apple M1 Max
Binaries:
Node: 20.19.2 - /private/var/folders/q0/df6zb1ks3hd34mgzqwdhlq6hw1nrvx/T/xfs-cf845975/node
Yarn: 4.9.2-dev - /private/var/folders/q0/df6zb1ks3hd34mgzqwdhlq6hw1nrvx/T/xfs-cf845975/yarn
npm: 10.8.2 - ~/Library/Caches/fnm_multishells/56147_1750227957146/bin/npm
bun: 1.2.7 - /opt/homebrew/bin/bun
Additional context
No response