diff --git a/lib/commands/link.js b/lib/commands/link.js index e166a0051299a..df54cb1fcfa08 100644 --- a/lib/commands/link.js +++ b/lib/commands/link.js @@ -110,24 +110,28 @@ class Link extends ArboristWorkspaceCmd { this.npm.config.get('save-dev') || this.npm.config.get('save-prod') ) - // create a new arborist instance for the local prefix and - // reify all the pending names as symlinks there - const localArb = new Arborist({ - ...this.npm.flatOptions, - prune: false, - path: this.npm.prefix, - save, - }) - await localArb.reify({ - ...this.npm.flatOptions, - prune: false, - path: this.npm.prefix, - add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`), - save, - workspaces: this.workspaceNames, - }) + const add = names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`) + const paths = this.workspacePaths?.length ? this.workspacePaths : [this.npm.prefix] + + // Workspace-targeted npm link should create the symlink inside each selected + // workspace rather than reifying at the project root and relying on hoisting. + for (const path of paths) { + const localArb = new Arborist({ + ...this.npm.flatOptions, + prune: false, + path, + save, + }) + await localArb.reify({ + ...this.npm.flatOptions, + prune: false, + path, + add, + save, + }) - await reifyFinish(this.npm, localArb) + await reifyFinish(this.npm, localArb) + } } async linkPkg () { diff --git a/tap-snapshots/test/lib/commands/link.js.test.cjs b/tap-snapshots/test/lib/commands/link.js.test.cjs index c26e30da1ef62..c79d8b7ff21f2 100644 --- a/tap-snapshots/test/lib/commands/link.js.test.cjs +++ b/tap-snapshots/test/lib/commands/link.js.test.cjs @@ -20,12 +20,11 @@ exports[`test/lib/commands/link.js TAP link global linked pkg to local nm when u ` exports[`test/lib/commands/link.js TAP link global linked pkg to local workspace using args > should create a local symlink to global pkg 1`] = ` -{CWD}/prefix/node_modules/@myscope/bar -> {CWD}/global/node_modules/@myscope/bar -{CWD}/prefix/node_modules/@myscope/linked -> {CWD}/other/scoped-linked -{CWD}/prefix/node_modules/a -> {CWD}/global/node_modules/a -{CWD}/prefix/node_modules/link-me-too -> {CWD}/other/link-me-too -{CWD}/prefix/node_modules/test-pkg-link -> {CWD}/other/test-pkg-link -{CWD}/prefix/node_modules/x -> {CWD}/prefix/packages/x +{CWD}/prefix/packages/x/node_modules/@myscope/bar -> {CWD}/global/node_modules/@myscope/bar +{CWD}/prefix/packages/x/node_modules/@myscope/linked -> {CWD}/other/scoped-linked +{CWD}/prefix/packages/x/node_modules/a -> {CWD}/global/node_modules/a +{CWD}/prefix/packages/x/node_modules/link-me-too -> {CWD}/other/link-me-too +{CWD}/prefix/packages/x/node_modules/test-pkg-link -> {CWD}/other/test-pkg-link ` diff --git a/test/lib/commands/link.js b/test/lib/commands/link.js index 184f7d6420339..d72c42a39d84d 100644 --- a/test/lib/commands/link.js +++ b/test/lib/commands/link.js @@ -18,12 +18,12 @@ const mockLink = async (t, { globalPrefixDir, ...opts } = {}) => { }, }) - const printLinks = async ({ global = false } = {}) => { + const printLinks = async ({ global = false, path } = {}) => { let res = '' const arb = new Arborist(global ? { path: resolve(mock.npm.globalDir, '..'), global: true, - } : { path: mock.prefix }) + } : { path: path || mock.prefix }) const tree = await arb.loadActual() const linkedItems = [...tree.inventory.values()] .sort((a, b) => a.pkgid.localeCompare(b.pkgid, 'en')) @@ -190,7 +190,7 @@ t.test('link global linked pkg to local nm when using args', async t => { }) t.test('link global linked pkg to local workspace using args', async t => { - const { link, printLinks } = await mockLink(t, { + const { link, printLinks, prefix } = await mockLink(t, { globalPrefixDir: { node_modules: { '@myscope': { @@ -286,7 +286,9 @@ t.test('link global linked pkg to local workspace using args', async t => { 'file:../other/link-me-too', ]) - t.matchSnapshot(await printLinks(), 'should create a local symlink to global pkg') + t.matchSnapshot(await printLinks({ + path: resolve(prefix, 'packages/x'), + }), 'should create a local symlink to global pkg') }) t.test('link pkg already in global space', async t => {