Skip to content

Commit

Permalink
fix(manager/npm): determine yarn version from lockfile (#23975)
Browse files Browse the repository at this point in the history
  • Loading branch information
viceice committed Aug 21, 2023
1 parent 04df5c6 commit 375d267
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 40 deletions.
50 changes: 28 additions & 22 deletions 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', () => {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -492,7 +498,7 @@ describe('modules/manager/npm/extract/locked-versions', () => {
});

it('uses pnpm-lock', async () => {
pnpm.getPnpmLock.mockReturnValue({
pnpm.getPnpmLock.mockResolvedValue({
lockedVersionsWithPath: {
'.': {
dependencies: {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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,
});
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
14 changes: 3 additions & 11 deletions lib/modules/manager/npm/extract/locked-versions.ts
Expand Up @@ -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>[]
Expand All @@ -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 ??= {};
Expand Down
15 changes: 14 additions & 1 deletion 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');

Expand Down Expand Up @@ -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'
);
});
});
16 changes: 16 additions & 0 deletions lib/modules/manager/npm/extract/yarn.ts
Expand Up @@ -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';
}
2 changes: 1 addition & 1 deletion lib/modules/manager/npm/post-update/yarn.spec.ts
Expand Up @@ -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();
Expand Down
11 changes: 6 additions & 5 deletions lib/modules/manager/npm/post-update/yarn.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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');
Expand Down

0 comments on commit 375d267

Please sign in to comment.