Skip to content

Commit 17344ca

Browse files
TrevorBurnhamzkochan
authored andcommitted
fix(update): prevent package.json updates when updating indirect dependencies (#5118) (#10155)
close #5118
1 parent df80112 commit 17344ca

File tree

3 files changed

+55
-1
lines changed

3 files changed

+55
-1
lines changed

.changeset/every-trains-shake.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@pnpm/plugin-commands-installation": patch
3+
"pnpm": patch
4+
---
5+
6+
When a user runs `pnpm update` on a dependency that is not directly listed in `package.json`, none of the direct dependencies should be updated [#10155](https://github.com/pnpm/pnpm/pull/10155).

pkg-manager/plugin-commands-installation/src/installDeps.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ when running add/update with the --workspace option')
272272
}
273273

274274
let updateMatch: UpdateDepsMatcher | null
275+
let updatePackageManifest = opts.updatePackageManifest
276+
let updateMatching: ((pkgName: string) => boolean) | undefined
275277
if (opts.update) {
276278
if (params.length === 0) {
277279
const ignoreDeps = opts.updateConfig?.ignoreDependencies
@@ -291,6 +293,10 @@ when running add/update with the --workspace option')
291293
throw new PnpmError('NO_PACKAGE_IN_DEPENDENCIES',
292294
'None of the specified packages were found in the dependencies.')
293295
}
296+
// No direct dependencies matched, so we're updating indirect dependencies only
297+
// Don't update package.json in this case, and limit updates to only matching dependencies
298+
updatePackageManifest = false
299+
updateMatching = (pkgName: string) => updateMatch!(pkgName) != null
294300
}
295301
}
296302

@@ -343,7 +349,11 @@ when running add/update with the --workspace option')
343349
return
344350
}
345351

346-
const { updatedCatalogs, updatedManifest, ignoredBuilds } = await install(manifest, installOpts)
352+
const { updatedCatalogs, updatedManifest, ignoredBuilds } = await install(manifest, {
353+
...installOpts,
354+
updatePackageManifest,
355+
updateMatching,
356+
})
347357
if (opts.update === true && opts.save !== false) {
348358
await Promise.all([
349359
writeProjectManifest(updatedManifest),

pnpm/test/update.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,44 @@ test('update with tag @latest will downgrade prerelease', async function () {
684684
expect(lockfile2).toHaveProperty(['packages', '@pnpm.e2e/[email protected]'])
685685
})
686686

687+
test('update indirect dependency should not update package.json', async function () {
688+
const project = prepare({
689+
dependencies: {
690+
'@pnpm.e2e/pkg-with-1-dep': '^100.0.0',
691+
},
692+
})
693+
694+
// Ensure the initial versions
695+
await addDistTag('@pnpm.e2e/pkg-with-1-dep', '100.0.0', 'latest')
696+
await addDistTag('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.0.0', 'latest')
697+
698+
await execPnpm(['install'])
699+
700+
const pkg1 = await readPackageJsonFromDir(process.cwd())
701+
expect(pkg1.dependencies?.['@pnpm.e2e/pkg-with-1-dep']).toBe('^100.0.0')
702+
703+
const lockfile1 = project.readLockfile()
704+
expect(lockfile1.importers['.'].dependencies?.['@pnpm.e2e/pkg-with-1-dep'].version).toBe('100.0.0')
705+
706+
// Now publish a new version of the direct dependency and update the indirect dependency
707+
await addDistTag('@pnpm.e2e/pkg-with-1-dep', '100.1.0', 'latest')
708+
await addDistTag('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0', 'latest')
709+
710+
// Update the indirect dependency only
711+
await execPnpm(['update', '@pnpm.e2e/dep-of-pkg-with-1-dep@latest'])
712+
713+
// The direct dependency in package.json should remain unchanged at ^100.0.0
714+
const pkg2 = await readPackageJsonFromDir(process.cwd())
715+
expect(pkg2.dependencies?.['@pnpm.e2e/pkg-with-1-dep']).toBe('^100.0.0')
716+
717+
// But the lockfile should have the updated indirect dependency
718+
const lockfile2 = project.readLockfile()
719+
expect(Object.keys(lockfile2.packages ?? {})).toContain('@pnpm.e2e/[email protected]')
720+
721+
// The direct dependency should remain at 100.0.0 in the lockfile (not upgraded to 100.1.0)
722+
expect(lockfile2.importers['.'].dependencies?.['@pnpm.e2e/pkg-with-1-dep'].version).toBe('100.0.0')
723+
})
724+
687725
test('update to latest recursive workspace (outdated, updated, prerelease, outdated)', async function () {
688726
await addDistTag('@pnpm.e2e/has-prerelease', '2.0.0', 'latest')
689727

0 commit comments

Comments
 (0)