From dec8a472a2992d07e285e16d55a5fe1ff3a2dcd3 Mon Sep 17 00:00:00 2001 From: Fotis Papadogeorgopoulos Date: Sun, 29 Dec 2024 18:30:36 +0200 Subject: [PATCH] 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 --- .changeset/forty-yaks-jog.md | 6 +++ .changeset/moody-berries-design.md | 6 +++ .../core/src/install/extendInstallOptions.ts | 1 - pkg-manager/core/src/install/index.ts | 5 +- .../src/recursive.ts | 2 + pkg-manager/resolve-dependencies/src/index.ts | 1 - .../src/resolveDependencyTree.ts | 4 +- pnpm/test/monorepo/dedupePeers.test.ts | 52 +++++++++++++++++++ 8 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 .changeset/forty-yaks-jog.md create mode 100644 .changeset/moody-berries-design.md diff --git a/.changeset/forty-yaks-jog.md b/.changeset/forty-yaks-jog.md new file mode 100644 index 0000000000..ca0870e404 --- /dev/null +++ b/.changeset/forty-yaks-jog.md @@ -0,0 +1,6 @@ +--- +"@pnpm/plugin-commands-installation": patch +"pnpm": patch +--- + +`pnpm update --filter --latest ` 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). diff --git a/.changeset/moody-berries-design.md b/.changeset/moody-berries-design.md new file mode 100644 index 0000000000..74aa780741 --- /dev/null +++ b/.changeset/moody-berries-design.md @@ -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. diff --git a/pkg-manager/core/src/install/extendInstallOptions.ts b/pkg-manager/core/src/install/extendInstallOptions.ts index cd1fb0a7e5..2b29b6125b 100644 --- a/pkg-manager/core/src/install/extendInstallOptions.ts +++ b/pkg-manager/core/src/install/extendInstallOptions.ts @@ -98,7 +98,6 @@ export interface StrictInstallOptions { unsafePerm: boolean registries: Registries tag: string - updateToLatest?: boolean overrides: Record ownLifecycleHooksStdio: 'inherit' | 'pipe' // We can automatically calculate these diff --git a/pkg-manager/core/src/install/index.ts b/pkg-manager/core/src/install/index.ts index 435c1ae5a9..c3bfe6ffd1 100644 --- a/pkg-manager/core/src/install/index.ts +++ b/pkg-manager/core/src/install/index.ts @@ -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, diff --git a/pkg-manager/plugin-commands-installation/src/recursive.ts b/pkg-manager/plugin-commands-installation/src/recursive.ts index 0e1d29d4ed..9552946564 100755 --- a/pkg-manager/plugin-commands-installation/src/recursive.ts +++ b/pkg-manager/plugin-commands-installation/src/recursive.ts @@ -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) } })) diff --git a/pkg-manager/resolve-dependencies/src/index.ts b/pkg-manager/resolve-dependencies/src/index.ts index 86a8a056ce..28fa1b4c87 100644 --- a/pkg-manager/resolve-dependencies/src/index.ts +++ b/pkg-manager/resolve-dependencies/src/index.ts @@ -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))) diff --git a/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts b/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts index 1facf920b5..9e188a260c 100644 --- a/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts +++ b/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts @@ -88,6 +88,7 @@ export interface Importer { export interface ImporterToResolveGeneric extends Importer { updatePackageManifest: boolean updateMatching?: (pkgName: string) => boolean + updateToLatest?: boolean hasRemovedDependencies?: boolean preferredVersions?: PreferredVersions wantedDependencies: Array @@ -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 ( }, updateDepth: -1, updateMatching: importer.updateMatching, + updateToLatest: importer.updateToLatest, prefix: importer.rootDir, supportedArchitectures: opts.supportedArchitectures, - updateToLatest: opts.updateToLatest, } return { updatePackageManifest: importer.updatePackageManifest, diff --git a/pnpm/test/monorepo/dedupePeers.test.ts b/pnpm/test/monorepo/dedupePeers.test.ts index 4d72e5ba3c..8c1e377533 100644 --- a/pnpm/test/monorepo/dedupePeers.test.ts +++ b/pnpm/test/monorepo/dedupePeers.test.ts @@ -91,6 +91,58 @@ auto-install-peers=false`, 'utf8') expect(loadJsonFile('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('project-1/package.json').dependencies['@pnpm.e2e/foo']).toBe('1.0.0') // eslint-disable-line + expect(loadJsonFile('project-1/package.json').dependencies['@pnpm.e2e/bar']).toBe('100.0.0') // eslint-disable-line + expect(loadJsonFile('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(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' })