fix: pnpm update --filter --latest should only change relevant packages and projects, with dedupe-peer-dependents=true (#8905)

* test(update): add failing tests for update with dedupe-peer-dependents=true

Relates to https://github.com/pnpm/pnpm/issues/8877

* fix: update --filter --latest should work with dedupe-peer-dependents

Fixes https://github.com/pnpm/pnpm/issues/8877, whereby
`update --filter --latest` with `dedupe-peer-dependents` would end up
updating all available dependencies for all projects.

* test(pnpm): more accurate dedupePeers filtered install case

* docs: add changeset for updateToLatest moving to projects/importers

* docs: add changesets for pnpm and plugin-commands-installation

* chore: fix tsc issue by removing unknown bound resolver property

This unknown property was accepted by tsc prior to adding updateToLatest
in toResovleImporter options, but now it was erroring out. This is
likely a tsc quirk about the shape of the object; regardless that
property is not defined, and should not be present.

* test: keep only pnpm/test/monorepo/dedupePeers.test.ts

There was duplicate coverage of the pnpm update --filter --latest
command between two tests, so this keeps only the one dedicated
to testing the dedupe-peer-dependents feature.

* chore: fix unused import error
This commit is contained in:
Fotis Papadogeorgopoulos
2024-12-29 18:30:36 +02:00
committed by Zoltan Kochan
parent c056fe054b
commit dec8a472a2
8 changed files with 72 additions and 5 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/plugin-commands-installation": patch
"pnpm": patch
---
`pnpm update --filter <pattern> --latest <pkg>` should only change the specified package for the specified workspace, when `dedupe-peer-dependents` is set to `true` [#8877](https://github.com/pnpm/pnpm/issues/8877).

View File

@@ -0,0 +1,6 @@
---
"@pnpm/resolve-dependencies": major
"@pnpm/core": major
---
The `updateToLatest` option is now part of projects/importers, instead of an option of the resolution/installation.

View File

@@ -98,7 +98,6 @@ export interface StrictInstallOptions {
unsafePerm: boolean
registries: Registries
tag: string
updateToLatest?: boolean
overrides: Record<string, string>
ownLifecycleHooksStdio: 'inherit' | 'pipe'
// We can automatically calculate these

View File

@@ -109,6 +109,7 @@ const DEV_PREINSTALL = 'pnpm:devPreinstall'
interface InstallMutationOptions {
update?: boolean
updateToLatest?: boolean
updateMatching?: UpdateMatchingFunction
updatePackageManifest?: boolean
}
@@ -163,6 +164,7 @@ export async function install (
rootDir,
update: opts.update,
updateMatching: opts.updateMatching,
updateToLatest: opts.updateToLatest,
updatePackageManifest: opts.updatePackageManifest,
},
],
@@ -209,6 +211,7 @@ export async function mutateModulesInSingleProject (
{
...project,
update: maybeOpts.update,
updateToLatest: maybeOpts.updateToLatest,
updateMatching: maybeOpts.updateMatching,
updatePackageManifest: maybeOpts.updatePackageManifest,
} as MutatedProject,
@@ -835,6 +838,7 @@ export async function addDependenciesToPackage (
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
updateToLatest: opts.updateToLatest,
},
],
{
@@ -1028,7 +1032,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => {
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
storeController: opts.storeController,
tag: opts.tag,
updateToLatest: opts.updateToLatest,
virtualStoreDir: ctx.virtualStoreDir,
virtualStoreDirMaxLength: ctx.virtualStoreDirMaxLength,
wantedLockfile: ctx.wantedLockfile,

View File

@@ -234,6 +234,7 @@ export async function recursive (
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
updateToLatest: opts.latest,
} as MutatedProject)
return
case 'install':
@@ -245,6 +246,7 @@ export async function recursive (
update: opts.update,
updateMatching: opts.updateMatching,
updatePackageManifest: opts.updatePackageManifest,
updateToLatest: opts.latest,
} as MutatedProject)
}
}))

View File

