diff --git a/packages/cli/src/services/embedded-packages/__tests__/lockfile-packages.spec.ts b/packages/cli/src/services/embedded-packages/__tests__/lockfile-packages.spec.ts index 2e03eb66..a567f381 100644 --- a/packages/cli/src/services/embedded-packages/__tests__/lockfile-packages.spec.ts +++ b/packages/cli/src/services/embedded-packages/__tests__/lockfile-packages.spec.ts @@ -677,6 +677,37 @@ packages: {} { name: '@acme/outside', kind: 'unfetchable' }, ]) }) + + it('resolves link targets against the importer directory', () => { + const { excluded } = parsePnpmLockfilePackages(` +lockfileVersion: '9.0' +importers: + .: {} + packages/e2e: + dependencies: + '@acme/design-system': + specifier: workspace:* + version: link:../design-system + '@acme/outside': + specifier: file:../../../elsewhere + version: link:../../../elsewhere + packages/design-system: {} +packages: {} +`) + expect(excluded).toEqual([ + { + name: '@acme/design-system', + reason: `'@acme/design-system' is a workspace package, which cannot be embedded as a registry tarball`, + kind: 'workspace', + }, + { + name: '@acme/outside', + reason: `'@acme/outside' is a local directory link outside the workspace, which cannot be embedded` + + ` as a registry tarball`, + kind: 'unfetchable', + }, + ]) + }) }) describe('parseNpmLockfilePackages() links', () => { diff --git a/packages/cli/src/services/embedded-packages/lockfile-packages.ts b/packages/cli/src/services/embedded-packages/lockfile-packages.ts index e3ffc261..3ec7f2d5 100644 --- a/packages/cli/src/services/embedded-packages/lockfile-packages.ts +++ b/packages/cli/src/services/embedded-packages/lockfile-packages.ts @@ -231,7 +231,7 @@ export function parsePnpmLockfilePackages (content: string): LockfilePackages { const importers = data?.importers if (typeof importers === 'object' && importers !== null) { const linkedNames = new Set() - for (const importer of Object.values(importers)) { + for (const [importerPath, importer] of Object.entries(importers)) { for (const group of ['dependencies', 'devDependencies', 'optionalDependencies']) { for (const [name, dep] of Object.entries(importer?.[group] ?? {})) { const version = typeof dep === 'string' ? dep : dep?.version @@ -239,8 +239,13 @@ export function parsePnpmLockfilePackages (content: string): LockfilePackages { linkedNames.add(name) // Same distinction as npm's `link: true` entries: a link whose // target escapes the workspace is not part of the project the - // bundle carries. - const target = version.slice('link:'.length) + // bundle carries. pnpm writes the target relative to the + // importer's directory, so a sibling member's link + // (`link:../design-system` from `packages/e2e`) stays inside. + const linkTarget = version.slice('link:'.length) + const target = path.isAbsolute(linkTarget) + ? linkTarget + : path.posix.join(importerPath, linkTarget) const escapesWorkspace = linkTargetEscapesWorkspace(target) result.excluded.push({ name,