fix: throw a frozen lockfile error when catalogs change (#10231)

* fix: throw a frozen lockfile error when catalogs change

* fix: work around lockfile mismatch when installing `__fixtures__`

```
> @ step1 /home/runner/work/pnpm/pnpm/__fixtures__
> node ../pnpm/dist/pnpm.mjs install -rf --frozen-lockfile --no-shared-workspace-lockfile --no-link-workspace-packages

.                                        |  WARN  using --force I sure hope you know what you are doing
Scope: all 26 workspace projects
circular                                 | Progress: resolved 1, reused 0, downloaded 0, added 0
circular                                 |   +4 +
fixture                                  | Progress: resolved 1, reused 0, downloaded 0, added 0
fixture                                  |  +12 +
fixture-with-no-pkg-name-and-no-version  | Progress: resolved 1, reused 0, downloaded 0, added 0
fixture-with-no-pkg-name-and-no-version  |  +12 +
fixture-with-no-pkg-version              | Progress: resolved 1, reused 0, downloaded 0, added 0
fixture-with-no-pkg-version              |  +12 +
circular                                 | Progress: resolved 4, reused 0, downloaded 4, added 4, done
fixture                                  | Progress: resolved 12, reused 6, downloaded 6, added 12, done
fixture-with-no-pkg-name-and-no-version  | Progress: resolved 12, reused 0, downloaded 0, added 12, done
fixture-with-no-pkg-version              | Progress: resolved 12, reused 0, downloaded 0, added 12, done
general                                  | Progress: resolved 1, reused 0, downloaded 0, added 0
general                                  |  +13 +
has-2-outdated-deps                      | Progress: resolved 1, reused 0, downloaded 0, added 0
has-2-outdated-deps                      |   +2 +
undefined
/home/runner/work/pnpm/pnpm/__fixtures__/has-outdated-deps-using-catalog-protocol:
 ERR_PNPM_LOCKFILE_CONFIG_MISMATCH  Cannot proceed with the frozen installation. The current "catalogs" configuration doesn't match the value found in the lockfile

Update your lockfile using "pnpm install --no-frozen-lockfile"
```

close #9369
This commit is contained in:
Brandon Cheng
2025-11-25 19:09:37 -05:00
committed by GitHub
parent 1e6de2539b
commit 69ebe38764
10 changed files with 80 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
---
"@pnpm/lockfile.settings-checker": patch
"@pnpm/lockfile.verification": patch
"@pnpm/core": patch
"@pnpm/deps.status": patch
---
Properly throw a frozen lockfile error when changing catalogs defined in `pnpm-workspace.yaml` and running `pnpm install --frozen-lockfile`. This previously passed silently as reported in [#9369](https://github.com/pnpm/pnpm/issues/9369).

View File

@@ -18,3 +18,7 @@ packages:
- '!workspace-has-shared-package-lock-json'
- '!workspace-has-shared-npm-shrinkwrap-json'
sharedWorkspaceLockfile: false
catalog:
# Used in has-outdated-deps-using-catalog-protocol fixture.
is-negative: ^1.0.0

View File

@@ -488,6 +488,7 @@ async function assertWantedLockfileUpToDate (
])
const outdatedLockfileSettingName = getOutdatedLockfileSetting(wantedLockfile, {
catalogs: config.catalogs,
autoInstallPeers: config.autoInstallPeers,
injectWorkspacePackages: config.injectWorkspacePackages,
excludeLinksFromLockfile: config.excludeLinksFromLockfile,

View File

@@ -33,8 +33,10 @@
"compile": "tsc --build && pnpm run lint --fix"
},
"dependencies": {
"@pnpm/catalogs.types": "workspace:*",
"@pnpm/crypto.hash": "workspace:*",
"@pnpm/lockfile.types": "workspace:*",
"@pnpm/lockfile.verification": "workspace:*",
"@pnpm/parse-overrides": "workspace:*",
"p-map-values": "catalog:",
"ramda": "catalog:"

View File

@@ -1,7 +1,10 @@
import { type Catalogs } from '@pnpm/catalogs.types'
import { type LockfileObject, type PatchFile } from '@pnpm/lockfile.types'
import { allCatalogsAreUpToDate } from '@pnpm/lockfile.verification'
import { equals } from 'ramda'
export type ChangedField =
| 'catalogs'
| 'patchedDependencies'
| 'overrides'
| 'packageExtensionsChecksum'
@@ -15,6 +18,7 @@ export type ChangedField =
export function getOutdatedLockfileSetting (
lockfile: LockfileObject,
{
catalogs,
overrides,
packageExtensionsChecksum,
ignoredOptionalDependencies,
@@ -25,6 +29,7 @@ export function getOutdatedLockfileSetting (
pnpmfileChecksum,
injectWorkspacePackages,
}: {
catalogs?: Catalogs
overrides?: Record<string, string>
packageExtensionsChecksum?: string
patchedDependencies?: Record<string, PatchFile>
@@ -36,6 +41,9 @@ export function getOutdatedLockfileSetting (
injectWorkspacePackages?: boolean
}
): ChangedField | null {
if (!allCatalogsAreUpToDate(catalogs ?? {}, lockfile.catalogs)) {
return 'catalogs'
}
if (!equals(lockfile.overrides ?? {}, overrides ?? {})) {
return 'overrides'
}

View File

@@ -12,6 +12,9 @@
{
"path": "../../__utils__/prepare"
},
{
"path": "../../catalogs/types"
},
{
"path": "../../config/parse-overrides"
},
@@ -20,6 +23,9 @@
},
{
"path": "../types"
},
{
"path": "../verification"
}
]
}

View File

@@ -1,4 +1,5 @@
export { allProjectsAreUpToDate } from './allProjectsAreUpToDate.js'
export { allCatalogsAreUpToDate } from './allCatalogsAreUpToDate.js'
export { getWorkspacePackagesByDirectory } from './getWorkspacePackagesByDirectory.js'
export { localTarballDepsAreUpToDate } from './localTarballDepsAreUpToDate.js'
export { linkedPackagesAreUpToDate } from './linkedPackagesAreUpToDate.js'

View File

@@ -410,6 +410,7 @@ export async function mutateModules (
if (!opts.ignorePackageManifest) {
const outdatedLockfileSettingName = getOutdatedLockfileSetting(ctx.wantedLockfile, {
autoInstallPeers: opts.autoInstallPeers,
catalogs: opts.catalogs,
injectWorkspacePackages: opts.injectWorkspacePackages,
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
peersSuffixMaxLength: opts.peersSuffixMaxLength,

View File

@@ -256,6 +256,49 @@ test('lockfile is updated if catalog config changes', async () => {
})
})
test('frozen lockfile error is thrown if catalog config changes', async () => {
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([
{
name: 'project1',
dependencies: {
'is-positive': 'catalog:',
},
},
])
await mutateModules(installProjects(projects), {
...options,
lockfileOnly: true,
catalogs: {
default: {
'is-positive': '=1.0.0',
},
},
})
expect(readLockfile().importers['project1' as ProjectId]).toEqual({
dependencies: {
'is-positive': {
specifier: 'catalog:',
version: '1.0.0',
},
},
})
const frozenLockfileMutation = mutateModules(installProjects(projects), {
...options,
lockfileOnly: true,
frozenLockfile: true,
catalogs: {
default: {
'is-positive': '=3.1.0',
},
},
})
await expect(frozenLockfileMutation).rejects.toThrow('Cannot proceed with the frozen installation. The current "catalogs" configuration doesn\'t match the value found in the lockfile')
})
test('lockfile catalog snapshots retain existing entries on --filter', async () => {
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([
{

6
pnpm-lock.yaml generated
View File

@@ -4012,12 +4012,18 @@ importers:
lockfile/settings-checker:
dependencies:
'@pnpm/catalogs.types':
specifier: workspace:*
version: link:../../catalogs/types
'@pnpm/crypto.hash':
specifier: workspace:*
version: link:../../crypto/hash
'@pnpm/lockfile.types':
specifier: workspace:*
version: link:../types
'@pnpm/lockfile.verification':
specifier: workspace:*
version: link:../verification
'@pnpm/parse-overrides':
specifier: workspace:*
version: link:../../config/parse-overrides