@@ -125,7 +125,6 @@ export async function resolveDependencies (
preferredVersions: opts.preferredVersions,
virtualStoreDir: opts.virtualStoreDir,
workspacePackages: opts.workspacePackages,
updateToLatest: opts.updateToLatest,
noDependencySelectors: importers.every(({ wantedDependencies }) => wantedDependencies.length === 0),
})
const projectsToResolve = await Promise.all(importers.map(async (project) => _toResolveImporter(project)))

View File

@@ -88,6 +88,7 @@ export interface Importer<WantedDepExtraProps> {
export interface ImporterToResolveGeneric<WantedDepExtraProps> extends Importer<WantedDepExtraProps> {
updatePackageManifest: boolean
updateMatching?: (pkgName: string) => boolean
updateToLatest?: boolean
hasRemovedDependencies?: boolean
preferredVersions?: PreferredVersions
wantedDependencies: Array<WantedDepExtraProps & WantedDependency & { updateDepth: number }>
@@ -127,7 +128,6 @@ export interface ResolveDependenciesOptions {
wantedLockfile: Lockfile
workspacePackages: WorkspacePackages
supportedArchitectures?: SupportedArchitectures
updateToLatest?: boolean
peersSuffixMaxLength: number
}
@@ -214,9 +214,9 @@ export async function resolveDependencyTree<T> (
},
updateDepth: -1,
updateMatching: importer.updateMatching,
updateToLatest: importer.updateToLatest,
prefix: importer.rootDir,
supportedArchitectures: opts.supportedArchitectures,
updateToLatest: opts.updateToLatest,
}
return {
updatePackageManifest: importer.updatePackageManifest,

View File

@@ -91,6 +91,58 @@ auto-install-peers=false`, 'utf8')
expect(loadJsonFile<any>('project-2/package.json').dependencies['@pnpm.e2e/abc-grand-parent-with-c']).toBe('^1.0.1') // eslint-disable-line
})
// Covers https://github.com/pnpm/pnpm/issues/8877
test('partial update --latest in a workspace should not affect other packages when dedupe-peer-dependents is true', async () => {
await addDistTag({ package: '@pnpm.e2e/foo', version: '1.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.0.0', distTag: 'latest' })
preparePackages([
{
location: 'project-1',
package: {
name: 'project-1',
dependencies: {
'@pnpm.e2e/foo': '1.0.0',
'@pnpm.e2e/bar': '100.0.0',
},
},
},
{
location: 'project-2',
package: {
name: 'project-2',
dependencies: {
'@pnpm.e2e/foo': '1.0.0',
},
},
},
])
writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] })
fs.writeFileSync('.npmrc', `dedupe-peer-dependents=true
auto-install-peers=false`, 'utf8')
await execPnpm(['install'])
await addDistTag({ package: '@pnpm.e2e/foo', version: '2.0.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/bar', version: '100.1.0', distTag: 'latest' })
await execPnpm(['update', '--filter', 'project-2', '--latest'])
// project 1's manifest is unaffected, while project 2 has foo updated
expect(loadJsonFile<any>('project-1/package.json').dependencies['@pnpm.e2e/foo']).toBe('1.0.0') // eslint-disable-line
expect(loadJsonFile<any>('project-1/package.json').dependencies['@pnpm.e2e/bar']).toBe('100.0.0') // eslint-disable-line
expect(loadJsonFile<any>('project-2/package.json').dependencies['@pnpm.e2e/foo']).toBe('2.0.0') // eslint-disable-line
// similar for the importers in the lockfile; project 1 is unaffected, while
// project 2 resolves the latest foo
const lockfile = readYamlFile<any>(path.resolve(WANTED_LOCKFILE)) // eslint-disable-line
expect(lockfile.importers['project-1']?.dependencies?.['@pnpm.e2e/foo'].version).toStrictEqual('1.0.0')
expect(lockfile.importers['project-1']?.dependencies?.['@pnpm.e2e/bar'].version).toStrictEqual('100.0.0')
expect(lockfile.importers['project-2']?.dependencies?.['@pnpm.e2e/foo'].version).toStrictEqual('2.0.0')
})
// Covers https://github.com/pnpm/pnpm/issues/6154
test('peer dependents deduplication should not remove peer dependencies', async () => {
await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' })