diff --git a/.changeset/honest-timers-teach.md b/.changeset/honest-timers-teach.md new file mode 100644 index 0000000000..a4926088bb --- /dev/null +++ b/.changeset/honest-timers-teach.md @@ -0,0 +1,5 @@ +--- +"@pnpm/lockfile.verification": patch +--- + +Don't crash when the lockfile doesn't have a project in it during verification. diff --git a/lockfile/verification/src/allProjectsAreUpToDate.ts b/lockfile/verification/src/allProjectsAreUpToDate.ts index c1210e56ce..293c1a2348 100644 --- a/lockfile/verification/src/allProjectsAreUpToDate.ts +++ b/lockfile/verification/src/allProjectsAreUpToDate.ts @@ -20,6 +20,7 @@ import { } from '@pnpm/types' import pEvery from 'p-every' import any from 'ramda/src/any' +import isEmpty from 'ramda/src/isEmpty' import semver from 'semver' import getVersionSelectorType from 'version-selector-type' import { allCatalogsAreUpToDate } from './allCatalogsAreUpToDate' @@ -58,7 +59,11 @@ export async function allProjectsAreUpToDate ( }) return pEvery(projects, (project) => { const importer = opts.wantedLockfile.importers[project.id] - return !hasLocalTarballDepsInRoot(importer) && + if (importer == null) { + return DEPENDENCIES_FIELDS.every((depType) => project.manifest[depType] == null || isEmpty(project.manifest[depType])) + } + return importer != null && + !hasLocalTarballDepsInRoot(importer) && _satisfiesPackageManifest(importer, project.manifest).satisfies && _linkedPackagesAreUpToDate({ dir: project.rootDir, diff --git a/lockfile/verification/test/allProjectsAreUpToDate.test.ts b/lockfile/verification/test/allProjectsAreUpToDate.test.ts index 5b88d38a41..3c110df0b9 100644 --- a/lockfile/verification/test/allProjectsAreUpToDate.test.ts +++ b/lockfile/verification/test/allProjectsAreUpToDate.test.ts @@ -1,6 +1,7 @@ import { LOCKFILE_VERSION } from '@pnpm/constants' import { prepareEmpty } from '@pnpm/prepare' -import { type ProjectId, type ProjectRootDir } from '@pnpm/types' +import { type WorkspacePackages } from '@pnpm/resolver-base' +import { type DependencyManifest, type ProjectId, type ProjectRootDir } from '@pnpm/types' import { allProjectsAreUpToDate } from '@pnpm/lockfile.verification' import { writeFile, mkdir } from 'fs/promises' import { type Lockfile } from '@pnpm/lockfile.types' @@ -534,3 +535,126 @@ test('allProjectsAreUpToDate(): returns true if workspace dependency\'s version lockfileDir: process.cwd(), })).toBeTruthy() }) + +test('allProjectsAreUpToDate(): returns false if one of the importers is not present in the lockfile', async () => { + const fooManifest: DependencyManifest = { + name: 'foo', + version: '1.0.0', + dependencies: { + 'is-odd': '1.0.0', + }, + } + const barManifest: DependencyManifest = { + name: 'bar', + version: '1.0.0', + dependencies: { + 'is-even': '1.0.0', + }, + } + const workspacePackages: WorkspacePackages = new Map([ + ['foo', new Map([ + ['1.0.0', { + rootDir: 'foo' as ProjectRootDir, + manifest: fooManifest, + }], + ])], + ['bar', new Map([ + ['1.0.0', { + rootDir: 'bar' as ProjectRootDir, + manifest: barManifest, + }], + ])], + ]) + expect(await allProjectsAreUpToDate([ + { + id: 'bar' as ProjectId, + manifest: barManifest, + rootDir: 'bar' as ProjectRootDir, + }, + { + id: 'foo' as ProjectId, + manifest: fooManifest, + rootDir: 'foo' as ProjectRootDir, + }, + ], { + autoInstallPeers: false, + catalogs: {}, + excludeLinksFromLockfile: false, + linkWorkspacePackages: true, + wantedLockfile: { + importers: { + ['bar' as ProjectId]: { + dependencies: { + 'is-even': '1.0.0', + }, + specifiers: { + 'is-even': '1.0.0', + }, + }, + }, + lockfileVersion: LOCKFILE_VERSION, + }, + workspacePackages, + lockfileDir: '', + })).toBeFalsy() +}) + +test('allProjectsAreUpToDate(): returns true if one of the importers is not present in the lockfile but the importer has no dependencies', async () => { + const fooManifest: DependencyManifest = { + name: 'foo', + version: '1.0.0', + } + const barManifest: DependencyManifest = { + name: 'bar', + version: '1.0.0', + dependencies: { + 'is-even': '1.0.0', + }, + } + const workspacePackages: WorkspacePackages = new Map([ + ['foo', new Map([ + ['1.0.0', { + rootDir: 'foo' as ProjectRootDir, + manifest: fooManifest, + }], + ])], + ['bar', new Map([ + ['1.0.0', { + rootDir: 'bar' as ProjectRootDir, + manifest: barManifest, + }], + ])], + ]) + expect(await allProjectsAreUpToDate([ + { + id: 'bar' as ProjectId, + manifest: barManifest, + rootDir: 'bar' as ProjectRootDir, + }, + { + id: 'foo' as ProjectId, + manifest: fooManifest, + rootDir: 'foo' as ProjectRootDir, + }, + ], { + autoInstallPeers: false, + catalogs: {}, + excludeLinksFromLockfile: false, + linkWorkspacePackages: true, + wantedLockfile: { + importers: { + ['bar' as ProjectId]: { + dependencies: { + 'is-even': '1.0.0', + }, + specifiers: { + 'is-even': '1.0.0', + }, + }, + }, + lockfileVersion: LOCKFILE_VERSION, + }, + workspacePackages, + lockfileDir: '', + })).toBeTruthy() +})