diff --git a/.changeset/olive-falcons-share.md b/.changeset/olive-falcons-share.md new file mode 100644 index 0000000000..f047afa25c --- /dev/null +++ b/.changeset/olive-falcons-share.md @@ -0,0 +1,6 @@ +--- +"@pnpm/resolve-dependencies": major +"@pnpm/core": major +--- + +The update options are passed on per project basis. So the `update` and `updateMatching` options are options of importers/projects. diff --git a/pkg-manager/core/src/index.ts b/pkg-manager/core/src/index.ts index 44f2b15c8a..131446ec07 100644 --- a/pkg-manager/core/src/index.ts +++ b/pkg-manager/core/src/index.ts @@ -14,3 +14,4 @@ export { ProjectOptions, UnexpectedStoreError, UnexpectedVirtualStoreDirError } export { InstallOptions } from './install/extendInstallOptions' export { WorkspacePackages } from '@pnpm/resolver-base' +export { UpdateMatchingFunction } from '@pnpm/resolve-dependencies' diff --git a/pkg-manager/core/src/install/extendInstallOptions.ts b/pkg-manager/core/src/install/extendInstallOptions.ts index 3938d5ff40..185c9d486c 100644 --- a/pkg-manager/core/src/install/extendInstallOptions.ts +++ b/pkg-manager/core/src/install/extendInstallOptions.ts @@ -54,9 +54,6 @@ export interface StrictInstallOptions { reporter: ReporterFunction force: boolean forcePublicHoistPattern: boolean - update: boolean - updateMatching?: (pkgName: string) => boolean - updatePackageManifest?: boolean depth: number lockfileDir: string modulesDir: string @@ -196,7 +193,6 @@ const defaults = async (opts: InstallOptions) => { process.platform === 'cygwin' || !process.setgid || process.getuid() !== 0, - update: false, useLockfile: true, saveLockfile: true, useGitBranchLockfile: false, diff --git a/pkg-manager/core/src/install/index.ts b/pkg-manager/core/src/install/index.ts index fcb2dc4637..4811e77247 100644 --- a/pkg-manager/core/src/install/index.ts +++ b/pkg-manager/core/src/install/index.ts @@ -45,6 +45,7 @@ import { DependenciesGraphNode, PinnedVersion, resolveDependencies, + UpdateMatchingFunction, WantedDependency, } from '@pnpm/resolve-dependencies' import { @@ -89,36 +90,50 @@ const BROKEN_LOCKFILE_INTEGRITY_ERRORS = new Set([ const DEV_PREINSTALL = 'pnpm:devPreinstall' -export type DependenciesMutation = ( - { - mutation: 'install' - pruneDirectDependencies?: boolean - } | { - allowNew?: boolean - dependencySelectors: string[] - mutation: 'installSome' - peer?: boolean - pruneDirectDependencies?: boolean - pinnedVersion?: PinnedVersion - targetDependenciesField?: DependenciesField - } | { - mutation: 'uninstallSome' - dependencyNames: string[] - targetDependenciesField?: DependenciesField - } | { - mutation: 'unlink' - } | { - mutation: 'unlinkSome' - dependencyNames: string[] - } -) +interface InstallMutationOptions { + update?: boolean + updateMatching?: UpdateMatchingFunction + updatePackageManifest?: boolean +} + +export interface InstallDepsMutation extends InstallMutationOptions { + mutation: 'install' + pruneDirectDependencies?: boolean +} + +export interface InstallSomeDepsMutation extends InstallMutationOptions { + allowNew?: boolean + dependencySelectors: string[] + mutation: 'installSome' + peer?: boolean + pruneDirectDependencies?: boolean + pinnedVersion?: PinnedVersion + targetDependenciesField?: DependenciesField +} + +export interface UninstallSomeDepsMutation { + mutation: 'uninstallSome' + dependencyNames: string[] + targetDependenciesField?: DependenciesField +} + +export interface UnlinkDepsMutation { + mutation: 'unlink' +} + +export interface UnlinkSomeDepsMutation { + mutation: 'unlinkSome' + dependencyNames: string[] +} + +export type DependenciesMutation = InstallDepsMutation | InstallSomeDepsMutation | UninstallSomeDepsMutation | UnlinkDepsMutation | UnlinkSomeDepsMutation export async function install ( manifest: ProjectManifest, opts: Omit & { preferredVersions?: PreferredVersions pruneDirectDependencies?: boolean - } + } & InstallMutationOptions ) { const rootDir = opts.dir ?? process.cwd() const projects = await mutateModules( @@ -127,6 +142,9 @@ export async function install ( mutation: 'install', pruneDirectDependencies: opts.pruneDirectDependencies, rootDir, + update: opts.update, + updateMatching: opts.updateMatching, + updatePackageManifest: opts.updatePackageManifest, }, ], { @@ -165,10 +183,17 @@ export async function mutateModulesInSingleProject ( rootDir: string modulesDir?: string }, - maybeOpts: Omit + maybeOpts: Omit & InstallMutationOptions ): Promise { const [updatedProject] = await mutateModules( - [project], + [ + { + ...project, + update: maybeOpts.update, + updateMatching: maybeOpts.updateMatching, + updatePackageManifest: maybeOpts.updatePackageManifest, + } as MutatedProject, + ], { ...maybeOpts, allProjects: [{ @@ -195,7 +220,7 @@ export async function mutateModules ( throw new PnpmError('OPTIONAL_DEPS_REQUIRE_PROD_DEPS', 'Optional dependencies cannot be installed without production dependencies') } - const installsOnly = projects.every((project) => project.mutation === 'install') + const installsOnly = projects.every((project) => project.mutation === 'install' && !project.update && !project.updateMatching) if (!installsOnly) opts.strictPeerDependencies = false // @ts-expect-error opts['forceNewModules'] = installsOnly @@ -306,7 +331,6 @@ export async function mutateModules ( opts.frozenLockfileIfExists && ctx.existsWantedLockfile if ( !ctx.lockfileHadConflicts && - !opts.update && !opts.fixLockfile && !opts.dedupe && installsOnly && @@ -396,7 +420,9 @@ export async function mutateModules ( if (BROKEN_LOCKFILE_INTEGRITY_ERRORS.has(error.code)) { needsFullResolution = true // Ideally, we would not update but currently there is no other way to redownload the integrity of the package - opts.update = true + for (const project of projects) { + (project as InstallMutationOptions).update = true + } } // A broken lockfile may be caused by a badly resolved Git conflict logger.warn({ @@ -432,14 +458,14 @@ export async function mutateModules ( case 'install': { await installCase({ ...projectOpts, - updatePackageManifest: opts.updatePackageManifest ?? opts.update, + updatePackageManifest: (projectOpts as InstallDepsMutation).updatePackageManifest ?? (projectOpts as InstallDepsMutation).update, }) break } case 'installSome': { await installSome({ ...projectOpts, - updatePackageManifest: opts.updatePackageManifest !== false, + updatePackageManifest: (projectOpts as InstallSomeDepsMutation).updatePackageManifest !== false, }) break } @@ -510,7 +536,7 @@ export async function mutateModules ( const wantedDependencies = getWantedDependencies(project.manifest, { autoInstallPeers: opts.autoInstallPeers, includeDirect: opts.includeDirect, - updateWorkspaceDependencies: opts.update, + updateWorkspaceDependencies: project.update, nodeExecPath: opts.nodeExecPath, }) .map((wantedDependency) => ({ ...wantedDependency, updateSpec: true, preserveNonSemverVersionSpec: true })) @@ -549,7 +575,7 @@ export async function mutateModules ( devDependencies, optional: project.targetDependenciesField === 'optionalDependencies', optionalDependencies, - updateWorkspaceDependencies: opts.update, + updateWorkspaceDependencies: project.update, preferredSpecs, overrides: opts.overrides, }) @@ -684,7 +710,7 @@ export async function addDependenciesToPackage ( peer?: boolean pinnedVersion?: 'major' | 'minor' | 'patch' targetDependenciesField?: DependenciesField - } + } & InstallMutationOptions ) { const rootDir = opts.dir ?? process.cwd() const projects = await mutateModules( @@ -697,6 +723,9 @@ export async function addDependenciesToPackage ( pinnedVersion: opts.pinnedVersion, rootDir, targetDependenciesField: opts.targetDependenciesField, + update: opts.update, + updateMatching: opts.updateMatching, + updatePackageManifest: opts.updatePackageManifest, }, ], { @@ -798,8 +827,9 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => { stage: 'resolution_started', }) + const update = projects.some((project) => (project as InstallMutationOptions).update) const preferredVersions = opts.preferredVersions ?? ( - !opts.update + !update ? getPreferredVersionsFromLockfileAndManifests(ctx.wantedLockfile.packages, Object.values(ctx.projects).map(({ manifest }) => manifest)) : undefined ) @@ -853,7 +883,7 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => { allowNonAppliedPatches: opts.allowNonAppliedPatches, autoInstallPeers: opts.autoInstallPeers, currentLockfile: ctx.currentLockfile, - defaultUpdateDepth: (opts.update || (opts.updateMatching != null)) ? opts.depth : -1, + defaultUpdateDepth: opts.depth, dedupePeerDependents: opts.dedupePeerDependents, dryRun: opts.lockfileOnly, engineStrict: opts.engineStrict, @@ -875,7 +905,6 @@ const _installInContext: InstallFunction = async (projects, ctx, opts) => { saveWorkspaceProtocol: opts.saveWorkspaceProtocol, storeController: opts.storeController, tag: opts.tag, - updateMatching: opts.updateMatching, virtualStoreDir: ctx.virtualStoreDir, wantedLockfile: ctx.wantedLockfile, workspacePackages: opts.workspacePackages, @@ -1248,7 +1277,9 @@ const installInContext: InstallFunction = async (projects, ctx, opts) => { ) throw error opts.needsFullResolution = true // Ideally, we would not update but currently there is no other way to redownload the integrity of the package - opts.update = true + for (const project of projects) { + (project as InstallMutationOptions).update = true + } logger.warn({ error, message: error.message, diff --git a/pkg-manager/core/test/install/misc.ts b/pkg-manager/core/test/install/misc.ts index 433fe8082e..35d3a0a3ec 100644 --- a/pkg-manager/core/test/install/misc.ts +++ b/pkg-manager/core/test/install/misc.ts @@ -1,6 +1,6 @@ import * as path from 'path' import { promises as fs } from 'fs' -import { prepareEmpty, preparePackages } from '@pnpm/prepare' +import { prepareEmpty, prepare, preparePackages } from '@pnpm/prepare' import { PnpmError } from '@pnpm/error' import { PackageManifestLog, @@ -716,6 +716,7 @@ test('lockfile locks npm dependencies', async () => { const reporter = sinon.spy() await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }) + await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }) const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], await testDefaults({ save: true, reporter })) @@ -815,7 +816,7 @@ test('should throw error when trying to install a package without name', async ( // Covers https://github.com/pnpm/pnpm/issues/1193 test('rewrites node_modules created by npm', async () => { - const project = prepareEmpty() + const project = prepare() await execa('npm', ['install', 'rimraf@2.5.1', '@types/node', '--save']) @@ -1004,6 +1005,7 @@ test('all the subdeps of dependencies are linked when a node_modules is partiall test('subdep symlinks are updated if the lockfile has new subdep versions specified', async () => { await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }) + await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }) const project = prepareEmpty() await mutateModulesInSingleProject({ diff --git a/pkg-manager/core/test/install/multipleImporters.ts b/pkg-manager/core/test/install/multipleImporters.ts index b7569b578e..ec3bc0aec5 100644 --- a/pkg-manager/core/test/install/multipleImporters.ts +++ b/pkg-manager/core/test/install/multipleImporters.ts @@ -927,10 +927,12 @@ test('update workspace range', async () => { dependencySelectors: ['dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6'], mutation: 'installSome', rootDir: path.resolve('project-1'), + update: true, }, { mutation: 'install', rootDir: path.resolve('project-2'), + update: true, }, ], await testDefaults({ allProjects: [ @@ -972,7 +974,6 @@ test('update workspace range', async () => { rootDir: path.resolve('project-2'), }, ], - update: true, workspacePackages: { dep1: { '2.0.0': { @@ -1071,10 +1072,12 @@ test('update workspace range when save-workspace-protocol is "rolling"', async ( dependencySelectors: ['dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6'], mutation: 'installSome', rootDir: path.resolve('project-1'), + update: true, }, { mutation: 'install', rootDir: path.resolve('project-2'), + update: true, }, ], await testDefaults({ allProjects: [ @@ -1113,7 +1116,6 @@ test('update workspace range when save-workspace-protocol is "rolling"', async ( }, ], saveWorkspaceProtocol: 'rolling', - update: true, workspacePackages: { dep1: { '2.0.0': { diff --git a/pkg-manager/core/test/install/updatingPkgJson.ts b/pkg-manager/core/test/install/updatingPkgJson.ts index f08e154f4e..d9ef1ccd3b 100644 --- a/pkg-manager/core/test/install/updatingPkgJson.ts +++ b/pkg-manager/core/test/install/updatingPkgJson.ts @@ -184,6 +184,7 @@ test('multiple save to package.json with `exact` versions (@rstacruz/tap-spec & }) test('save to package.json with save prefix ~', async () => { + await addDistTag({ package: '@pnpm.e2e/pkg-with-1-dep', version: '100.0.0', distTag: 'latest' }) prepareEmpty() const manifest = await addDependenciesToPackage({}, ['@pnpm.e2e/pkg-with-1-dep'], await testDefaults({ pinnedVersion: 'minor' })) @@ -211,10 +212,9 @@ test('an update bumps the versions in the manifest', async () => { }, mutation: 'install', rootDir: process.cwd(), - }, - await testDefaults({ update: true, - })) + }, + await testDefaults()) expect(manifest).toStrictEqual({ dependencies: { diff --git a/pkg-manager/plugin-commands-installation/src/recursive.ts b/pkg-manager/plugin-commands-installation/src/recursive.ts index cc182f1c3e..aa6b7f43c4 100755 --- a/pkg-manager/plugin-commands-installation/src/recursive.ts +++ b/pkg-manager/plugin-commands-installation/src/recursive.ts @@ -28,6 +28,7 @@ import { MutatedProject, mutateModules, ProjectOptions, + UpdateMatchingFunction, WorkspacePackages, } from '@pnpm/core' import isSubdir from 'is-subdir' @@ -79,6 +80,8 @@ type RecursiveOptions = CreateStoreControllerOptions & Pick update?: boolean + updatePackageManifest?: boolean + updateMatching?: UpdateMatchingFunction useBetaCli?: boolean allProjectsGraph: ProjectsGraph selectedProjectsGraph: ProjectsGraph @@ -234,6 +237,9 @@ export async function recursive ( }), rootDir, targetDependenciesField, + update: opts.update, + updateMatching: opts.updateMatching, + updatePackageManifest: opts.updatePackageManifest, } as MutatedProject) return case 'install': @@ -242,6 +248,9 @@ export async function recursive ( mutation, pruneDirectDependencies: opts.pruneDirectDependencies, rootDir, + update: opts.update, + updateMatching: opts.updateMatching, + updatePackageManifest: opts.updatePackageManifest, } as MutatedProject) } })) diff --git a/pkg-manager/resolve-dependencies/src/index.ts b/pkg-manager/resolve-dependencies/src/index.ts index dc5c4d2110..9b98263e55 100644 --- a/pkg-manager/resolve-dependencies/src/index.ts +++ b/pkg-manager/resolve-dependencies/src/index.ts @@ -26,7 +26,7 @@ import promiseShare from 'promise-share' import difference from 'ramda/src/difference' import { getWantedDependencies, WantedDependency } from './getWantedDependencies' import { depPathToRef } from './depPathToRef' -import { createNodeIdForLinkedLocalPkg } from './resolveDependencies' +import { createNodeIdForLinkedLocalPkg, UpdateMatchingFunction } from './resolveDependencies' import { Importer, LinkedDependency, @@ -53,6 +53,7 @@ export { LinkedDependency, ResolvedPackage, PinnedVersion, + UpdateMatchingFunction, WantedDependency, } @@ -81,6 +82,8 @@ export type ImporterToResolve = Importer<{ binsDir: string manifest: ProjectManifest originalManifest?: ProjectManifest + update?: boolean + updateMatching?: UpdateMatchingFunction updatePackageManifest: boolean targetDependenciesField?: DependenciesField } @@ -100,7 +103,6 @@ export async function resolveDependencies ( defaultUpdateDepth: opts.defaultUpdateDepth, lockfileOnly: opts.dryRun, preferredVersions: opts.preferredVersions, - updateAll: Boolean(opts.updateMatching), virtualStoreDir: opts.virtualStoreDir, workspacePackages: opts.workspacePackages, }) diff --git a/pkg-manager/resolve-dependencies/src/resolveDependencies.ts b/pkg-manager/resolve-dependencies/src/resolveDependencies.ts index 0c5e8e7afc..24a373c353 100644 --- a/pkg-manager/resolve-dependencies/src/resolveDependencies.ts +++ b/pkg-manager/resolve-dependencies/src/resolveDependencies.ts @@ -159,7 +159,6 @@ export interface ResolutionContext { registries: Registries resolutionMode?: 'highest' | 'time-based' | 'lowest-direct' virtualStoreDir: string - updateMatching?: (pkgName: string) => boolean useLockfileV6?: boolean workspacePackages?: WorkspacePackages missingPeersOfChildrenByPkgId: Record @@ -240,6 +239,8 @@ type ParentPkg = Pick +export type UpdateMatchingFunction = (pkgName: string) => boolean + interface ResolvedDependenciesOptions { currentDepth: number parentPkg: ParentPkg @@ -252,6 +253,7 @@ interface ResolvedDependenciesOptions { publishedBy?: Date pickLowestVersion?: boolean resolvedDependencies?: ResolvedDependencies + updateMatching?: UpdateMatchingFunction updateDepth: number prefix: string } @@ -647,8 +649,8 @@ async function resolveDependenciesOfDependency ( const update = ((extendedWantedDep.infoFromLockfile?.dependencyLockfile) == null) || ( updateShouldContinue && ( - (ctx.updateMatching == null) || - ctx.updateMatching(extendedWantedDep.infoFromLockfile.name!) + (options.updateMatching == null) || + options.updateMatching(extendedWantedDep.infoFromLockfile.name!) ) ) || Boolean( (ctx.workspacePackages != null) && @@ -672,6 +674,7 @@ async function resolveDependenciesOfDependency ( publishedBy: options.publishedBy, update, updateDepth, + updateMatching: options.updateMatching, } const resolveDependencyResult = await resolveDependency(extendedWantedDep.wantedDependency, ctx, resolveDependencyOpts) @@ -706,6 +709,7 @@ async function resolveDependenciesOfDependency ( parentDepth: options.currentDepth, updateDepth, prefix: options.prefix, + updateMatching: options.updateMatching, }) return { resolveDependencyResult, @@ -751,6 +755,7 @@ async function resolveChildren ( dependencyLockfile, parentDepth, updateDepth, + updateMatching, prefix, }: { parentPkg: PkgAddress @@ -758,6 +763,7 @@ async function resolveChildren ( parentDepth: number updateDepth: number prefix: string + updateMatching?: UpdateMatchingFunction }, { parentPkgAliases, @@ -802,6 +808,7 @@ async function resolveChildren ( publishedBy, resolvedDependencies, updateDepth, + updateMatching, } ) ctx.childrenByParentDepPath[parentPkg.depPath] = pkgAddresses.map((child) => ({ @@ -1001,6 +1008,7 @@ interface ResolveDependencyOptions { pickLowestVersion?: boolean update: boolean updateDepth: number + updateMatching?: UpdateMatchingFunction } type ResolveDependencyResult = PkgAddress | LinkedDependency | null diff --git a/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts b/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts index 4caac381d6..029dd7db5f 100644 --- a/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts +++ b/pkg-manager/resolve-dependencies/src/resolveDependencyTree.ts @@ -52,6 +52,7 @@ export interface Importer { export interface ImporterToResolveGeneric extends Importer { updatePackageManifest: boolean + updateMatching?: (pkgName: string) => boolean hasRemovedDependencies?: boolean preferredVersions?: PreferredVersions wantedDependencies: Array @@ -79,7 +80,6 @@ export interface ResolveDependenciesOptions { preferWorkspacePackages?: boolean resolutionMode?: 'highest' | 'time-based' | 'lowest-direct' resolvePeersFromWorkspaceRoot?: boolean - updateMatching?: (pkgName: string) => boolean linkWorkspacePackagesDepth?: number lockfileDir: string storeController: StoreController @@ -122,7 +122,6 @@ export async function resolveDependencyTree ( resolutionMode: opts.resolutionMode, skipped: wantedToBeSkippedPackageIds, storeController: opts.storeController, - updateMatching: opts.updateMatching, virtualStoreDir: opts.virtualStoreDir, wantedLockfile: opts.wantedLockfile, appliedPatches: new Set(), @@ -154,6 +153,7 @@ export async function resolveDependencyTree ( ...projectSnapshot.optionalDependencies, }, updateDepth: -1, + updateMatching: importer.updateMatching, prefix: importer.rootDir, } return { diff --git a/pkg-manager/resolve-dependencies/src/toResolveImporter.ts b/pkg-manager/resolve-dependencies/src/toResolveImporter.ts index 387120fe90..87360156cf 100644 --- a/pkg-manager/resolve-dependencies/src/toResolveImporter.ts +++ b/pkg-manager/resolve-dependencies/src/toResolveImporter.ts @@ -15,7 +15,6 @@ export async function toResolveImporter ( defaultUpdateDepth: number lockfileOnly: boolean preferredVersions?: PreferredVersions - updateAll: boolean virtualStoreDir: string workspacePackages: WorkspacePackages }, @@ -29,6 +28,7 @@ export async function toResolveImporter ( virtualStoreDir: opts.virtualStoreDir, workspacePackages: opts.workspacePackages, }) + const defaultUpdateDepth = (project.update === true || (project.updateMatching != null)) ? opts.defaultUpdateDepth : -1 const existingDeps = nonLinkedDependencies .filter(({ alias }) => !project.wantedDependencies.some((wantedDep) => wantedDep.alias === alias)) let wantedDependencies!: Array @@ -39,22 +39,22 @@ export async function toResolveImporter ( ] .map((dep) => ({ ...dep, - updateDepth: opts.defaultUpdateDepth, + updateDepth: defaultUpdateDepth, })) } else { // Direct local tarballs are always checked, // so their update depth should be at least 0 const updateLocalTarballs = (dep: WantedDependency) => ({ ...dep, - updateDepth: opts.updateAll - ? opts.defaultUpdateDepth + updateDepth: project.updateMatching != null + ? defaultUpdateDepth : (prefIsLocalTarball(dep.pref) ? 0 : -1), }) wantedDependencies = [ ...project.wantedDependencies.map( - opts.defaultUpdateDepth < 0 + defaultUpdateDepth < 0 ? updateLocalTarballs - : (dep) => ({ ...dep, updateDepth: opts.defaultUpdateDepth })), + : (dep) => ({ ...dep, updateDepth: defaultUpdateDepth })), ...existingDeps.map(updateLocalTarballs), ] } diff --git a/pnpm/test/monorepo/dedupePeers.test.ts b/pnpm/test/monorepo/dedupePeers.test.ts index 3d4b4d0d3f..e9d36acb56 100644 --- a/pnpm/test/monorepo/dedupePeers.test.ts +++ b/pnpm/test/monorepo/dedupePeers.test.ts @@ -6,6 +6,7 @@ import { preparePackages } from '@pnpm/prepare' import { addDistTag } from '@pnpm/registry-mock' import { sync as readYamlFile } from 'read-yaml-file' import { createPeersFolderSuffix } from '@pnpm/dependency-path' +import { sync as loadJsonFile } from 'load-json-file' import { sync as writeYamlFile } from 'write-yaml-file' import { execPnpm } from '../utils' @@ -46,3 +47,46 @@ auto-install-peers=false`, 'utf8') expect(depPaths).toContain(`/@pnpm.e2e/abc/1.0.0${createPeersFolderSuffix([{ name: '@pnpm.e2e/peer-a', version: '1.0.0' }, { name: '@pnpm.e2e/peer-b', version: '1.0.0' }, { name: '@pnpm.e2e/peer-c', version: '1.0.0' }])}`) expect(depPaths).toContain(`/@pnpm.e2e/abc-parent-with-ab/1.0.0${createPeersFolderSuffix([{ name: '@pnpm.e2e/peer-c', version: '1.0.0' }])}`) }) + +test('partial update in a workspace should work with dedupe-peer-dependents is true', async () => { + await addDistTag({ package: '@pnpm.e2e/abc-parent-with-ab', version: '1.0.0', distTag: 'latest' }) + await addDistTag({ package: '@pnpm.e2e/abc', version: '1.0.0', distTag: 'latest' }) + await addDistTag({ package: '@pnpm.e2e/peer-a', version: '1.0.0', distTag: 'latest' }) + await addDistTag({ package: '@pnpm.e2e/peer-b', version: '1.0.0', distTag: 'latest' }) + await addDistTag({ package: '@pnpm.e2e/peer-c', version: '1.0.0', distTag: 'latest' }) + + preparePackages([ + { + location: 'project-1', + package: { + name: 'project-1', + + dependencies: { + '@pnpm.e2e/abc-grand-parent-with-c': '^1.0.0', + }, + }, + }, + { + location: 'project-2', + package: { + name: 'project-2', + + dependencies: { + '@pnpm.e2e/abc-grand-parent-with-c': '^1.0.0', + }, + }, + }, + ]) + + writeYamlFile('pnpm-workspace.yaml', { packages: ['**', '!store/**'] }) + writeFileSync('.npmrc', `dedupe-peer-dependents=true +auto-install-peers=false`, 'utf8') + await execPnpm(['install']) + await addDistTag({ package: '@pnpm.e2e/abc-parent-with-ab', version: '1.0.1', distTag: 'latest' }) + process.chdir('project-2') + await execPnpm(['update']) + process.chdir('..') + + expect(loadJsonFile('project-1/package.json').dependencies['@pnpm.e2e/abc-grand-parent-with-c']).toBe('^1.0.0') // eslint-disable-line + expect(loadJsonFile('project-2/package.json').dependencies['@pnpm.e2e/abc-grand-parent-with-c']).toBe('^1.0.1') // eslint-disable-line +})