Files
openclaw/src/plugins/min-host-version.ts
2026-05-02 03:38:48 +01:00

95 lines
2.8 KiB
TypeScript

import { isAtLeast, parseSemver } from "../infra/runtime-guard.js";
export const MIN_HOST_VERSION_FORMAT =
'openclaw.install.minHostVersion must use a semver floor in the form ">=x.y.z[-prerelease][+build]"';
const SEMVER_LABEL_RE = String.raw`\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?`;
const MIN_HOST_VERSION_RE = new RegExp(`^>=(${SEMVER_LABEL_RE})$`);
const LEGACY_MIN_HOST_VERSION_RE = /^(\d+)\.(\d+)\.(\d+)$/;
export type MinHostVersionRequirement = {
raw: string;
minimumLabel: string;
};
import { normalizeOptionalString } from "../shared/string-coerce.js";
export type MinHostVersionCheckResult =
| { ok: true; requirement: MinHostVersionRequirement | null }
| { ok: false; kind: "invalid"; error: string }
| { ok: false; kind: "unknown_host_version"; requirement: MinHostVersionRequirement }
| {
ok: false;
kind: "incompatible";
requirement: MinHostVersionRequirement;
currentVersion: string;
};
export function parseMinHostVersionRequirement(
raw: unknown,
options: { allowLegacyBareSemver?: boolean } = {},
): MinHostVersionRequirement | null {
if (typeof raw !== "string") {
return null;
}
const trimmed = raw.trim();
if (!trimmed) {
return null;
}
const match =
trimmed.match(MIN_HOST_VERSION_RE) ??
(options.allowLegacyBareSemver ? trimmed.match(LEGACY_MIN_HOST_VERSION_RE) : null);
if (!match) {
return null;
}
const minimumLabel = match.length >= 4 ? `${match[1]}.${match[2]}.${match[3]}` : (match[1] ?? "");
if (!parseSemver(minimumLabel)) {
return null;
}
return {
raw: trimmed,
minimumLabel,
};
}
export function validateMinHostVersion(raw: unknown): string | null {
if (raw === undefined) {
return null;
}
return parseMinHostVersionRequirement(raw) ? null : MIN_HOST_VERSION_FORMAT;
}
export function checkMinHostVersion(params: {
currentVersion: string | undefined;
minHostVersion: unknown;
allowLegacyBareSemver?: boolean;
}): MinHostVersionCheckResult {
if (params.minHostVersion === undefined) {
return { ok: true, requirement: null };
}
const requirement = parseMinHostVersionRequirement(params.minHostVersion, {
allowLegacyBareSemver: params.allowLegacyBareSemver,
});
if (!requirement) {
return { ok: false, kind: "invalid", error: MIN_HOST_VERSION_FORMAT };
}
const currentVersion = normalizeOptionalString(params.currentVersion) || "unknown";
const currentSemver = parseSemver(currentVersion);
if (!currentSemver) {
return {
ok: false,
kind: "unknown_host_version",
requirement,
};
}
const minimumSemver = parseSemver(requirement.minimumLabel)!;
if (!isAtLeast(currentSemver, minimumSemver)) {
return {
ok: false,
kind: "incompatible",
requirement,
currentVersion,
};
}
return { ok: true, requirement };
}