mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
fix: catalog snapshots removed on filtered install with --fix-lockfile (#9152)
close #8639
This commit is contained in:
6
.changeset/rare-avocados-listen.md
Normal file
6
.changeset/rare-avocados-listen.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@pnpm/core": patch
|
||||
pnpm: patch
|
||||
---
|
||||
|
||||
Fix a bug causing catalog snapshots to be removed from the `pnpm-lock.yaml` file when using `--fix-lockfile` and `--filter`. [#8639](https://github.com/pnpm/pnpm/issues/8639)
|
||||
@@ -1433,74 +1433,56 @@ const installInContext: InstallFunction = async (projects, ctx, opts) => {
|
||||
const allProjectsLocatedInsideWorkspace = Object.values(ctx.projects)
|
||||
.filter((project) => isPathInsideWorkspace(project.rootDirRealPath ?? project.rootDir))
|
||||
if (allProjectsLocatedInsideWorkspace.length > projects.length) {
|
||||
if (
|
||||
allMutationsAreInstalls(projects) &&
|
||||
await allProjectsAreUpToDate(allProjectsLocatedInsideWorkspace, {
|
||||
catalogs: opts.catalogs,
|
||||
autoInstallPeers: opts.autoInstallPeers,
|
||||
excludeLinksFromLockfile: opts.excludeLinksFromLockfile,
|
||||
linkWorkspacePackages: opts.linkWorkspacePackagesDepth >= 0,
|
||||
wantedLockfile: ctx.wantedLockfile,
|
||||
workspacePackages: ctx.workspacePackages,
|
||||
lockfileDir: opts.lockfileDir,
|
||||
})
|
||||
) {
|
||||
return installInContext(projects, ctx, {
|
||||
...opts,
|
||||
frozenLockfile: true,
|
||||
})
|
||||
} else {
|
||||
const newProjects = [...projects]
|
||||
const getWantedDepsOpts = {
|
||||
autoInstallPeers: opts.autoInstallPeers,
|
||||
includeDirect: opts.includeDirect,
|
||||
updateWorkspaceDependencies: false,
|
||||
nodeExecPath: opts.nodeExecPath,
|
||||
injectWorkspacePackages: opts.injectWorkspacePackages,
|
||||
}
|
||||
const _isWantedDepPrefSame = isWantedDepPrefSame.bind(null, ctx.wantedLockfile.catalogs, opts.catalogs)
|
||||
for (const project of allProjectsLocatedInsideWorkspace) {
|
||||
if (!newProjects.some(({ rootDir }) => rootDir === project.rootDir)) {
|
||||
// This code block mirrors the installCase() function in
|
||||
// mutateModules(). Consider a refactor that combines this logic
|
||||
// to deduplicate code.
|
||||
const wantedDependencies = getWantedDependencies(project.manifest, getWantedDepsOpts)
|
||||
.map((wantedDependency) => ({ ...wantedDependency, updateSpec: true, preserveNonSemverVersionSpec: true }))
|
||||
forgetResolutionsOfPrevWantedDeps(ctx.wantedLockfile.importers[project.id], wantedDependencies, _isWantedDepPrefSame)
|
||||
newProjects.push({
|
||||
mutation: 'install',
|
||||
...project,
|
||||
wantedDependencies,
|
||||
pruneDirectDependencies: false,
|
||||
updatePackageManifest: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
const result = await installInContext(newProjects, ctx, {
|
||||
...opts,
|
||||
lockfileOnly: true,
|
||||
})
|
||||
const { stats, ignoredBuilds } = await headlessInstall({
|
||||
...ctx,
|
||||
...opts,
|
||||
currentEngine: {
|
||||
nodeVersion: opts.nodeVersion,
|
||||
pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
|
||||
},
|
||||
currentHoistedLocations: ctx.modulesFile?.hoistedLocations,
|
||||
selectedProjectDirs: projects.map((project) => project.rootDir),
|
||||
allProjects: ctx.projects,
|
||||
prunedAt: ctx.modulesFile?.prunedAt,
|
||||
wantedLockfile: result.newLockfile,
|
||||
useLockfile: opts.useLockfile && ctx.wantedLockfileIsModified,
|
||||
hoistWorkspacePackages: opts.hoistWorkspacePackages,
|
||||
})
|
||||
return {
|
||||
...result,
|
||||
stats,
|
||||
ignoredBuilds,
|
||||
const newProjects = [...projects]
|
||||
const getWantedDepsOpts = {
|
||||
autoInstallPeers: opts.autoInstallPeers,
|
||||
includeDirect: opts.includeDirect,
|
||||
updateWorkspaceDependencies: false,
|
||||
nodeExecPath: opts.nodeExecPath,
|
||||
injectWorkspacePackages: opts.injectWorkspacePackages,
|
||||
}
|
||||
const _isWantedDepPrefSame = isWantedDepPrefSame.bind(null, ctx.wantedLockfile.catalogs, opts.catalogs)
|
||||
for (const project of allProjectsLocatedInsideWorkspace) {
|
||||
if (!newProjects.some(({ rootDir }) => rootDir === project.rootDir)) {
|
||||
// This code block mirrors the installCase() function in
|
||||
// mutateModules(). Consider a refactor that combines this logic to
|
||||
// deduplicate code.
|
||||
const wantedDependencies = getWantedDependencies(project.manifest, getWantedDepsOpts)
|
||||
.map((wantedDependency) => ({ ...wantedDependency, updateSpec: true, preserveNonSemverVersionSpec: true }))
|
||||
forgetResolutionsOfPrevWantedDeps(ctx.wantedLockfile.importers[project.id], wantedDependencies, _isWantedDepPrefSame)
|
||||
newProjects.push({
|
||||
mutation: 'install',
|
||||
...project,
|
||||
wantedDependencies,
|
||||
pruneDirectDependencies: false,
|
||||
updatePackageManifest: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
const result = await installInContext(newProjects, ctx, {
|
||||
...opts,
|
||||
lockfileOnly: true,
|
||||
})
|
||||
const { stats, ignoredBuilds } = await headlessInstall({
|
||||
...ctx,
|
||||
...opts,
|
||||
currentEngine: {
|
||||
nodeVersion: opts.nodeVersion,
|
||||
pnpmVersion: opts.packageManager.name === 'pnpm' ? opts.packageManager.version : '',
|
||||
},
|
||||
currentHoistedLocations: ctx.modulesFile?.hoistedLocations,
|
||||
selectedProjectDirs: projects.map((project) => project.rootDir),
|
||||
allProjects: ctx.projects,
|
||||
prunedAt: ctx.modulesFile?.prunedAt,
|
||||
wantedLockfile: result.newLockfile,
|
||||
useLockfile: opts.useLockfile && ctx.wantedLockfileIsModified,
|
||||
hoistWorkspacePackages: opts.hoistWorkspacePackages,
|
||||
})
|
||||
return {
|
||||
...result,
|
||||
stats,
|
||||
ignoredBuilds,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (opts.nodeLinker === 'hoisted' && !opts.lockfileOnly) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type ProjectRootDir, type ProjectId, type ProjectManifest } from '@pnpm
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { addDistTag } from '@pnpm/registry-mock'
|
||||
import { type MutatedProject, mutateModules, type ProjectOptions, type MutateModulesOptions, addDependenciesToPackage } from '@pnpm/core'
|
||||
import { type CatalogSnapshots } from '@pnpm/lockfile.types'
|
||||
import { sync as loadJsonFile } from 'load-json-file'
|
||||
import path from 'path'
|
||||
import { testDefaults } from './utils'
|
||||
@@ -379,6 +380,61 @@ test('lockfile catalog snapshots do not contain stale references on --filter', a
|
||||
expect(loadJsonFile<ProjectManifest>(pathToIsPositivePkgJson)?.version).toBe('3.1.0')
|
||||
})
|
||||
|
||||
// Regression test for https://github.com/pnpm/pnpm/issues/8639
|
||||
test('--fix-lockfile with --filter does not erase catalog snapshots', async () => {
|
||||
const { options, projects, readLockfile } = preparePackagesAndReturnObjects([
|
||||
{
|
||||
name: 'project1',
|
||||
dependencies: {
|
||||
'is-negative': 'catalog:',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'project2',
|
||||
dependencies: {
|
||||
'is-positive': 'catalog:',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
const catalogs = {
|
||||
default: {
|
||||
'is-positive': '^1.0.0',
|
||||
'is-negative': '^1.0.0',
|
||||
},
|
||||
}
|
||||
|
||||
const expectedCatalogsSnapshot: CatalogSnapshots = {
|
||||
default: {
|
||||
'is-negative': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
'is-positive': { specifier: '^1.0.0', version: '1.0.0' },
|
||||
},
|
||||
}
|
||||
|
||||
await mutateModules(installProjects(projects), {
|
||||
...options,
|
||||
lockfileOnly: true,
|
||||
catalogs,
|
||||
})
|
||||
|
||||
// Sanity check this test is set up correctly.
|
||||
expect(readLockfile().catalogs).toStrictEqual(expectedCatalogsSnapshot)
|
||||
|
||||
// The catalogs snapshot should still be the same after performing a filtered
|
||||
// install with --fix-lockfile.
|
||||
const onlyProject1 = installProjects(projects).slice(0, 1)
|
||||
expect(onlyProject1).toMatchObject([{ id: 'project1' }])
|
||||
|
||||
await mutateModules(onlyProject1, {
|
||||
...options,
|
||||
lockfileOnly: true,
|
||||
fixLockfile: true,
|
||||
catalogs,
|
||||
})
|
||||
|
||||
expect(readLockfile().catalogs).toStrictEqual(expectedCatalogsSnapshot)
|
||||
})
|
||||
|
||||
test('external dependency using catalog protocol errors', async () => {
|
||||
const { options, projects } = preparePackagesAndReturnObjects([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user