mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-13 15:36:25 +00:00
fix(pnpm): defer lock file constraint parsing (#22311)
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
This commit is contained in:
parent
4e255cc99f
commit
d4d742c464
6 changed files with 124 additions and 179 deletions
|
@ -520,11 +520,10 @@ describe('modules/manager/npm/extract/locked-versions', () => {
|
|||
packageFile: 'some-file',
|
||||
},
|
||||
];
|
||||
pnpm.getConstraints.mockReturnValue('>=6.0.0 >=8');
|
||||
await getLockedVersions(packageFiles);
|
||||
expect(packageFiles).toEqual([
|
||||
{
|
||||
extractedConstraints: { pnpm: '>=6.0.0 >=8' },
|
||||
extractedConstraints: { pnpm: '>=6.0.0' },
|
||||
deps: [
|
||||
{ currentValue: '1.0.0', depName: 'a', lockedVersion: '1.0.0' },
|
||||
{ currentValue: '2.0.0', depName: 'b', lockedVersion: '2.0.0' },
|
||||
|
|
|
@ -3,7 +3,7 @@ import { logger } from '../../../../logger';
|
|||
import type { PackageFile } from '../../types';
|
||||
import type { NpmManagerData } from '../types';
|
||||
import { getNpmLock } from './npm';
|
||||
import { getConstraints, getPnpmLock } from './pnpm';
|
||||
import { getPnpmLock } from './pnpm';
|
||||
import type { LockFile } from './types';
|
||||
import { getYarnLock } from './yarn';
|
||||
|
||||
|
@ -106,14 +106,6 @@ export async function getLockedVersions(
|
|||
logger.trace(`Retrieving/parsing ${pnpmShrinkwrap}`);
|
||||
lockFileCache[pnpmShrinkwrap] = await getPnpmLock(pnpmShrinkwrap);
|
||||
}
|
||||
const { lockfileVersion } = lockFileCache[pnpmShrinkwrap];
|
||||
if (lockfileVersion) {
|
||||
packageFile.extractedConstraints ??= {};
|
||||
packageFile.extractedConstraints.pnpm = getConstraints(
|
||||
lockfileVersion,
|
||||
packageFile.extractedConstraints.pnpm
|
||||
);
|
||||
}
|
||||
|
||||
for (const dep of packageFile.deps) {
|
||||
// TODO: types (#7154)
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
detectPnpmWorkspaces,
|
||||
extractPnpmFilters,
|
||||
findPnpmWorkspace,
|
||||
getConstraints,
|
||||
getPnpmLock,
|
||||
} from './pnpm';
|
||||
|
||||
|
@ -231,53 +230,6 @@ describe('modules/manager/npm/extract/pnpm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getConstraints()', () => {
|
||||
// no constraints
|
||||
it.each([
|
||||
[6.0, undefined, '>=8'],
|
||||
[5.4, undefined, '>=7 <8'],
|
||||
[5.3, undefined, '>=6 <7'],
|
||||
[5.2, undefined, '>=5.10.0 <6'],
|
||||
[5.1, undefined, '>=3.5.0 <5.9.3'],
|
||||
[5.0, undefined, '>=3 <3.5.0'],
|
||||
])('adds constraints for %f', (lockfileVersion, constraints, expected) => {
|
||||
expect(getConstraints(lockfileVersion, constraints)).toBe(expected);
|
||||
});
|
||||
|
||||
// constraints present
|
||||
it.each([
|
||||
[6.0, '>=8.2.0', '>=8.2.0'],
|
||||
[6.0, '>=7', '>=7 >=8'],
|
||||
|
||||
[5.4, '^7.2.0', '^7.2.0'],
|
||||
[5.4, '<7.2.0', '<7.2.0 >=7'],
|
||||
[5.4, '>7.2.0', '>7.2.0 <8'],
|
||||
[5.4, '>=6', '>=6 >=7 <8'],
|
||||
|
||||
[5.3, '^6.0.0', '^6.0.0'],
|
||||
[5.3, '<6.2.0', '<6.2.0 >=6'],
|
||||
[5.3, '>6.2.0', '>6.2.0 <7'],
|
||||
[5.3, '>=5', '>=5 >=6 <7'],
|
||||
|
||||
[5.2, '5.10.0', '5.10.0'],
|
||||
[5.2, '>5.0.0 <5.18.0', '>5.0.0 <5.18.0 >=5.10.0'],
|
||||
[5.2, '>5.10.0', '>5.10.0 <6'],
|
||||
[5.2, '>=5', '>=5 >=5.10.0 <6'],
|
||||
|
||||
[5.1, '^4.0.0', '^4.0.0'],
|
||||
[5.1, '<4', '<4 >=3.5.0'],
|
||||
[5.1, '>=4', '>=4 <5.9.3'],
|
||||
[5.1, '>=3', '>=3 >=3.5.0 <5.9.3'],
|
||||
|
||||
[5.0, '3.1.0', '3.1.0'],
|
||||
[5.0, '^3.0.0', '^3.0.0 <3.5.0'],
|
||||
[5.0, '>=3', '>=3 <3.5.0'],
|
||||
[5.0, '>=2', '>=2 >=3 <3.5.0'],
|
||||
])('adds constraints for %f', (lockfileVersion, constraints, expected) => {
|
||||
expect(getConstraints(lockfileVersion, constraints)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getPnpmLock()', () => {
|
||||
const readLocalFile = jest.spyOn(fs, 'readLocalFile');
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import is from '@sindresorhus/is';
|
||||
import { findPackages } from 'find-packages';
|
||||
import { load } from 'js-yaml';
|
||||
import semver from 'semver';
|
||||
import upath from 'upath';
|
||||
import { GlobalConfig } from '../../../../config/global';
|
||||
import { logger } from '../../../../logger';
|
||||
|
@ -191,101 +190,3 @@ export async function getPnpmLock(filePath: string): Promise<LockFile> {
|
|||
return { lockedVersions: {} };
|
||||
}
|
||||
}
|
||||
|
||||
export function getConstraints(
|
||||
lockfileVersion: number,
|
||||
constraints?: string
|
||||
): string {
|
||||
let newConstraints = constraints;
|
||||
|
||||
// find matching lockfileVersion and use its constraints
|
||||
// if no match found use lockfileVersion 5
|
||||
// lockfileVersion 5 is the minimum version required to generate the pnpm-lock.yaml file
|
||||
const { lowerBound, upperBound, lowerConstraint, upperConstraint } =
|
||||
lockToPnpmVersionMapping.find(
|
||||
(m) => m.lockfileVersion === lockfileVersion
|
||||
) ?? {
|
||||
lockfileVersion: 5.0,
|
||||
lowerBound: '2.24.0',
|
||||
upperBound: '3.5.0',
|
||||
lowerConstraint: '>=3',
|
||||
upperConstraint: '<3.5.0',
|
||||
};
|
||||
|
||||
// inorder to ensure that the constraint doesn't allow any pnpm versions that can't generate the extracted lockfileVersion
|
||||
// compare the current constraint to the lowerBound and upperBound of the lockfileVersion
|
||||
// if the current constraint is not comaptible, add the lowerConstraint and upperConstraint, whichever is needed
|
||||
if (newConstraints) {
|
||||
// if constraint satisfies versions lower than lowerBound add the lowerConstraint to narrow the range
|
||||
if (semver.satisfies(lowerBound, newConstraints)) {
|
||||
newConstraints += ` ${lowerConstraint}`;
|
||||
}
|
||||
|
||||
// if constraint satisfies versions higher than upperBound add the upperConstraint to narrow the range
|
||||
if (
|
||||
upperBound &&
|
||||
upperConstraint &&
|
||||
semver.satisfies(upperBound, newConstraints)
|
||||
) {
|
||||
newConstraints += ` ${upperConstraint}`;
|
||||
}
|
||||
}
|
||||
// if no constraint is present, add the lowerConstraint and upperConstraint corresponding to the lockfileVersion
|
||||
else {
|
||||
newConstraints = `${lowerConstraint}${
|
||||
upperConstraint ? ` ${upperConstraint}` : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
return newConstraints;
|
||||
}
|
||||
|
||||
/**
|
||||
pnpm lockfiles have corresponding version numbers called "lockfileVersion"
|
||||
each lockfileVersion can only be generated by a certain pnpm version ranges
|
||||
eg. lockfileVersion: 5.4 can only be generated by pnpm version >=7 && <8
|
||||
official list can be found here : https:github.com/pnpm/spec/tree/master/lockfile
|
||||
we use the mapping present below to find the compatible pnpm version range for a given lockfileVersion
|
||||
|
||||
the various terms used in the mapping are explained below:
|
||||
lowerConstriant : lowest pnpm version that can generate the lockfileVersion
|
||||
upperConstraint : highest pnpm version that can generate the lockfileVersion
|
||||
lowerBound : highest pnpm version that is less than the lowerConstraint
|
||||
upperBound : lowest pnpm version that is greater than upperConstraint
|
||||
|
||||
For handling future lockfileVersions, we need to:
|
||||
1. add a upperBound and upperConstraint to the current lastest lockfileVersion
|
||||
2. add an object for the new lockfileVersion with lowerBound and lowerConstraint
|
||||
*/
|
||||
|
||||
const lockToPnpmVersionMapping = [
|
||||
{ lockfileVersion: 6.0, lowerBound: '7.32.0', lowerConstraint: '>=8' },
|
||||
{
|
||||
lockfileVersion: 5.4,
|
||||
lowerBound: '6.35.1',
|
||||
upperBound: '8.0.0',
|
||||
lowerConstraint: '>=7',
|
||||
upperConstraint: '<8',
|
||||
},
|
||||
{
|
||||
lockfileVersion: 5.3,
|
||||
lowerBound: '5.18.10',
|
||||
upperBound: '7.0.0',
|
||||
lowerConstraint: '>=6',
|
||||
upperConstraint: '<7',
|
||||
},
|
||||
{
|
||||
lockfileVersion: 5.2,
|
||||
lowerBound: '5.9.3',
|
||||
upperBound: '5.18.10',
|
||||
lowerConstraint: '>=5.10.0',
|
||||
upperConstraint: '<6',
|
||||
},
|
||||
{
|
||||
lockfileVersion: 5.1,
|
||||
lowerBound: '3.4.1',
|
||||
upperBound: '5.9.3',
|
||||
lowerConstraint: '>=3.5.0',
|
||||
upperConstraint: '<5.9.3',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -266,4 +266,42 @@ describe('modules/manager/npm/post-update/pnpm', () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
|
||||
describe('getConstraintsFromLockFile()', () => {
|
||||
it('returns null if no lock file', async () => {
|
||||
fs.readLocalFile.mockResolvedValueOnce(null);
|
||||
const res = await pnpmHelper.getConstraintFromLockFile('some-file-name');
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when error reading lock file', async () => {
|
||||
fs.readLocalFile.mockRejectedValueOnce(new Error('foo'));
|
||||
const res = await pnpmHelper.getConstraintFromLockFile('some-file-name');
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if no lockfileVersion', async () => {
|
||||
fs.readLocalFile.mockResolvedValueOnce('foo: bar\n');
|
||||
const res = await pnpmHelper.getConstraintFromLockFile('some-file-name');
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null if lockfileVersion is not a number', async () => {
|
||||
fs.readLocalFile.mockResolvedValueOnce('lockfileVersion: foo\n');
|
||||
const res = await pnpmHelper.getConstraintFromLockFile('some-file-name');
|
||||
expect(res).toBeNull();
|
||||
});
|
||||
|
||||
it('returns default if lockfileVersion is 1', async () => {
|
||||
fs.readLocalFile.mockResolvedValueOnce('lockfileVersion: 1\n');
|
||||
const res = await pnpmHelper.getConstraintFromLockFile('some-file-name');
|
||||
expect(res).toBe('>=3 <3.5.0');
|
||||
});
|
||||
|
||||
it('maps supported versions', async () => {
|
||||
fs.readLocalFile.mockResolvedValueOnce('lockfileVersion: 5.3\n');
|
||||
const res = await pnpmHelper.getConstraintFromLockFile('some-file-name');
|
||||
expect(res).toBe('>=6 <7');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -41,9 +41,10 @@ export async function generateLockFile(
|
|||
const pnpmToolConstraint: ToolConstraint = {
|
||||
toolName: 'pnpm',
|
||||
constraint:
|
||||
getPnpmConstraintFromUpgrades(upgrades) ??
|
||||
config.constraints?.pnpm ??
|
||||
(await getPnpmConstraint(lockFileDir)),
|
||||
getPnpmConstraintFromUpgrades(upgrades) ?? // if pnpm is being upgraded, it comes first
|
||||
config.constraints?.pnpm ?? // from user config or extraction
|
||||
(await getPnpmConstraintFromPackageFile(lockFileDir)) ?? // look in package.json > packageManager or engines
|
||||
(await getConstraintFromLockFile(lockFileName)), // use lockfileVersion to find pnpm version range
|
||||
};
|
||||
|
||||
const extraEnv: ExtraEnv = {
|
||||
|
@ -116,10 +117,10 @@ export async function generateLockFile(
|
|||
return { lockFile };
|
||||
}
|
||||
|
||||
async function getPnpmConstraint(
|
||||
export async function getPnpmConstraintFromPackageFile(
|
||||
lockFileDir: string
|
||||
): Promise<string | undefined> {
|
||||
let result: string | undefined;
|
||||
let constraint: string | undefined;
|
||||
const rootPackageJson = upath.join(lockFileDir, 'package.json');
|
||||
const content = await readLocalFile(rootPackageJson, 'utf8');
|
||||
if (content) {
|
||||
|
@ -129,27 +130,89 @@ async function getPnpmConstraint(
|
|||
const nameAndVersion = packageManager.split('@');
|
||||
const name = nameAndVersion[0];
|
||||
if (name === 'pnpm') {
|
||||
result = nameAndVersion[1];
|
||||
constraint = nameAndVersion[1];
|
||||
}
|
||||
} else {
|
||||
const engines = packageJson?.engines;
|
||||
if (engines) {
|
||||
result = engines['pnpm'];
|
||||
constraint = engines['pnpm'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
const lockFileName = upath.join(lockFileDir, 'pnpm-lock.yaml');
|
||||
const content = await readLocalFile(lockFileName, 'utf8');
|
||||
if (content) {
|
||||
const pnpmLock = load(content) as PnpmLockFile;
|
||||
if (
|
||||
is.number(pnpmLock.lockfileVersion) &&
|
||||
pnpmLock.lockfileVersion < 5.4
|
||||
) {
|
||||
result = '<7';
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return constraint;
|
||||
}
|
||||
|
||||
export async function getConstraintFromLockFile(
|
||||
lockFileName: string
|
||||
): Promise<string | null> {
|
||||
let constraint: string | null = null;
|
||||
try {
|
||||
const lockfileContent = await readLocalFile(lockFileName, 'utf8');
|
||||
if (!lockfileContent) {
|
||||
return null;
|
||||
}
|
||||
const pnpmLock = load(lockfileContent) as PnpmLockFile;
|
||||
if (!is.number(pnpmLock?.lockfileVersion)) {
|
||||
return null;
|
||||
}
|
||||
// find matching lockfileVersion and use its constraints
|
||||
// if no match found use lockfileVersion 5
|
||||
// lockfileVersion 5 is the minimum version required to generate the pnpm-lock.yaml file
|
||||
const { lowerConstraint, upperConstraint } = lockToPnpmVersionMapping.find(
|
||||
(m) => m.lockfileVersion === pnpmLock.lockfileVersion
|
||||
) ?? {
|
||||
lockfileVersion: 5.0,
|
||||
lowerConstraint: '>=3',
|
||||
upperConstraint: '<3.5.0',
|
||||
};
|
||||
constraint = lowerConstraint;
|
||||
if (upperConstraint) {
|
||||
constraint += ` ${upperConstraint}`;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'Error getting pnpm constraints from lock file');
|
||||
}
|
||||
return constraint;
|
||||
}
|
||||
|
||||
/**
|
||||
pnpm lockfiles have corresponding version numbers called "lockfileVersion"
|
||||
each lockfileVersion can only be generated by a certain pnpm version ranges
|
||||
eg. lockfileVersion: 5.4 can only be generated by pnpm version >=7 && <8
|
||||
official list can be found here : https://github.com/pnpm/spec/tree/master/lockfile
|
||||
we use the mapping present below to find the compatible pnpm version range for a given lockfileVersion
|
||||
|
||||
the various terms used in the mapping are explained below:
|
||||
lowerConstriant : lowest pnpm version that can generate the lockfileVersion
|
||||
upperConstraint : highest pnpm version that can generate the lockfileVersion
|
||||
lowerBound : highest pnpm version that is less than the lowerConstraint
|
||||
upperBound : lowest pnpm version that is greater than upperConstraint
|
||||
|
||||
For handling future lockfileVersions, we need to:
|
||||
1. add a upperBound and upperConstraint to the current lastest lockfileVersion
|
||||
2. add an object for the new lockfileVersion with lowerBound and lowerConstraint
|
||||
*/
|
||||
|
||||
const lockToPnpmVersionMapping = [
|
||||
{ lockfileVersion: 6.0, lowerConstraint: '>=8' },
|
||||
{
|
||||
lockfileVersion: 5.4,
|
||||
lowerConstraint: '>=7',
|
||||
upperConstraint: '<8',
|
||||
},
|
||||
{
|
||||
lockfileVersion: 5.3,
|
||||
lowerConstraint: '>=6',
|
||||
upperConstraint: '<7',
|
||||
},
|
||||
{
|
||||
lockfileVersion: 5.2,
|
||||
lowerConstraint: '>=5.10.0',
|
||||
upperConstraint: '<6',
|
||||
},
|
||||
{
|
||||
lockfileVersion: 5.1,
|
||||
lowerConstraint: '>=3.5.0',
|
||||
upperConstraint: '<5.9.3',
|
||||
},
|
||||
];
|
||||
|
|
Loading…
Reference in a new issue