Files
pnpm/pkg-manager/core/test/install/dedupe.ts
2025-08-22 21:56:49 +02:00

230 lines
8.0 KiB
TypeScript

import { prepareEmpty } from '@pnpm/prepare'
import { addDependenciesToPackage, install } from '@pnpm/core'
import { addDistTag } from '@pnpm/registry-mock'
import { testDefaults } from '../utils/index.js'
test('prefer version ranges specified for top dependencies', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
await install(
{
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
'@pnpm.e2e/pkg-with-1-dep': '*',
},
},
testDefaults()
)
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
})
test('prefer version ranges specified for top dependencies, when doing named installation', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
const { updatedManifest: manifest } = await install(
{
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
},
},
testDefaults()
)
await addDependenciesToPackage(manifest, ['@pnpm.e2e/pkg-with-1-dep'], testDefaults())
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
})
test('prefer version ranges specified for top dependencies, even if they are aliased', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
await install(
{
dependencies: {
foo: 'npm:@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0',
'@pnpm.e2e/pkg-with-1-dep': '*',
},
},
testDefaults()
)
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
})
test('prefer version ranges specified for top dependencies, even if the subdependencies are aliased', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
await install(
{
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '100.0.0',
'@pnpm.e2e/pkg-with-1-aliased-dep': '100.0.0',
},
},
testDefaults()
)
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
})
test('ignore version of root dependency when it is incompatible with the indirect dependency\'s range', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
await install(
{
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '101.0.0',
'@pnpm.e2e/pkg-with-1-dep': '100.0.0',
},
},
testDefaults()
)
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@101.0.0'])
})
test('prefer dist-tag specified for top dependency', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'stable' })
await install(
{
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': 'stable',
'@pnpm.e2e/pkg-with-1-dep': '100.0.0',
},
},
testDefaults()
)
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
})
test('prefer version ranges passed in via opts.preferredVersions', async () => {
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
const project = prepareEmpty()
await install(
{
dependencies: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': '^100.0.0',
'@pnpm.e2e/pkg-with-1-dep': '*',
},
},
testDefaults(
{
preferredVersions: {
'@pnpm.e2e/dep-of-pkg-with-1-dep': {
'100.0.0': 'version',
},
},
}
)
)
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
})
// Covers https://github.com/pnpm/pnpm/issues/1187
test('prefer version of package that also satisfies the range of the same package higher in the dependency graph', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
await addDependenciesToPackage(
{},
['@pnpm.e2e/has-foo-as-dep-and-subdep'],
testDefaults()
)
const lockfile = project.readLockfile()
expect(
Object.keys(lockfile.packages)
).toStrictEqual(
[
'@pnpm.e2e/foo@100.0.0',
'@pnpm.e2e/has-foo-as-dep-and-subdep@1.0.0',
'@pnpm.e2e/requires-any-foo@1.0.0',
]
)
})
test('dedupe subdependency when a newer version of the same package is installed', async () => {
const project = prepareEmpty()
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.0.0', distTag: 'latest' })
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0', '@pnpm.e2e/pkg-with-1-dep@100.0.0'], testDefaults())
await addDistTag({ package: '@pnpm.e2e/dep-of-pkg-with-1-dep', version: '100.1.0', distTag: 'latest' })
await addDependenciesToPackage(manifest, ['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'], testDefaults())
const lockfile = project.readLockfile()
expect(lockfile.packages).toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'])
expect(lockfile.packages).not.toHaveProperty(['@pnpm.e2e/dep-of-pkg-with-1-dep@100.0.0'])
})
test('when resolving dependencies, prefer versions that are used by direct dependencies over versions used in subdeps', async () => {
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
const project = prepareEmpty()
const { updatedManifest: manifest } = await install({
dependencies: {
'@pnpm.e2e/foo': '100.0.0',
'@pnpm.e2e/has-foo-100.1.0-dep-1': '1.0.0',
'@pnpm.e2e/has-foo-100.1.0-dep-2': '1.0.0',
},
}, testDefaults())
await addDependenciesToPackage(manifest, ['@pnpm.e2e/has-foo-100.0.0-range-dep'], testDefaults())
const lockfile = project.readLockfile()
expect(lockfile.snapshots['@pnpm.e2e/has-foo-100.0.0-range-dep@1.0.0']).toHaveProperty(['dependencies', '@pnpm.e2e/foo'], '100.0.0')
})
test('when resolving dependencies, prefer versions that are used by direct dependencies over versions used in subdeps #2', async () => {
await addDistTag({ package: '@pnpm.e2e/foo', version: '100.1.0', distTag: 'latest' })
const project = prepareEmpty()
const { updatedManifest: manifest } = await install({
dependencies: {
'@pnpm.e2e/foo': '100.0.0',
'@pnpm.e2e/has-foo-100.1.0-dep-1': '1.0.0',
'@pnpm.e2e/has-foo-100.1.0-dep-2': '1.0.0',
},
}, testDefaults())
await addDependenciesToPackage(manifest, ['@pnpm.e2e/has-foo-100.0.0-range-dep'], testDefaults())
const lockfile = project.readLockfile()
expect(lockfile.snapshots['@pnpm.e2e/has-foo-100.0.0-range-dep@1.0.0']).toHaveProperty(['dependencies', '@pnpm.e2e/foo'], '100.0.0')
})