From ef4916804673e64a2b84028e88b2562a3f72e99a Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 29 Oct 2026 20:08:37 +0900 Subject: [PATCH 1/2] fix(cli): classify pnpm sibling workspace links as workspace packages [RED-1312] pnpm writes importer link: targets relative to the importer's directory, so a member linking a sibling (link:../design-system from packages/e2e) was treated as a link outside the workspace. Embed patterns matching such a member then warned that it was skipped, or failed validation with an "outside the workspace" error. Resolve the target against the importer path before the escape check. Co-Authored-By: Claude Opus 5.5 --- .../__tests__/lockfile-packages.spec.ts | 30 +++++++++++++++++++ .../embedded-packages/lockfile-packages.ts | 11 +++++-- 2 files changed, 38 insertions(+), 3 deletions(-) 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..3c3a7619 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,36 @@ 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, From 88fbe84fa5a2ccb9640a2e74542bc108752a4c43 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Thu, 29 Oct 2026 20:35:12 +0900 Subject: [PATCH 2/2] test(cli): separate the sibling-link spec with a blank line [RED-1312] Co-Authored-By: Claude Opus 5.5 --- .../embedded-packages/__tests__/lockfile-packages.spec.ts | 1 + 1 file changed, 1 insertion(+) 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 3c3a7619..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,7 @@ packages: {} { name: '@acme/outside', kind: 'unfetchable' }, ]) }) + it('resolves link targets against the importer directory', () => { const { excluded } = parsePnpmLockfilePackages(` lockfileVersion: '9.0'