mirror of
https://github.com/pnpm/pnpm.git
synced 2026-04-10 18:18:56 -04:00
* fix(lockfile): handle non-semver versions in lockfile merger without crashing * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Zoltan Kochan <z@kochan.io> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
390 lines
10 KiB
TypeScript
390 lines
10 KiB
TypeScript
import type { LockfileObject } from '@pnpm/lockfile.types'
|
|
import type { DepPath, ProjectId } from '@pnpm/types'
|
|
|
|
import { mergeLockfileChanges } from '../src/index.js'
|
|
|
|
const simpleLockfile = {
|
|
importers: {
|
|
'.': {
|
|
dependencies: {
|
|
foo: '1.0.0',
|
|
},
|
|
specifiers: {
|
|
foo: '1.0.0',
|
|
},
|
|
},
|
|
},
|
|
lockfileVersion: '5.2',
|
|
packages: {
|
|
'/foo@1.0.0': {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
test('picks the newer version when dependencies differ inside importer', () => {
|
|
const mergedLockfile = mergeLockfileChanges(
|
|
{
|
|
...simpleLockfile,
|
|
importers: {
|
|
['.' as ProjectId]: {
|
|
...simpleLockfile.importers['.'],
|
|
dependencies: {
|
|
foo: '1.2.0',
|
|
bar: '3.0.0(qar@1.0.0)',
|
|
zoo: '4.0.0(qar@1.0.0)',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
...simpleLockfile,
|
|
importers: {
|
|
['.' as ProjectId]: {
|
|
...simpleLockfile.importers['.'],
|
|
dependencies: {
|
|
foo: '1.1.0',
|
|
bar: '4.0.0(qar@1.0.0)',
|
|
zoo: '3.0.0(qar@1.0.0)',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
expect(mergedLockfile.importers['.' as ProjectId].dependencies?.foo).toBe('1.2.0')
|
|
expect(mergedLockfile.importers['.' as ProjectId].dependencies?.bar).toBe('4.0.0(qar@1.0.0)')
|
|
expect(mergedLockfile.importers['.' as ProjectId].dependencies?.zoo).toBe('4.0.0(qar@1.0.0)')
|
|
})
|
|
|
|
test('picks the newer version when dependencies differ inside package', () => {
|
|
const base: LockfileObject = {
|
|
importers: {
|
|
['.' as ProjectId]: {
|
|
dependencies: {
|
|
a: '1.0.0',
|
|
},
|
|
specifiers: {},
|
|
},
|
|
},
|
|
lockfileVersion: '5.2',
|
|
packages: {
|
|
['/a@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
foo: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/foo@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
const mergedLockfile = mergeLockfileChanges(
|
|
{
|
|
...base,
|
|
packages: {
|
|
...base.packages,
|
|
['/a@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
linked: 'link:../1',
|
|
foo: '1.2.0',
|
|
bar: '3.0.0(qar@1.0.0)',
|
|
zoo: '4.0.0(qar@1.0.0)',
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/bar@3.0.0(qar@1.0.0)' as DepPath]: {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/zoo@4.0.0(qar@1.0.0)' as DepPath]: {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/foo@1.2.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/qar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
...base,
|
|
packages: {
|
|
...base.packages,
|
|
['/a@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
linked: 'link:../1',
|
|
foo: '1.1.0',
|
|
bar: '4.0.0(qar@1.0.0)',
|
|
zoo: '3.0.0(qar@1.0.0)',
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/bar@4.0.0(qar@1.0.0)' as DepPath]: {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/zoo@3.0.0(qar@1.0.0)' as DepPath]: {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/foo@1.1.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
['/qar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: '',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
expect(mergedLockfile.packages?.['/a@1.0.0' as DepPath].dependencies?.linked).toBe('link:../1')
|
|
expect(mergedLockfile.packages?.['/a@1.0.0' as DepPath].dependencies?.foo).toBe('1.2.0')
|
|
expect(mergedLockfile.packages?.['/a@1.0.0' as DepPath].dependencies?.bar).toBe('4.0.0(qar@1.0.0)')
|
|
expect(mergedLockfile.packages?.['/a@1.0.0' as DepPath].dependencies?.zoo).toBe('4.0.0(qar@1.0.0)')
|
|
expect(Object.keys(mergedLockfile.packages ?? {}).sort()).toStrictEqual([
|
|
'/a@1.0.0',
|
|
'/bar@3.0.0(qar@1.0.0)',
|
|
'/bar@4.0.0(qar@1.0.0)',
|
|
'/foo@1.0.0',
|
|
'/foo@1.1.0',
|
|
'/foo@1.2.0',
|
|
'/qar@1.0.0',
|
|
'/zoo@3.0.0(qar@1.0.0)',
|
|
'/zoo@4.0.0(qar@1.0.0)',
|
|
])
|
|
})
|
|
|
|
test('prefers our lockfile resolutions when it has newer packages', () => {
|
|
const mergedLockfile = mergeLockfileChanges(
|
|
{
|
|
...simpleLockfile,
|
|
packages: {
|
|
['/foo@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
bar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
['/bar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
...simpleLockfile,
|
|
packages: {
|
|
['/foo@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
bar: '1.1.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
['/bar@1.1.0' as DepPath]: {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
['/qar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
expect(mergedLockfile).toStrictEqual({
|
|
...simpleLockfile,
|
|
packages: {
|
|
'/foo@1.0.0': {
|
|
dependencies: {
|
|
bar: '1.1.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
'/bar@1.0.0': {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
'/bar@1.1.0': {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
'/qar@1.0.0': {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
test('prefers our lockfile resolutions when it has newer packages #2', () => {
|
|
const mergedLockfile = mergeLockfileChanges(
|
|
{
|
|
...simpleLockfile,
|
|
packages: {
|
|
['/foo@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
bar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
['/bar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
...simpleLockfile,
|
|
packages: {
|
|
['/foo@1.0.0' as DepPath]: {
|
|
dependencies: {
|
|
bar: '1.1.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
['/bar@1.1.0' as DepPath]: {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
['/qar@1.0.0' as DepPath]: {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
expect(mergedLockfile).toStrictEqual({
|
|
...simpleLockfile,
|
|
packages: {
|
|
'/foo@1.0.0': {
|
|
dependencies: {
|
|
bar: '1.1.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
'/bar@1.0.0': {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
'/bar@1.1.0': {
|
|
dependencies: {
|
|
qar: '1.0.0',
|
|
},
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
'/qar@1.0.0': {
|
|
resolution: {
|
|
integrity: 'sha512-aBVzCAzfyApU0gg36QgCpJixGtYwuQ4djrn11J+DTB5vE4OmBPuZiulgTCA9ByULgVAyNV2CTpjjvZmxzukSLw==',
|
|
},
|
|
},
|
|
},
|
|
})
|
|
})
|
|
|
|
test('does not crash when merging non-semver versions (link: protocol)', () => {
|
|
const base: LockfileObject = {
|
|
importers: {
|
|
['.' as ProjectId]: {
|
|
dependencies: { a: '1.0.0' },
|
|
specifiers: {},
|
|
},
|
|
},
|
|
lockfileVersion: '5.2',
|
|
packages: {
|
|
['/a@1.0.0' as DepPath]: {
|
|
dependencies: { linked: 'link:../pkg1' },
|
|
resolution: { integrity: '' },
|
|
},
|
|
},
|
|
}
|
|
|
|
const mergedLockfile = mergeLockfileChanges(
|
|
base,
|
|
{
|
|
...base,
|
|
packages: {
|
|
['/a@1.0.0' as DepPath]: {
|
|
dependencies: { linked: 'link:../pkg2' },
|
|
resolution: { integrity: '' },
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
// Should not crash and should pick theirs (the incoming change)
|
|
expect(mergedLockfile.packages?.['/a@1.0.0' as DepPath].dependencies?.linked).toBe('link:../pkg2')
|
|
})
|