diff --git a/lib/modules/manager/npm/extract/locked-versions.spec.ts b/lib/modules/manager/npm/extract/locked-versions.spec.ts index 119eb2faba428bded6a674138e54a9e72c40f2df..c63a8494d8b4283a558d54dc03cf1753b081ef86 100644 --- a/lib/modules/manager/npm/extract/locked-versions.spec.ts +++ b/lib/modules/manager/npm/extract/locked-versions.spec.ts @@ -1,14 +1,20 @@ -import { logger } from '../../../../../test/util'; +import { logger, mocked } from '../../../../../test/util'; import type { PackageFile } from '../../types'; import type { NpmManagerData } from '../types'; import { getLockedVersions } from './locked-versions'; +import * as _npm from './npm'; +import * as _pnpm from './pnpm'; +import * as _yarn from './yarn'; -const npm = require('./npm'); -const pnpm = require('./pnpm'); -const yarn = require('./yarn'); +const npm = mocked(_npm); +const pnpm = mocked(_pnpm); +const yarn = mocked(_yarn); jest.mock('./npm'); -jest.mock('./yarn'); +jest.mock('./yarn', () => ({ + ...jest.requireActual<any>('./yarn'), + getYarnLock: jest.fn(), +})); jest.mock('./pnpm'); describe('modules/manager/npm/extract/locked-versions', () => { @@ -52,7 +58,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { const yarnVersion = '1.22.0'; const lockfileVersion = undefined; const isYarn1 = true; - yarn.getYarnLock.mockReturnValue({ + yarn.getYarnLock.mockResolvedValue({ isYarn1, lockfileVersion, lockedVersions, @@ -89,7 +95,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { const yarnVersion = '2.1.0'; const lockfileVersion = undefined; const isYarn1 = false; - yarn.getYarnLock.mockReturnValue({ + yarn.getYarnLock.mockResolvedValue({ isYarn1, lockfileVersion, lockedVersions, @@ -136,7 +142,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { const yarnVersion = '2.2.0'; const lockfileVersion = 6; const isYarn1 = false; - yarn.getYarnLock.mockReturnValue({ + yarn.getYarnLock.mockResolvedValue({ isYarn1, lockfileVersion, lockedVersions, @@ -183,7 +189,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { const yarnVersion = '3.0.0'; const lockfileVersion = 8; const isYarn1 = false; - yarn.getYarnLock.mockReturnValue({ + yarn.getYarnLock.mockResolvedValue({ isYarn1, lockfileVersion, lockedVersions, @@ -222,7 +228,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { const yarnVersion = '3.2.0'; const lockfileVersion = 8; const isYarn1 = false; - yarn.getYarnLock.mockReturnValue({ + yarn.getYarnLock.mockResolvedValue({ isYarn1, lockfileVersion, lockedVersions, @@ -259,7 +265,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('uses package-lock.json with npm v6.0.0', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', c: '3.0.0' }, lockfileVersion: 1, }); @@ -290,7 +296,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('uses package-lock.json with npm v7.0.0', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', c: '3.0.0' }, lockfileVersion: 2, }); @@ -327,7 +333,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('augments v2 lock file constraint', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', c: '3.0.0' }, lockfileVersion: 2, }); @@ -364,7 +370,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('skips augmenting v2 lock file constraint', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', c: '3.0.0' }, lockfileVersion: 2, }); @@ -401,7 +407,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('appends <7 to npm extractedConstraints', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', @@ -446,7 +452,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('skips appending <7 to npm extractedConstraints', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', @@ -492,7 +498,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('uses pnpm-lock', async () => { - pnpm.getPnpmLock.mockReturnValue({ + pnpm.getPnpmLock.mockResolvedValue({ lockedVersionsWithPath: { '.': { dependencies: { @@ -553,7 +559,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('uses pnpm-lock in subfolder', async () => { - pnpm.getPnpmLock.mockReturnValue({ + pnpm.getPnpmLock.mockResolvedValue({ lockedVersionsWithPath: { '.': { dependencies: { @@ -614,7 +620,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('uses pnpm-lock with workspaces', async () => { - pnpm.getPnpmLock.mockReturnValue({ + pnpm.getPnpmLock.mockResolvedValue({ lockedVersionsWithPath: { 'workspace-package': { dependencies: { @@ -692,7 +698,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('should log warning if unsupported lockfileVersion is found', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: {}, lockfileVersion: 99, }); @@ -722,7 +728,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { describe('lockfileVersion 3', () => { it('uses package-lock.json with npm v9.0.0', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', @@ -763,7 +769,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }); it('uses package-lock.json with npm v7.0.0', async () => { - npm.getNpmLock.mockReturnValue({ + npm.getNpmLock.mockResolvedValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', diff --git a/lib/modules/manager/npm/extract/locked-versions.ts b/lib/modules/manager/npm/extract/locked-versions.ts index 6195f9589739896923046b571fe006dd6d56f410..7e3f1e4a80bd9debd5ae9fe938b4da6ed487374b 100644 --- a/lib/modules/manager/npm/extract/locked-versions.ts +++ b/lib/modules/manager/npm/extract/locked-versions.ts @@ -7,7 +7,7 @@ import type { NpmManagerData } from '../types'; import { getNpmLock } from './npm'; import { getPnpmLock } from './pnpm'; import type { LockFile } from './types'; -import { getYarnLock } from './yarn'; +import { getYarnLock, getYarnVersionFromLock } from './yarn'; export async function getLockedVersions( packageFiles: PackageFile<NpmManagerData>[] @@ -25,18 +25,10 @@ export async function getLockedVersions( logger.trace(`Retrieving/parsing ${yarnLock}`); lockFileCache[yarnLock] = await getYarnLock(yarnLock); } - const { lockfileVersion, isYarn1 } = lockFileCache[yarnLock]; + const { isYarn1 } = lockFileCache[yarnLock]; let yarn: string | undefined; if (!isYarn1 && !packageFile.extractedConstraints?.yarn) { - if (lockfileVersion && lockfileVersion >= 8) { - // https://github.com/yarnpkg/berry/commit/9bcd27ae34aee77a567dd104947407532fa179b3 - yarn = '^3.0.0'; - } else if (lockfileVersion && lockfileVersion >= 6) { - // https://github.com/yarnpkg/berry/commit/f753790380cbda5b55d028ea84b199445129f9ba - yarn = '^2.2.0'; - } else { - yarn = '^2.0.0'; - } + yarn = getYarnVersionFromLock(lockFileCache[yarnLock]); } if (yarn) { packageFile.extractedConstraints ??= {}; diff --git a/lib/modules/manager/npm/extract/yarn.spec.ts b/lib/modules/manager/npm/extract/yarn.spec.ts index 594dddce55ee9bab9c5094a0f4f21f2d72b2cbb3..12e572ddc1e47df44f74d9f7d7972fba8cdeaf63 100644 --- a/lib/modules/manager/npm/extract/yarn.spec.ts +++ b/lib/modules/manager/npm/extract/yarn.spec.ts @@ -1,6 +1,6 @@ import { Fixtures } from '../../../../../test/fixtures'; import { fs } from '../../../../../test/util'; -import { getYarnLock } from './yarn'; +import { getYarnLock, getYarnVersionFromLock } from './yarn'; jest.mock('../../../../util/fs'); @@ -55,4 +55,17 @@ describe('modules/manager/npm/extract/yarn', () => { expect(Object.keys(res.lockedVersions!)).toHaveLength(14); }); }); + + it('getYarnVersionFromLock', () => { + expect(getYarnVersionFromLock({ isYarn1: true })).toBe('^1.22.18'); + expect(getYarnVersionFromLock({ isYarn1: false, lockfileVersion: 8 })).toBe( + '^3.0.0' + ); + expect(getYarnVersionFromLock({ isYarn1: false, lockfileVersion: 6 })).toBe( + '^2.2.0' + ); + expect(getYarnVersionFromLock({ isYarn1: false, lockfileVersion: 3 })).toBe( + '^2.0.0' + ); + }); }); diff --git a/lib/modules/manager/npm/extract/yarn.ts b/lib/modules/manager/npm/extract/yarn.ts index 4a0bf63484224dfa4150e236bc97a03b0c48ebf0..d61b53e51b29393d2672966353756a0beaeb04d9 100644 --- a/lib/modules/manager/npm/extract/yarn.ts +++ b/lib/modules/manager/npm/extract/yarn.ts @@ -85,3 +85,19 @@ export async function isZeroInstall(yarnrcYmlPath: string): Promise<boolean> { } return false; } + +export function getYarnVersionFromLock(lockfile: LockFile): string { + const { lockfileVersion, isYarn1 } = lockfile; + if (isYarn1) { + return '^1.22.18'; + } + if (lockfileVersion && lockfileVersion >= 8) { + // https://github.com/yarnpkg/berry/commit/9bcd27ae34aee77a567dd104947407532fa179b3 + return '^3.0.0'; + } else if (lockfileVersion && lockfileVersion >= 6) { + // https://github.com/yarnpkg/berry/commit/f753790380cbda5b55d028ea84b199445129f9ba + return '^2.2.0'; + } + + return '^2.0.0'; +} diff --git a/lib/modules/manager/npm/post-update/yarn.spec.ts b/lib/modules/manager/npm/post-update/yarn.spec.ts index b17e525e865d32f55b9d0ad426f2e2525ab784ed..01f25c822a0e37431a88123719ec235caa9ea68b 100644 --- a/lib/modules/manager/npm/post-update/yarn.spec.ts +++ b/lib/modules/manager/npm/post-update/yarn.spec.ts @@ -350,7 +350,7 @@ describe('modules/manager/npm/post-update/yarn', () => { Fixtures.mock({}); const execSnapshots = mockExecAll(new Error('some-error')); const res = await yarnHelper.generateLockFile('some-dir', {}); - expect(fs.readFile).toHaveBeenCalledTimes(2); + expect(fs.readFile).toHaveBeenCalledTimes(3); expect(res.error).toBeTrue(); expect(res.lockFile).toBeUndefined(); expect(fixSnapshots(execSnapshots)).toMatchSnapshot(); diff --git a/lib/modules/manager/npm/post-update/yarn.ts b/lib/modules/manager/npm/post-update/yarn.ts index 99a1aa110ba82937fd932032fb1ae9c2b9112905..55e1a01bd88ade4a9f7d0005c08d8322b3be76f4 100644 --- a/lib/modules/manager/npm/post-update/yarn.ts +++ b/lib/modules/manager/npm/post-update/yarn.ts @@ -24,6 +24,7 @@ import { newlineRegex, regEx } from '../../../../util/regex'; import { uniqueStrings } from '../../../../util/string'; import { NpmDatasource } from '../../../datasource/npm'; import type { PostUpdateConfig, Upgrade } from '../../types'; +import { getYarnLock, getYarnVersionFromLock } from '../extract/yarn'; import type { NpmManagerData } from '../types'; import { getNodeToolConstraint } from './node-version'; import type { GenerateLockFileResult } from './types'; @@ -104,13 +105,13 @@ export async function generateLockFile( await getNodeToolConstraint(config, upgrades, lockFileDir, lazyPgkJson), ]; const yarnUpdate = upgrades.find(isYarnUpdate); - const yarnCompatibility = yarnUpdate - ? yarnUpdate.newValue - : config.constraints?.yarn ?? - getPackageManagerVersion('yarn', await lazyPgkJson.getValue()); + const yarnCompatibility = + (yarnUpdate ? yarnUpdate.newValue : config.constraints?.yarn) ?? + getPackageManagerVersion('yarn', await lazyPgkJson.getValue()) ?? + getYarnVersionFromLock(await getYarnLock(lockFileName)); const minYarnVersion = semver.validRange(yarnCompatibility) && - semver.minVersion(yarnCompatibility!); + semver.minVersion(yarnCompatibility); const isYarn1 = !minYarnVersion || minYarnVersion.major === 1; const isYarnDedupeAvailable = minYarnVersion && semver.gte(minYarnVersion, '2.2.0');