Skip to content

Commit c43f66d

Browse files
danezarcanis
authored andcommitted
Correctly install workspace child deps when workspace child not symlinked to root (#7289)
* Test for conflict between pkg versions in root and workspace child This test creates the scenario where the child is not symlinked into the root node_modules folder, because the root installs a different version of the workspace child from the registry. * fix(hoister): Correctly change path for ws child dependencies when child not hoisted * Update CHANGELOG.md * Better comment
1 parent 1ec3190 commit c43f66d

File tree

8 files changed

+67
-11
lines changed

8 files changed

+67
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa
44

55
## Master
66

7+
- Correctly installs workspace child dependencies when workspace child not symlinked to root.
8+
9+
[#7289](https://github.com/yarnpkg/yarn/pull/7289) - [**Daniel Tschinder**](https://github.com/danez)
10+
711
- Makes running scripts with Plug'n Play possible on node 13
812

913
[#7650](https://github.com/yarnpkg/yarn/pull/7650) - [**Sander Verweij**](https://github.com/sverweij)

__tests__/commands/install/workspaces-install.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,25 @@ test.concurrent('install should install unhoistable dependencies in workspace no
103103
});
104104
});
105105

106+
test.concurrent(
107+
'install should install unhoistable dependencies in workspace node_modules even when no symlink exists',
108+
(): Promise<void> => {
109+
return runInstall({}, 'workspaces-install-conflict-without-symlink', async (config): Promise<void> => {
110+
// node_modules/[email protected]
111+
let packageFile = await fs.readFile(path.join(config.cwd, 'node_modules', 'isarray', 'package.json'));
112+
expect(JSON.parse(packageFile).version).toEqual('1.0.0');
113+
114+
// node_modules/[email protected]
115+
packageFile = await fs.readFile(path.join(config.cwd, 'node_modules', 'arrify', 'package.json'));
116+
expect(JSON.parse(packageFile).version).toEqual('1.0.0');
117+
118+
// node_modules/arrify/[email protected]
119+
packageFile = await fs.readFile(path.join(config.cwd, 'arrify', 'node_modules', 'isarray', 'package.json'));
120+
expect(JSON.parse(packageFile).version).toEqual('2.0.0');
121+
});
122+
},
123+
);
124+
106125
test.concurrent('install should link workspaces that refer each other', (): Promise<void> => {
107126
return runInstall({}, 'workspaces-install-link', async (config): Promise<void> => {
108127
// packages/workspace-1/node_modules/left-pad - missing because it is hoisted to the root
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "arrify",
3+
"version": "2.0.0",
4+
"dependencies": {
5+
"isarray": "2.0.0"
6+
}
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "my-project",
3+
"private": true,
4+
"dependencies": {
5+
"arrify": "1.0.0",
6+
"isarray": "1.0.0"
7+
},
8+
"workspaces": [
9+
"arrify"
10+
]
11+
}

src/package-hoister.js

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,15 @@ export default class PackageHoister {
835835
// up of modules from different registries so we need to handle this specially
836836
const parts: Array<string> = [];
837837
const keyParts = key.split('#');
838+
const isWorkspaceEntry = this.workspaceLayout && keyParts[0] === this.workspaceLayout.virtualManifestName;
839+
840+
// Don't add the virtual manifest (keyParts.length === 1)
841+
// or ws childs which were not hoisted to the root (keyParts.length === 2).
842+
// If a ws child was hoisted its key would not contain the virtual manifest name
843+
if (isWorkspaceEntry && keyParts.length <= 2) {
844+
continue;
845+
}
846+
838847
for (let i = 0; i < keyParts.length; i++) {
839848
const key = keyParts.slice(0, i + 1).join('#');
840849
const hoisted = this.tree.get(key);
@@ -843,16 +852,26 @@ export default class PackageHoister {
843852
parts.push(keyParts[i]);
844853
}
845854

846-
const shallowLocs = [];
847-
if (this.config.modulesFolder) {
848-
// remove the first part which will be the folder name and replace it with a
849-
// hardcoded modules folder
850-
parts.splice(0, 1, this.config.modulesFolder);
855+
// Check if the destination is pointing to a sub folder of the virtualManifestName
856+
// e.g. _project_/node_modules/workspace-aggregator-123456/node_modules/workspaceChild/node_modules/dependency
857+
// This probably happened because the hoister was not able to hoist the workspace child to the root
858+
// So we have to change the folder to the workspace package location
859+
if (this.workspaceLayout && isWorkspaceEntry) {
860+
const wspPkg = this.workspaceLayout.workspaces[keyParts[1]];
861+
invariant(wspPkg, `expected workspace package to exist for "${keyParts[1]}"`);
862+
parts.splice(0, 4, wspPkg.loc);
851863
} else {
852-
// first part will be the registry-specific module folder
853-
parts.splice(0, 0, this.config.lockfileFolder);
864+
if (this.config.modulesFolder) {
865+
// remove the first part which will be the folder name and replace it with a
866+
// hardcoded modules folder
867+
parts.splice(0, 1, this.config.modulesFolder);
868+
} else {
869+
// first part will be the registry-specific module folder
870+
parts.splice(0, 0, this.config.lockfileFolder);
871+
}
854872
}
855873

874+
const shallowLocs = [];
856875
info.shallowPaths.forEach(shallowPath => {
857876
const shallowCopyParts = parts.slice();
858877
shallowCopyParts[0] = this.config.cwd;

src/package-linker.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,10 +251,6 @@ export default class PackageLinker {
251251
} else if (workspaceLayout && remote.type === 'workspace' && !isShallow) {
252252
src = remote.reference;
253253
type = 'symlink';
254-
if (dest.indexOf(workspaceLayout.virtualManifestName) !== -1) {
255-
// we don't need to install virtual manifest
256-
continue;
257-
}
258254
// to get real path for non hoisted dependencies
259255
symlinkPaths.set(dest, src);
260256
} else {

0 commit comments

Comments
 (0)