diff --git a/.changeset/fix-headless-install-injected-self-ref.md b/.changeset/fix-headless-install-injected-self-ref.md new file mode 100644 index 0000000000..0ec5b92a68 --- /dev/null +++ b/.changeset/fix-headless-install-injected-self-ref.md @@ -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. diff --git a/lockfile/verification/src/linkedPackagesAreUpToDate.ts b/lockfile/verification/src/linkedPackagesAreUpToDate.ts index f1b63fb971..97d352a4b6 100644 --- a/lockfile/verification/src/linkedPackagesAreUpToDate.ts +++ b/lockfile/verification/src/linkedPackagesAreUpToDate.ts @@ -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]) } diff --git a/lockfile/verification/test/allProjectsAreUpToDate.test.ts b/lockfile/verification/test/allProjectsAreUpToDate.test.ts index 262bfdbad2..0d2b6de1ef 100644 --- a/lockfile/verification/test/allProjectsAreUpToDate.test.ts +++ b/lockfile/verification/test/allProjectsAreUpToDate.test.ts @@ -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([ {