Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Extract ReleaseResult filtering functions #23253

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 131 additions & 70 deletions lib/modules/datasource/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { ExternalHostError } from '../../types/errors/external-host-error';
import * as memCache from '../../util/cache/memory';
import * as packageCache from '../../util/cache/package';
import { clone } from '../../util/clone';
import { filterMap } from '../../util/filter-map';
import { regEx } from '../../util/regex';
import { Result } from '../../util/result';
import { uniq } from '../../util/uniq';
import { trimTrailingSlash } from '../../util/url';
import { defaultVersioning } from '../versioning';
import * as allVersioning from '../versioning';
Expand Down Expand Up @@ -343,6 +343,131 @@ function getRawReleases(
return promisedRes;
}

function applyExtractVersion<
Config extends Pick<GetPkgReleasesConfig, 'extractVersion'>
>(config: Config, releaseResult: ReleaseResult): void {
if (!config.extractVersion) {
return;
}

const extractVersionRegEx = regEx(config.extractVersion);
releaseResult.releases = filterMap(releaseResult.releases, (release) => {
const version = extractVersionRegEx.exec(release.version)?.groups?.version;
if (!version) {
return null;
}

release.version = version;
return release;
});
}

function filterValidVersions<
Config extends Pick<GetPkgReleasesConfig, 'versioning' | 'datasource'>
>(config: Config, releaseResult: ReleaseResult): void {
const versioningName =
config.versioning ?? getDefaultVersioning(config.datasource);
const versioning = allVersioning.get(versioningName);

releaseResult.releases = filterMap(releaseResult.releases, (release) =>
versioning.isVersion(release.version) ? release : null
);
}

function sortAndRemoveDuplicates<
Config extends Pick<GetPkgReleasesConfig, 'versioning' | 'datasource'>
>(config: Config, releaseResult: ReleaseResult): void {
const versioningName =
config.versioning ?? getDefaultVersioning(config.datasource);
const versioning = allVersioning.get(versioningName);

releaseResult.releases = releaseResult.releases.sort((a, b) =>
versioning.sortVersions(a.version, b.version)
);

// Once releases are sorted, deduplication is straightforward and efficient
let previousVersion: string | null = null;
releaseResult.releases = filterMap(releaseResult.releases, (release) => {
if (previousVersion === release.version) {
return null;
}
previousVersion = release.version;
return release;
});
}

function applyConstraintsFiltering<
Config extends Pick<
GetPkgReleasesConfig,
| 'constraintsFiltering'
| 'versioning'
| 'datasource'
| 'constraints'
| 'packageName'
>
>(config: Config, releaseResult: ReleaseResult): void {
if (config?.constraintsFiltering !== 'strict') {
for (const release of releaseResult.releases) {
delete release.constraints;
}
return;
}

const versioningName =
config.versioning ?? getDefaultVersioning(config.datasource);
const versioning = allVersioning.get(versioningName);

const configConstraints = config.constraints;
const filteredReleases: string[] = [];
releaseResult.releases = filterMap(releaseResult.releases, (release) => {
const releaseConstraints = release.constraints;
delete release.constraints;

// istanbul ignore if
if (!configConstraints || !releaseConstraints) {
return release;
}

for (const [name, configConstraint] of Object.entries(configConstraints)) {
// istanbul ignore if
if (!versioning.isValid(configConstraint)) {
continue;
}

const constraint = releaseConstraints[name];
if (!is.nonEmptyArray(constraint)) {
// A release with no constraints is OK
continue;
}

const satisfiesConstraints = constraint.some(
// If the constraint value is a subset of any release's constraints, then it's OK
// fallback to release's constraint match if subset is not supported by versioning
(releaseConstraint) =>
!releaseConstraint ||
(versioning.subset?.(configConstraint, releaseConstraint) ??
versioning.matches(configConstraint, releaseConstraint))
);

if (!satisfiesConstraints) {
filteredReleases.push(release.version);
return null;
}
}

return release;
});

if (filteredReleases.length) {
const count = filteredReleases.length;
const packageName = config.packageName;
const releases = filteredReleases.join(', ');
logger.debug(
`Filtered ${count} releases for ${packageName} due to constraintsFiltering=strict: ${releases}`
);
}
}

export async function getPkgReleases(
config: GetPkgReleasesConfig
): Promise<ReleaseResult | null> {
Expand Down Expand Up @@ -373,75 +498,11 @@ export async function getPkgReleases(
if (!res) {
return res;
}
if (config.extractVersion) {
const extractVersionRegEx = regEx(config.extractVersion);
res.releases = res.releases
.map((release) => {
const version = extractVersionRegEx.exec(release.version)?.groups
?.version;
if (version) {
return { ...release, version }; // overwrite version
}
return null; // filter out any we can't extract
})
.filter(is.truthy);
}
// Use the datasource's default versioning if none is configured
const versioning =
config.versioning ?? getDefaultVersioning(config.datasource);
const version = allVersioning.get(versioning);

// Filter and sort valid versions
res.releases = res.releases
.filter((release) => version.isVersion(release.version))
.sort((a, b) => version.sortVersions(a.version, b.version));

// Filter versions for uniqueness
res.releases = uniq(res.releases, (x, y) => x.version === y.version);

if (config?.constraintsFiltering === 'strict') {
const filteredReleases: string[] = [];
// Filter releases for compatibility
for (const [constraintName, constraintValue] of Object.entries(
config.constraints ?? {}
)) {
if (version.isValid(constraintValue)) {
res.releases = res.releases.filter((release) => {
const constraint = release.constraints?.[constraintName];
if (!is.nonEmptyArray(constraint)) {
// A release with no constraints is OK
return true;
}

const satisfiesConstraints = constraint.some(
// If the constraint value is a subset of any release's constraints, then it's OK
// fallback to release's constraint match if subset is not supported by versioning
(releaseConstraint) =>
!releaseConstraint ||
(version.subset?.(constraintValue, releaseConstraint) ??
version.matches(constraintValue, releaseConstraint))
);
if (!satisfiesConstraints) {
filteredReleases.push(release.version);
}
return satisfiesConstraints;
});
}
}
if (filteredReleases.length) {
logger.debug(
`Filtered ${
filteredReleases.length
} releases for ${packageName} due to constraintsFiltering=strict: ${filteredReleases.join(
', '
)}`
);
}
}
// Strip constraints from releases result
res.releases.forEach((release) => {
delete release.constraints;
});

applyExtractVersion(config, res);
filterValidVersions(config, res);
sortAndRemoveDuplicates(config, res);
applyConstraintsFiltering(config, res);
return res;
}

Expand Down