renovate/lib/modules/manager/npm/update/locked-dependency/common/parent-version.ts

131 lines
4.6 KiB
TypeScript

import { logger } from '../../../../../../logger';
import type {
GetPkgReleasesConfig,
ReleaseResult,
} from '../../../../../datasource';
import { getPkgReleases } from '../../../../../datasource';
import { api as semver } from '../../../../../versioning/npm';
const pkgCache = new Map<string, Promise<ReleaseResult | null>>();
function getPkgReleasesCached(
packageName: string,
): Promise<ReleaseResult | null> {
let cachedResult = pkgCache.get(packageName);
if (!cachedResult) {
const lookupConfig: GetPkgReleasesConfig = {
datasource: 'npm',
packageName,
};
cachedResult = getPkgReleases(lookupConfig);
pkgCache.set(packageName, cachedResult);
}
return cachedResult;
}
/**
* Finds the first stable version of parentName after parentStartingVersion which either:
* - depends on targetDepName@targetVersion or a range which it satisfies, OR
* - removes the dependency targetDepName altogether, OR
* - depends on any version of targetDepName higher than targetVersion
*/
export async function findFirstParentVersion(
parentName: string,
parentStartingVersion: string,
targetDepName: string,
targetVersion: string,
): Promise<string | null> {
// istanbul ignore if
if (!semver.isVersion(parentStartingVersion)) {
logger.debug('parentStartingVersion is not a version - cannot remediate');
return null;
}
logger.debug(
`Finding first version of ${parentName} starting with ${parentStartingVersion} which supports >= ${targetDepName}@${targetVersion}`,
);
try {
const targetDep = await getPkgReleasesCached(targetDepName);
// istanbul ignore if
if (!targetDep) {
logger.info(
{ targetDepName },
'Could not look up target dependency for remediation',
);
return null;
}
const targetVersions = targetDep.releases
.map((release) => release.version)
.filter(
(version) =>
semver.isVersion(version) &&
semver.isStable(version) &&
(version === targetVersion ||
semver.isGreaterThan(version, targetVersion)),
);
const parentDep = await getPkgReleasesCached(parentName);
// istanbul ignore if
if (!parentDep) {
logger.info(
{ parentName },
'Could not look up parent dependency for remediation',
);
return null;
}
const parentVersions = parentDep.releases
.map((release) => release.version)
.filter(
(version) =>
semver.isVersion(version) &&
semver.isStable(version) &&
(version === parentStartingVersion ||
semver.isGreaterThan(version, parentStartingVersion)),
)
.sort((v1, v2) => semver.sortVersions(v1, v2));
// iterate through parentVersions in sorted order
for (const parentVersion of parentVersions) {
const constraint = parentDep.releases.find(
(release) => release.version === parentVersion,
)?.dependencies?.[targetDepName];
if (!constraint) {
logger.debug(
`${targetDepName} has been removed from ${parentName}@${parentVersion}`,
);
return parentVersion;
}
if (semver.matches(targetVersion, constraint)) {
// could be version or range
logger.debug(
`${targetDepName} needs ${parentName}@${parentVersion} which uses constraint "${constraint}" in order to update to ${targetVersion}`,
);
return parentVersion;
}
if (semver.isVersion(constraint)) {
if (semver.isGreaterThan(constraint, targetVersion)) {
// it's not the version we were after - the parent skipped to a higher version
logger.debug(
`${targetDepName} needs ${parentName}@${parentVersion} which uses version "${constraint}" in order to update to greater than ${targetVersion}`,
);
return parentVersion;
}
} else if (
// check the range against all versions
targetVersions.some((version) => semver.matches(version, constraint))
) {
// the constraint didn't match the version we wanted, but it matches one of the versions higher
logger.debug(
`${targetDepName} needs ${parentName}@${parentVersion} which uses constraint "${constraint}" in order to update to greater than ${targetVersion}`,
);
return parentVersion;
}
}
} catch (err) /* istanbul ignore next */ {
logger.warn(
{ parentName, parentStartingVersion, targetDepName, targetVersion, err },
'findFirstParentVersion error',
);
return null;
}
logger.debug(`Could not find a matching version`);
return null;
}