diff --git a/lib/util/exec/buildpack.spec.ts b/lib/util/exec/buildpack.spec.ts index 050ac92da49eb0daeb938979dbdf992da313fcf8..7f489a182bd042da281fa2a45dc3bac5a7f3ff02 100644 --- a/lib/util/exec/buildpack.spec.ts +++ b/lib/util/exec/buildpack.spec.ts @@ -48,13 +48,14 @@ describe('util/exec/buildpack', () => { describe('resolveConstraint()', () => { beforeEach(() => { - datasource.getPkgReleases.mockResolvedValueOnce({ + datasource.getPkgReleases.mockResolvedValue({ releases: [ { version: '1.0.0' }, { version: '1.1.0' }, { version: '1.3.0' }, { version: '2.0.14' }, { version: '2.1.0' }, + { version: '2.2.0-pre.0' }, ], }); }); @@ -65,10 +66,36 @@ describe('util/exec/buildpack', () => { ).toBe('1.1.0'); }); - it('returns from latest', async () => { + it('returns highest stable', async () => { expect(await resolveConstraint({ toolName: 'composer' })).toBe('2.1.0'); }); + it('returns highest unstable', async () => { + datasource.getPkgReleases.mockResolvedValue({ + releases: [{ version: '2.0.14-b.1' }, { version: '2.1.0-a.1' }], + }); + expect(await resolveConstraint({ toolName: 'composer' })).toBe( + '2.1.0-a.1' + ); + }); + + it('respects latest', async () => { + datasource.getPkgReleases.mockResolvedValue({ + tags: { + latest: '2.0.14', + }, + releases: [ + { version: '1.0.0' }, + { version: '1.1.0' }, + { version: '1.3.0' }, + { version: '2.0.14' }, + { version: '2.1.0' }, + { version: '2.2.0-pre.0' }, + ], + }); + expect(await resolveConstraint({ toolName: 'composer' })).toBe('2.0.14'); + }); + it('throws for unknown tools', async () => { datasource.getPkgReleases.mockReset(); datasource.getPkgReleases.mockResolvedValueOnce({ diff --git a/lib/util/exec/buildpack.ts b/lib/util/exec/buildpack.ts index 2d79ff792cc09d373df22693e106c06cb5f9dbeb..ab9202a7fd777e2738e8fe440b3923db8be9b43f 100644 --- a/lib/util/exec/buildpack.ts +++ b/lib/util/exec/buildpack.ts @@ -1,3 +1,4 @@ +import is from '@sindresorhus/is'; import { quote } from 'shlex'; import { GlobalConfig } from '../../config/global'; import { logger } from '../../logger'; @@ -100,6 +101,22 @@ export function isDynamicInstall( ); } +function isStable( + version: string, + versioning: allVersioning.VersioningApi, + latest?: string +): boolean { + if (!versioning.isStable(version)) { + return false; + } + if (is.string(latest)) { + if (versioning.isGreaterThan(version, latest)) { + return false; + } + } + return true; +} + export async function resolveConstraint( toolConstraint: ToolConstraint ): Promise<string> { @@ -124,27 +141,51 @@ export async function resolveConstraint( const pkgReleases = await getPkgReleases(toolConfig); const releases = pkgReleases?.releases ?? []; - const versions = releases.map((r) => r.version); - const resolvedVersion = versions - .filter((v) => - constraint ? versioning.matches(v, constraint) : versioning.isStable(v) - ) - .pop(); - if (resolvedVersion) { - logger.debug({ toolName, constraint, resolvedVersion }, 'Resolved version'); - return resolvedVersion; + if (!releases?.length) { + throw new Error('No tool releases found.'); } - const latestVersion = versions.filter((v) => versioning.isStable(v)).pop(); - if (!latestVersion) { - throw new Error('No tool releases found.'); + const matchingReleases = releases.filter( + (r) => !constraint || versioning.matches(r.version, constraint) + ); + + const stableMatchingVersion = matchingReleases + .filter((r) => isStable(r.version, versioning, pkgReleases?.tags?.latest)) + .pop()?.version; + if (stableMatchingVersion) { + logger.debug( + { toolName, constraint, resolvedVersion: stableMatchingVersion }, + 'Resolved stable matching version' + ); + return stableMatchingVersion; + } + + const unstableMatchingVersion = matchingReleases.pop()?.version; + if (unstableMatchingVersion) { + logger.debug( + { toolName, constraint, resolvedVersion: unstableMatchingVersion }, + 'Resolved unstable matching version' + ); + return unstableMatchingVersion; } + + const stableVersion = releases + .filter((r) => isStable(r.version, versioning, pkgReleases?.tags?.latest)) + .pop()?.version; + if (stableVersion) { + logger.warn( + { toolName, constraint, stableVersion }, + 'No matching tool versions found for constraint - using latest stable version' + ); + } + + const highestVersion = releases.pop()!.version; logger.warn( - { toolName, constraint, latestVersion }, - 'No matching tool versions found for constraint - using latest version' + { toolName, constraint, highestVersion }, + 'No matching or stable tool versions found - using an unstable version' ); - return latestVersion; + return highestVersion; } export async function generateInstallCommands(