import type { NewValueConfig, VersioningApi } from '../types.ts'; import { parseRange, parseVersion } from './parser.ts'; import { coerceFloatingComponent, getFloatingRangeLowerBound, matches, rangeToString, tryBump, } from './range.ts'; import type { NugetBracketRange, NugetFloatingRange, NugetVersion, } from './types.ts'; import { compare, versionToString } from './version.ts'; export const id = 'nuget'; export const displayName = 'NuGet'; export const urls = [ '[NuGet package versioning](https://learn.microsoft.com/nuget/concepts/package-versioning)', '[NuGet version range tool](https://nugettools.azurewebsites.net/)', ]; export const supportsRanges = true; export const supportedRangeStrategies = ['bump']; class NugetVersioningApi implements VersioningApi { isCompatible(version: string, _current?: string): boolean { return this.isValid(version); } isSingleVersion(version: string): boolean { const r = parseRange(version); if (!r) { return false; } return r.type === 'nuget-exact-range'; } isStable(version: string): boolean { const v = parseVersion(version); if (v) { return !v.prerelease; } const r = parseRange(version); if (r?.type !== 'nuget-exact-range') { return false; } return !r.version.prerelease; } isValid(input: string): boolean { const v = parseVersion(input); if (v) { return true; } const r = parseRange(input); if (r) { return true; } return false; } isVersion(input: string | undefined | null): boolean { if (!input) { return false; } const v = parseVersion(input); if (!v) { return false; } return true; } getMajor(version: string): number | null { const v = parseVersion(version); if (!v) { return null; } return v.major; } getMinor(version: string): number | null { const v = parseVersion(version); if (!v) { return null; } return v.minor ?? null; } getPatch(version: string): number | null { const v = parseVersion(version); if (!v) { return null; } return v.patch ?? null; } equals(version: string, other: string): boolean { const x = parseVersion(version); const y = parseVersion(other); if (!x || !y) { return false; } return compare(x, y) === 0; } isGreaterThan(version: string, other: string): boolean { const x = parseVersion(version); const y = parseVersion(other); if (!x || !y) { return false; } return compare(x, y) > 0; } isLessThanRange(version: string, range: string): boolean { const v = parseVersion(version); if (!v) { return false; } const u = parseVersion(range); if (u) { return compare(v, u) < 0; } const r = parseRange(range); if (!r) { return false; } if (r.type === 'nuget-exact-range') { return compare(v, r.version) < 0; } if (r.type === 'nuget-bracket-range') { if (!r.min) { return false; } const minBound = r.min.type === 'nuget-version' ? r.min : getFloatingRangeLowerBound(r.min); const cmp = compare(v, minBound); return r.minInclusive ? cmp < 0 : cmp <= 0; } const minBound = getFloatingRangeLowerBound(r); return compare(v, minBound) < 0; } getSatisfyingVersion(versions: string[], range: string): string | null { const r = parseRange(range); if (r) { let result: string | null = null; let vMax: NugetVersion | undefined; for (const version of versions) { const v = parseVersion(version); if (!v) { continue; } if (!matches(v, r)) { continue; } if (!vMax || compare(v, vMax) > 0) { vMax = v; result = version; } } return result; } const u = parseVersion(range); if (u) { let result: string | null = null; let vMax: NugetVersion | undefined; for (const version of versions) { const v = parseVersion(version); if (!v) { continue; } // A bare version acts as a min-version range, but it should // still respect stability: a stable pin must not match a // pre-release version. if (v.prerelease && !u.prerelease) { continue; } if (compare(v, u) < 0) { continue; } if (!vMax || compare(v, vMax) > 0) { vMax = v; result = version; } } return result; } return null; } minSatisfyingVersion(versions: string[], range: string): string | null { const r = parseRange(range); if (r) { let result: string | null = null; let vMin: NugetVersion | undefined; for (const version of versions) { const v = parseVersion(version); if (!v) { continue; } if (!matches(v, r)) { continue; } if (!vMin || compare(v, vMin) < 0) { result = version; vMin = v; } } return result; } const u = parseVersion(range); if (u) { let result: string | null = null; let vMin: NugetVersion | undefined; for (const version of versions) { const v = parseVersion(version); if (!v) { continue; } if (compare(v, u) < 0) { continue; } if (!vMin || compare(v, vMin) < 0) { result = version; vMin = v; } } return result; } return null; } getPinnedValue(newVersion: string): string { const v = parseVersion(newVersion); if (!v) { return ''; } return rangeToString({ type: 'nuget-exact-range', version: v }); } getNewValue({ currentValue, newVersion }: NewValueConfig): string | null { const v = parseVersion(newVersion); if (!v) { return null; } if (this.isVersion(currentValue)) { return newVersion; } const r = parseRange(currentValue); if (!r) { return null; } if (this.isLessThanRange(newVersion, currentValue)) { return currentValue; } if (r.type === 'nuget-exact-range') { return rangeToString({ type: 'nuget-exact-range', version: v }); } if (r.type === 'nuget-floating-range') { const floating = r.floating; if (!floating) { return versionToString(v); } const res: NugetFloatingRange = { ...r }; if (floating === 'major') { res.major = coerceFloatingComponent(v.major); return tryBump(res, v, currentValue); } res.major = v.major; if (floating === 'minor') { res.minor = coerceFloatingComponent(v.minor); return tryBump(res, v, currentValue); } res.minor = v.minor ?? 0; if (floating === 'patch') { res.patch = coerceFloatingComponent(v.patch); return tryBump(res, v, currentValue); } res.patch = v.patch ?? 0; res.revision = coerceFloatingComponent(v.revision); return tryBump(res, v, currentValue); } const res: NugetBracketRange = { ...r }; if (!r.max) { res.min = v; res.minInclusive = true; return rangeToString(res); } if (matches(v, r)) { return currentValue; } if (!r.min) { res.max = v; res.maxInclusive = true; return rangeToString(res); } res.max = v; res.maxInclusive = true; return rangeToString(res); } sortVersions(version: string, other: string): number { const x = parseVersion(version); const y = parseVersion(other); if (!x || !y) { return 0; } return compare(x, y); } matches(version: string, range: string): boolean { const v = parseVersion(version); if (!v) { return false; } const u = parseVersion(range); if (u) { // A bare version acts as a min-version range, but it should // still respect stability: a stable pin must not match a // pre-release version. if (v.prerelease && !u.prerelease) { return false; } return compare(v, u) >= 0; } const r = parseRange(range); if (!r) { return false; } return matches(v, r); } } export const api: VersioningApi = new NugetVersioningApi(); export default api;