fix: reference root manifest directory instead of the dependent dir (#9132)

* fix: reference root manifest directory instead of the dependent dir

This fixes https://github.com/pnpm/pnpm/issues/9066.

* refactor: link test

* docs: add changeset

---------

Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
R.P. Pedraza
2025-02-25 09:15:38 +08:00
committed by GitHub
parent 41dada429b
commit 529696182f
5 changed files with 75 additions and 6 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-installation": patch
"pnpm": patch
---
`pnpm link <path>` should calculate relative path from the root of the workspace directory [#9132](https://github.com/pnpm/pnpm/pull/9132).

View File

@@ -39,6 +39,7 @@
"@pnpm/test-fixtures": "workspace:*",
"@pnpm/test-ipc-server": "workspace:*",
"@pnpm/workspace.filter-packages-from-dir": "workspace:*",
"@types/normalize-path": "catalog:",
"@types/proxyquire": "catalog:",
"@types/ramda": "catalog:",
"@types/sinon": "catalog:",
@@ -47,6 +48,7 @@
"@types/zkochan__table": "catalog:",
"delay": "catalog:",
"jest-diff": "catalog:",
"normalize-path": "catalog:",
"path-name": "catalog:",
"proxyquire": "catalog:",
"read-yaml-file": "catalog:",

View File

@@ -140,7 +140,7 @@ export async function handler (
await checkPeerDeps(cwd, opts)
const newManifest = opts.rootProjectManifest ?? {}
await addLinkToManifest(opts, newManifest, cwd, linkOpts.dir)
await addLinkToManifest(opts, newManifest, cwd, opts.rootProjectManifestDir)
await writeProjectManifest(newManifest)
await install.handler({
...linkOpts,
@@ -157,7 +157,7 @@ export async function handler (
const newManifest = opts.rootProjectManifest ?? {}
await Promise.all(
pkgPaths.map(async (dir) => {
await addLinkToManifest(opts, newManifest, dir, opts.dir)
await addLinkToManifest(opts, newManifest, dir, opts.rootProjectManifestDir)
await checkPeerDeps(dir, opts)
})
)
@@ -170,7 +170,7 @@ export async function handler (
})
}
async function addLinkToManifest (opts: ReadProjectManifestOpts, manifest: ProjectManifest, linkedDepDir: string, dependentDir: string) {
async function addLinkToManifest (opts: ReadProjectManifestOpts, manifest: ProjectManifest, linkedDepDir: string, manifestDir: string) {
if (!manifest.pnpm) {
manifest.pnpm = {
overrides: {},
@@ -180,7 +180,7 @@ async function addLinkToManifest (opts: ReadProjectManifestOpts, manifest: Proje
}
const { manifest: linkedManifest } = await tryReadProjectManifest(linkedDepDir, opts)
const linkedPkgName = linkedManifest?.name ?? path.basename(linkedDepDir)
const linkedPkgSpec = `link:${path.relative(dependentDir, linkedDepDir)}`
const linkedPkgSpec = `link:${path.relative(manifestDir, linkedDepDir)}`
manifest.pnpm.overrides![linkedPkgName] = linkedPkgSpec
if (DEPENDENCIES_FIELDS.every((depField) => manifest[depField]?.[linkedPkgName] == null)) {
manifest.dependencies = manifest.dependencies ?? {}

View File

@@ -1,8 +1,8 @@
import fs from 'fs'
import path from 'path'
import { install, link } from '@pnpm/plugin-commands-installation'
import { prepare, preparePackages } from '@pnpm/prepare'
import { isExecutable } from '@pnpm/assert-project'
import { prepare, preparePackages, prepareEmpty } from '@pnpm/prepare'
import { isExecutable, assertProject } from '@pnpm/assert-project'
import { fixtures } from '@pnpm/test-fixtures'
import { logger } from '@pnpm/logger'
import { sync as loadJsonFile } from 'load-json-file'
@@ -10,6 +10,8 @@ import PATH from 'path-name'
import writePkg from 'write-pkg'
import { DEFAULT_OPTS } from './utils'
import { type PnpmError } from '@pnpm/error'
import { sync as writeYamlFile } from 'write-yaml-file'
import normalize from 'normalize-path'
const f = fixtures(__dirname)
@@ -355,3 +357,56 @@ test('link: fail when global bin directory is not found', async () => {
}
expect(err.code).toBe('ERR_PNPM_NO_GLOBAL_BIN_DIR')
})
test('relative link from workspace package', async () => {
prepareEmpty()
const rootProjectManifest = {
name: 'project',
version: '1.0.0',
dependencies: {
'@pnpm.e2e/hello-world-js-bin': '*',
},
}
await writePkg('workspace/packages/project', rootProjectManifest)
const workspaceDir = path.resolve('workspace')
writeYamlFile(path.join(workspaceDir, 'pnpm-workspace.yaml'), { packages: ['packages/*'] })
f.copy('hello-world-js-bin', 'hello-world-js-bin')
const projectDir = path.resolve('workspace/packages/project')
const helloWorldJsBinDir = path.resolve('hello-world-js-bin')
process.chdir(projectDir)
await link.handler({
...DEFAULT_OPTS,
dedupeDirectDeps: false,
dir: process.cwd(),
globalPkgDir: '',
lockfileDir: workspaceDir,
rootProjectManifest,
rootProjectManifestDir: workspaceDir,
workspaceDir,
workspacePackagePatterns: ['packages/*'],
}, ['../../../hello-world-js-bin'])
const manifest = loadJsonFile<{ pnpm?: { overrides?: Record<string, string> } }>(path.join(workspaceDir, 'package.json'))
expect(normalize(manifest.pnpm?.overrides?.['@pnpm.e2e/hello-world-js-bin'] ?? '')).toBe('link:../hello-world-js-bin')
const workspace = assertProject(workspaceDir)
;[workspace.readLockfile(), workspace.readCurrentLockfile()].forEach(lockfile => {
expect(normalize(lockfile.importers['.'].dependencies?.['@pnpm.e2e/hello-world-js-bin'].version ?? ''))
.toBe('link:../hello-world-js-bin')
expect(normalize(lockfile.importers['packages/project'].dependencies?.['@pnpm.e2e/hello-world-js-bin'].version ?? ''))
.toBe('link:../../../hello-world-js-bin')
})
const validateSymlink = (basePath: string) => {
process.chdir(path.join(basePath, 'node_modules', '@pnpm.e2e'))
expect(path.resolve(fs.readlinkSync('hello-world-js-bin'))).toBe(helloWorldJsBinDir)
}
validateSymlink(workspaceDir)
validateSymlink(projectDir)
})

6
pnpm-lock.yaml generated
View File

@@ -5370,6 +5370,9 @@ importers:
'@pnpm/workspace.filter-packages-from-dir':
specifier: workspace:*
version: link:../../workspace/filter-packages-from-dir
'@types/normalize-path':
specifier: 'catalog:'
version: 3.0.2
'@types/proxyquire':
specifier: 'catalog:'
version: 1.3.31
@@ -5394,6 +5397,9 @@ importers:
jest-diff:
specifier: 'catalog:'
version: 29.7.0
normalize-path:
specifier: 'catalog:'
version: 3.0.0
path-name:
specifier: 'catalog:'
version: 1.0.0