fix(yarn-berry): prune workspace devDependencies from prod graph#311
Open
andreipopa-who wants to merge 2 commits into
Open
fix(yarn-berry): prune workspace devDependencies from prod graph#311andreipopa-who wants to merge 2 commits into
andreipopa-who wants to merge 2 commits into
Conversation
5 tasks
9 tasks
| }) | ||
| : {}; | ||
|
|
||
| const workspaceManifestFromResolution = workspacePackages?.[name]; |
Member
There was a problem hiding this comment.
Correct me if wrong but here we key by name but if a customer uses an alias like
"some-package": "npm:@alias/other-name@1"
Then if we pass in the alias to getYarnLockV2ChildNode as we pass in whatever the parents know the pkg as. Then we will be returning undefined for something we have an answer too.
Should we be respecting the alias here or am I overthinking it?
Collaborator
Author
There was a problem hiding this comment.
Good catch ! 🦾
This commit should solve the problem 👍
8d05f54
| }) | ||
| : {}; | ||
|
|
||
| const workspaceManifest = workspacePackages?.[name]; |
Yarn Berry merges a workspace package's dependencies and devDependencies into a single `dependencies` block in yarn.lock, dropping the dev marker. When a workspace package is consumed as a production dependency, the yarn-lock-v2 builder walked that whole block and inherited the parent's prod scope, promoting the consumed package's dev-only tooling (webpack, babel, ...) into the production graph as false positives. Give the builder the consumed member's own package.json dependency groups via the new optional `YarnLockV2WorkspaceArgs.workspacePackages` map. When a child resolves to a `@workspace:` node and `includeDevDeps` is false, drop the dev-only entries (names in devDependencies but not in dependencies/optional/peer; prod wins on overlap). `WorkspacePackageManifest` is exported from the package entry so consumers can build the map type-safely. Backward-compatible: with no `workspacePackages` provided, behavior is unchanged. Tests: 358/358 pass (incl. regression tests built from a workspace fixture).
5131405 to
08a9545
Compare
The workspace dev-dependency prune looked up the consumed member's manifest by the parent's name for the dependency. For an npm alias (e.g. "alias": "npm:@scope/real-pkg@1") that name is the alias, not the package's real name, so the lookup missed the workspacePackages entry (keyed by the real name) and pruning was skipped. Key both lookups by the resolved name (depInfo.alias.aliasTargetDepName when aliased) — the same value the child node's `name` field already uses, and add a unit test covering an aliased workspace package.
JamesPatrickGill
approved these changes
Jun 10, 2026
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.
Yarn Berry merges a workspace package's dependencies and devDependencies into a single
dependenciesblock in yarn.lock, dropping the dev marker. When a workspace package is consumed as a production dependency, the yarn-lock-v2 builder walked that whole block and inherited the parent's prod scope, promoting the consumed package's dev-only tooling (webpack, babel, ...) into the production graph as false positives.Give the builder the consumed member's own package.json dependency groups via the new optional
YarnLockV2WorkspaceArgs.workspacePackagesmap. When a child resolves to a@workspace:node andincludeDevDepsis false, drop the dev-only entries (names in devDependencies but not in dependencies/optional/peer; prod wins on overlap).Backward-compatible: with no
workspacePackagesprovided, behavior is unchanged.What this does
Fixes false positives in Yarn Berry (v2/3/4) workspaces, where a consumed workspace package's dev-only build tooling (
webpack,babel, ...) was reported as production dependencies of the consumer. Root cause:yarn.lockflattens a workspace member'sdependencies+devDependenciesinto a singledependenciesblock with no dev marker, andgetYarnLockV2ChildNodewalked the whole block while inheriting the parent's prod scope (noincludeDevDepsgate). The npm and pnpm parsers are unaffected because those lockfiles keep dev deps separate — so the fix belongs in the yarn-berry builder.types.ts: add optionalworkspacePackagestoYarnLockV2WorkspaceArgs(+WorkspacePackageManifest).yarn-lock-v2/utils.ts: newpruneWorkspaceDevDependencieshelper;getYarnLockV2ChildNodeprunes dev-only deps when the child is a@workspace:node andincludeDevDepsis false — in both the resolution and the normal branch.yarn-lock-v2/build-depgraph-simple.ts: threadincludeDevDeps+workspacePackagesthroughdfsVisit.Notes for the reviewer
resolutionfield (...@workspace:) onNormalisedPkgs, so no extract change is needed. Pruning drops names in the member'sdevDependenciesthat are not also independencies/optionalDependencies/peerDependencies(prod wins on overlap).npm run test:jest. New regression tests live intest/jest/dep-graph-builders/yarn-lock-v2.test.ts(fixturetest/jest/dep-graph-builders/fixtures/yarn-lock-v2/real/workspace-dev-deps/) and assert the leak without the map, the fix with it, and thatincludeDevDeps: trueis unaffected. Full suite: 358/358.workspacePackagesfrom each member'spackage.json. This PR is backward-compatible, so it can land and release ahead of the plugin bump.npm:-aliased workspace keys (@demo/shared-lib@npm:*) key off the package name; nested workspace→workspace hops prune at each boundary during DFS recursion.yarn.lockis a real generated Yarn 4 lockfile (~3.3k lines); the source change is ~70 lines.More information
Screenshots
Parsing
apps/my-app(whose only prod dep is the workspace package@demo/shared-lib, which itself has only devDependencies) against the rootyarn.lock,includeDevDeps: false:@demo/shared-lib)