fix(plugins): accept legacy clawhub api ranges

This commit is contained in:
Vincent Koc
2026-05-02 11:42:55 -07:00
parent 3b347d1c7e
commit 559bf7df1f
2 changed files with 27 additions and 5 deletions

View File

@@ -100,6 +100,14 @@ describe("clawhub helpers", () => {
expect(satisfiesPluginApiRange("invalid", "^1.2.0")).toBe(false);
});
it("accepts legacy bare major.minor plugin api ranges as lower bounds", () => {
expect(satisfiesPluginApiRange("2026.5.2", "2026.4")).toBe(true);
expect(satisfiesPluginApiRange("2026.4.0", "2026.4")).toBe(true);
expect(satisfiesPluginApiRange("2026.3.99", "2026.4")).toBe(false);
expect(satisfiesPluginApiRange("2026.5.2", "=2026.4")).toBe(false);
expect(satisfiesPluginApiRange("invalid", "2026.4")).toBe(false);
});
it.each(["*", "x", "X", "=*", "=x", ">=*", ">=x", "<=*", "^*", "~*"] as const)(
"accepts plugin api wildcard range %s for valid runtime versions",
(range) => {

View File

@@ -444,12 +444,25 @@ export async function resolveClawHubAuthToken(): Promise<string | undefined> {
return undefined;
}
function normalizePartialComparableVersion(version: string): {
version: string;
isPartial: boolean;
} {
const trimmed = version.trim();
return /^[vV]?[0-9]+\.[0-9]+$/.test(trimmed)
? { version: `${trimmed}.0`, isPartial: true }
: { version: trimmed, isPartial: false };
}
function compareSemver(left: string, right: string): number | null {
return compareComparableSemver(parseComparableSemver(left), parseComparableSemver(right));
return compareComparableSemver(
parseComparableSemver(normalizePartialComparableVersion(left).version),
parseComparableSemver(normalizePartialComparableVersion(right).version),
);
}
function upperBoundForCaret(version: string): string | null {
const parsed = parseComparableSemver(version);
const parsed = parseComparableSemver(normalizePartialComparableVersion(version).version);
if (!parsed) {
return null;
}
@@ -492,12 +505,13 @@ function satisfiesComparator(version: string, token: string): boolean {
if (!match) {
return false;
}
const operator = match[1] ?? "=";
const operator = match[1];
const target = match[2]?.trim();
if (!target) {
return false;
}
const cmp = compareSemver(version, target);
const normalizedTarget = normalizePartialComparableVersion(target);
const cmp = compareSemver(version, normalizedTarget.version);
if (cmp == null) {
return false;
}
@@ -512,7 +526,7 @@ function satisfiesComparator(version: string, token: string): boolean {
return cmp < 0;
case "=":
default:
return cmp === 0;
return normalizedTarget.isPartial && !operator ? cmp >= 0 : cmp === 0;
}
}