mirror of https://github.com/renovatebot/renovate
275 lines
7.3 KiB
TypeScript
275 lines
7.3 KiB
TypeScript
import { parseRange } from 'semver-utils';
|
|
import { logger } from '../../../logger';
|
|
import type { RangeStrategy } from '../../../types/versioning';
|
|
import { api as npm } from '../npm';
|
|
import type { NewValueConfig, VersioningApi } from '../types';
|
|
|
|
import { VERSION_PATTERN } from './patterns';
|
|
import {
|
|
npm2poetry,
|
|
poetry2npm,
|
|
poetry2semver,
|
|
semver2poetry,
|
|
} from './transform';
|
|
|
|
export const id = 'poetry';
|
|
export const displayName = 'Poetry';
|
|
export const urls = [
|
|
'https://python-poetry.org/docs/dependency-specification/',
|
|
'https://python-poetry.org/docs/faq#why-does-poetry-not-adhere-to-semantic-versioning',
|
|
'https://python-poetry.org/docs/faq#why-does-poetry-enforce-pep-440-versions',
|
|
];
|
|
export const supportsRanges = true;
|
|
export const supportedRangeStrategies: RangeStrategy[] = [
|
|
'bump',
|
|
'widen',
|
|
'pin',
|
|
'replace',
|
|
];
|
|
|
|
function equals(a: string, b: string): boolean {
|
|
const semverA = poetry2semver(a);
|
|
const semverB = poetry2semver(b);
|
|
return !!(semverA && semverB && npm.equals(semverA, semverB));
|
|
}
|
|
|
|
function getMajor(version: string): number | null {
|
|
const semverVersion = poetry2semver(version);
|
|
return semverVersion ? npm.getMajor(semverVersion) : null;
|
|
}
|
|
|
|
function getMinor(version: string): number | null {
|
|
const semverVersion = poetry2semver(version);
|
|
return semverVersion ? npm.getMinor(semverVersion) : null;
|
|
}
|
|
|
|
function getPatch(version: string): number | null {
|
|
const semverVersion = poetry2semver(version);
|
|
return semverVersion ? npm.getPatch(semverVersion) : null;
|
|
}
|
|
|
|
function isVersion(input: string): boolean {
|
|
return VERSION_PATTERN.test(input);
|
|
}
|
|
|
|
function isGreaterThan(a: string, b: string): boolean {
|
|
const semverA = poetry2semver(a);
|
|
const semverB = poetry2semver(b);
|
|
return !!(semverA && semverB && npm.isGreaterThan(semverA, semverB));
|
|
}
|
|
|
|
function isLessThanRange(version: string, range: string): boolean {
|
|
const semverVersion = poetry2semver(version);
|
|
return !!(
|
|
isVersion(version) &&
|
|
semverVersion &&
|
|
npm.isLessThanRange?.(semverVersion, poetry2npm(range))
|
|
);
|
|
}
|
|
|
|
export function isValid(input: string): boolean {
|
|
if (!input) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return npm.isValid(poetry2npm(input, true));
|
|
} catch {
|
|
logger.once.debug(
|
|
{ version: input },
|
|
'Poetry version or range is not supported by current implementation',
|
|
);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function isStable(version: string): boolean {
|
|
const semverVersion = poetry2semver(version);
|
|
return !!(semverVersion && npm.isStable(semverVersion));
|
|
}
|
|
|
|
function matches(version: string, range: string): boolean {
|
|
const semverVersion = poetry2semver(version);
|
|
return !!(
|
|
isVersion(version) &&
|
|
semverVersion &&
|
|
npm.matches(semverVersion, poetry2npm(range))
|
|
);
|
|
}
|
|
|
|
function getSatisfyingVersion(
|
|
versions: string[],
|
|
range: string,
|
|
): string | null {
|
|
const semverVersions: string[] = [];
|
|
versions.forEach((version) => {
|
|
const semverVersion = poetry2semver(version);
|
|
if (semverVersion) {
|
|
semverVersions.push(semverVersion);
|
|
}
|
|
});
|
|
const npmRange = poetry2npm(range);
|
|
const satisfyingVersion = npm.getSatisfyingVersion(semverVersions, npmRange);
|
|
return satisfyingVersion ? semver2poetry(satisfyingVersion) : null;
|
|
}
|
|
|
|
function minSatisfyingVersion(
|
|
versions: string[],
|
|
range: string,
|
|
): string | null {
|
|
const semverVersions: string[] = [];
|
|
versions.forEach((version) => {
|
|
const semverVersion = poetry2semver(version);
|
|
if (semverVersion) {
|
|
semverVersions.push(semverVersion);
|
|
}
|
|
});
|
|
const npmRange = poetry2npm(range);
|
|
const satisfyingVersion = npm.minSatisfyingVersion(semverVersions, npmRange);
|
|
return satisfyingVersion ? semver2poetry(satisfyingVersion) : null;
|
|
}
|
|
|
|
function isSingleVersion(constraint: string): boolean {
|
|
return (
|
|
(constraint.trim().startsWith('=') &&
|
|
isVersion(constraint.trim().substring(1).trim())) ||
|
|
isVersion(constraint.trim())
|
|
);
|
|
}
|
|
|
|
function handleShort(
|
|
operator: string,
|
|
currentValue: string,
|
|
newVersion: string,
|
|
): string | null {
|
|
const toVersionMajor = getMajor(newVersion);
|
|
const toVersionMinor = getMinor(newVersion);
|
|
const split = currentValue.split('.');
|
|
if (toVersionMajor !== null && split.length === 1) {
|
|
// [^,~]4
|
|
return `${operator}${toVersionMajor}`;
|
|
}
|
|
if (
|
|
toVersionMajor !== null &&
|
|
toVersionMinor !== null &&
|
|
split.length === 2
|
|
) {
|
|
// [^,~]4.1
|
|
return `${operator}${toVersionMajor}.${toVersionMinor}`;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getNewValue({
|
|
currentValue,
|
|
rangeStrategy,
|
|
currentVersion,
|
|
newVersion,
|
|
}: NewValueConfig): string {
|
|
if (rangeStrategy === 'pin') {
|
|
return newVersion;
|
|
}
|
|
if (rangeStrategy === 'replace') {
|
|
const npmCurrentValue = poetry2npm(currentValue);
|
|
try {
|
|
const massagedNewVersion = poetry2semver(newVersion);
|
|
if (
|
|
massagedNewVersion &&
|
|
isVersion(massagedNewVersion) &&
|
|
npm.matches(massagedNewVersion, npmCurrentValue)
|
|
) {
|
|
return currentValue;
|
|
}
|
|
} catch (err) /* istanbul ignore next */ {
|
|
logger.info(
|
|
{ err },
|
|
'Poetry versioning: Error caught checking if newVersion satisfies currentValue',
|
|
);
|
|
}
|
|
const parsedRange = parseRange(npmCurrentValue);
|
|
const element = parsedRange[parsedRange.length - 1];
|
|
if (parsedRange.length === 1 && element.operator) {
|
|
if (element.operator === '^') {
|
|
const version = handleShort('^', npmCurrentValue, newVersion);
|
|
if (version) {
|
|
return npm2poetry(version);
|
|
}
|
|
}
|
|
if (element.operator === '~') {
|
|
const version = handleShort('~', npmCurrentValue, newVersion);
|
|
if (version) {
|
|
return npm2poetry(version);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Explicitly check whether this is a fully-qualified version
|
|
if (
|
|
(VERSION_PATTERN.exec(newVersion)?.groups?.release ?? '').split('.')
|
|
.length !== 3
|
|
) {
|
|
logger.debug(
|
|
'Cannot massage python version to npm - returning currentValue',
|
|
);
|
|
return currentValue;
|
|
}
|
|
try {
|
|
const currentSemverVersion =
|
|
currentVersion && poetry2semver(currentVersion);
|
|
|
|
const newSemverVersion = poetry2semver(newVersion);
|
|
|
|
if (currentSemverVersion && newSemverVersion) {
|
|
const newSemver = npm.getNewValue({
|
|
currentValue: poetry2npm(currentValue),
|
|
rangeStrategy,
|
|
currentVersion: currentSemverVersion,
|
|
newVersion: newSemverVersion,
|
|
});
|
|
const newPoetry = newSemver && npm2poetry(newSemver);
|
|
if (newPoetry) {
|
|
return newPoetry;
|
|
}
|
|
}
|
|
} catch (err) /* istanbul ignore next */ {
|
|
logger.debug(
|
|
{ currentValue, rangeStrategy, currentVersion, newVersion, err },
|
|
'Could not generate new value using npm.getNewValue()',
|
|
);
|
|
}
|
|
|
|
// istanbul ignore next
|
|
return currentValue;
|
|
}
|
|
|
|
function sortVersions(a: string, b: string): number {
|
|
// istanbul ignore next
|
|
return npm.sortVersions(poetry2semver(a) ?? '', poetry2semver(b) ?? '');
|
|
}
|
|
|
|
function subset(subRange: string, superRange: string): boolean | undefined {
|
|
return npm.subset!(poetry2npm(subRange), poetry2npm(superRange));
|
|
}
|
|
|
|
export const api: VersioningApi = {
|
|
equals,
|
|
getMajor,
|
|
getMinor,
|
|
getPatch,
|
|
getNewValue,
|
|
getSatisfyingVersion,
|
|
isCompatible: isVersion,
|
|
isGreaterThan,
|
|
isLessThanRange,
|
|
isSingleVersion,
|
|
isStable,
|
|
isValid,
|
|
isVersion,
|
|
matches,
|
|
minSatisfyingVersion,
|
|
sortVersions,
|
|
subset,
|
|
};
|
|
export default api;
|