fix: use headless install for injected self-referencing file: dependencies (#10714)

When a package has an injected self-referencing dependency (e.g.
"pkg": "file:" with dependenciesMeta injected: true), the lockfile
resolves it as "link:". The linkedPackagesAreUpToDate() function
incorrectly reported these projects as not up-to-date because
refToRelative() returns null for link: refs, causing pnpm to skip
the fast headless install path and do full resolution instead.
This commit is contained in:
Zoltan Kochan
2026-02-28 01:39:42 +01:00
committed by GitHub
parent cb49a64af7
commit 521e4a6ec9
3 changed files with 56 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/lockfile.verification": patch
"pnpm": patch
---
Fix headless install not being used when a project has an injected self-referencing `file:` dependency that resolves to `link:` in the lockfile.

View File

@@ -52,6 +52,10 @@ export async function linkedPackagesAreUpToDate (
if (!currentSpec) return true
const lockfileRef = lockfileDeps[depName]
if (refIsLocalDirectory(project.snapshot.specifiers[depName])) {
// When a file: specifier resolves to link: in the lockfile
// (e.g. injected self-references), it's a local link with no
// entry in the packages section. Treat it as up-to-date.
if (lockfileRef.startsWith('link:')) return true
const depPath = refToRelative(lockfileRef, depName)
return depPath != null && isLocalFileDepUpdated(lockfileDir, lockfilePackages?.[depPath])
}

View File

@@ -867,6 +867,52 @@ test('allProjectsAreUpToDate(): returns true if one of the importers is not pres
})).toBeTruthy()
})
test('allProjectsAreUpToDate(): returns true for injected self-referencing file: dependency resolved as link:', async () => {
expect(await allProjectsAreUpToDate([
{
id: 'can-link' as ProjectId,
manifest: {
name: 'can-link',
version: '2.0.0',
dependenciesMeta: {
'can-link': {
injected: true,
},
},
devDependencies: {
'can-link': 'file:',
},
},
rootDir: 'can-link' as ProjectRootDir,
},
], {
autoInstallPeers: false,
catalogs: {},
excludeLinksFromLockfile: false,
linkWorkspacePackages: false,
wantedLockfile: {
importers: {
['can-link' as ProjectId]: {
dependenciesMeta: {
'can-link': {
injected: true,
},
},
devDependencies: {
'can-link': 'link:',
},
specifiers: {
'can-link': 'file:',
},
},
},
lockfileVersion: LOCKFILE_VERSION,
},
workspacePackages: new Map(),
lockfileDir: '',
})).toBeTruthy()
})
test('allProjectsAreUpToDate(): returns false if the lockfile is broken, the resolved versions do not satisfy the ranges', async () => {
expect(await allProjectsAreUpToDate([
{