diff --git a/__tests__/commands/install.js b/__tests__/commands/install.js index a22c13308e..7598668f6f 100644 --- a/__tests__/commands/install.js +++ b/__tests__/commands/install.js @@ -775,3 +775,14 @@ test.concurrent('install uses OS line endings when lockfile doesn\'t exist', asy assert(lockfile.indexOf(os.EOL) >= 0); }); }); + +test.concurrent('install will not overwrite files in symlinked scoped directories', async (): Promise => { + await runInstall({}, 'install-dont-overwrite-linked-scoped', async (config): Promise => { + const dependencyPath = path.join(config.cwd, 'node_modules', '@fakescope', 'fake-dependency'); + assert.equal( + 'Symlinked scoped package test', + (await fs.readJson(path.join(dependencyPath, 'package.json'))).description, + ); + assert.ok(!(await fs.exists(path.join(dependencyPath, 'index.js')))); + }); +}); diff --git a/__tests__/commands/remove.js b/__tests__/commands/remove.js index c5f482f7aa..c11b198523 100644 --- a/__tests__/commands/remove.js +++ b/__tests__/commands/remove.js @@ -135,7 +135,7 @@ test.concurrent('removes multiple installed packages', (): Promise => { test.concurrent('removes scoped packages', (): Promise => { return runRemove({}, ['@scoped/package'], 'scoped-package', async (config): Promise => { - assert(!await fs.exists(path.join(config.cwd, 'node_modules/@scoped'))); + assert(!await fs.exists(path.join(config.cwd, 'node_modules/@scoped/package'))); assert.deepEqual( JSON.parse(await fs.readFile(path.join(config.cwd, 'package.json'))).dependencies, diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.npmrc b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.npmrc new file mode 100644 index 0000000000..9465b97ac3 --- /dev/null +++ b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.npmrc @@ -0,0 +1 @@ +yarn-offline-mirror=./mirror-for-offline diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.yarn-link/@fakescope/fake-dependency b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.yarn-link/@fakescope/fake-dependency new file mode 120000 index 0000000000..a5398b4f4e --- /dev/null +++ b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/.yarn-link/@fakescope/fake-dependency @@ -0,0 +1 @@ +../../dir-to-link/ \ No newline at end of file diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/dir-to-link/package.json b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/dir-to-link/package.json new file mode 100644 index 0000000000..5de00bef5d --- /dev/null +++ b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/dir-to-link/package.json @@ -0,0 +1,7 @@ +{ + "name": "@fakescope/fake-dependency", + "description": "Symlinked scoped package test", + "version": "1.0.1", + "dependencies": {}, + "license": "MIT" +} diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/mirror-for-offline/@fakescope-fake-dependency-1.0.1.tgz b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/mirror-for-offline/@fakescope-fake-dependency-1.0.1.tgz new file mode 100644 index 0000000000..29e48dd0cd Binary files /dev/null and b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/mirror-for-offline/@fakescope-fake-dependency-1.0.1.tgz differ diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/node_modules/@fakescope/fake-dependency b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/node_modules/@fakescope/fake-dependency new file mode 120000 index 0000000000..a5398b4f4e --- /dev/null +++ b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/node_modules/@fakescope/fake-dependency @@ -0,0 +1 @@ +../../dir-to-link/ \ No newline at end of file diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/package.json b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/package.json new file mode 100644 index 0000000000..a0cf66cc64 --- /dev/null +++ b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@fakescope/fake-dependency": "1.0.1" + } +} diff --git a/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/yarn.lock b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/yarn.lock new file mode 100644 index 0000000000..ca461377b2 --- /dev/null +++ b/__tests__/fixtures/install/install-dont-overwrite-linked-scoped/yarn.lock @@ -0,0 +1,5 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 +"@fakescope/fake-dependency@1.0.1": + version "1.0.1" + resolved "@fakescope-fake-dependency-1.0.1.tgz#477dafd486d856af0b3faf5a5f1c895001221609" diff --git a/src/config.js b/src/config.js index 426653b1d1..4accbef1aa 100644 --- a/src/config.js +++ b/src/config.js @@ -179,7 +179,20 @@ export default class Config { await fs.mkdirp(this.tempFolder); await fs.mkdirp(this.linkFolder); - this.linkedModules = await fs.readdir(this.linkFolder); + + this.linkedModules = []; + + const maybeLinkedModules = await fs.readdir(this.linkFolder); + + for (const maybeLinked of maybeLinkedModules) { + // handle scoped modules separately + if (maybeLinked.indexOf('@') === 0) { + const scopedLinked = await fs.readdir(path.join(this.linkFolder, maybeLinked)); + this.linkedModules.push(...scopedLinked.map((dir) => path.join(maybeLinked, dir))); + } else { + this.linkedModules.push(maybeLinked); + } + } for (const key of Object.keys(registries)) { const Registry = registries[key]; diff --git a/src/package-linker.js b/src/package-linker.js index 346ff6fd7b..f853188c10 100644 --- a/src/package-linker.js +++ b/src/package-linker.js @@ -115,6 +115,7 @@ export default class PackageLinker { } async copyModules(patterns: Array): Promise { + let flatTree = await this.getFlatHoistedTree(patterns); // sorted tree makes file creation and copying not to interfere with each other @@ -158,7 +159,16 @@ export default class PackageLinker { if (await fs.exists(loc)) { const files = await fs.readdir(loc); for (const file of files) { - possibleExtraneous.add(path.join(loc, file)); + const filePath = path.join(loc, file); + // scoped packages are a nested one level deeper + if (file.indexOf('@') === 0) { + const scopedFiles = await fs.readdir(filePath); + for (const scoped of scopedFiles) { + possibleExtraneous.add(path.join(filePath, scoped)); + } + } else { + possibleExtraneous.add(filePath); + } } } }