feat(viewer): firmware, installed-version and space checks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
phantomptrandClaude Opus 5.5 committed 2026-09-27 23:20:37 -07:00
1 parent 2ed82f8554
commit 29052d27cd
2 files changed
+79

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { firmwareCheck, installedCheck, spaceCheck, versionCompare } from "./packageChecks";
describe("package checks", () => {
it("firmware: bad when the console is older than the package needs", () => {
expect(firmwareCheck("12.00", "5.10").verdict).toBe("bad");
expect(firmwareCheck("5.10", "9.60").verdict).toBe("ok");
expect(firmwareCheck("5.10", "5.10").verdict).toBe("ok");
expect(firmwareCheck(null, "9.60").verdict).toBe("unknown");
expect(firmwareCheck("5.10", null).verdict).toBe("unknown");
});
it("versions compare numerically across 2- and 3-part forms", () => {
expect(versionCompare("01.04", "01.02")).toBeGreaterThan(0);
expect(versionCompare("01.002.000", "01.002")).toBe(0);
expect(versionCompare("01.10", "01.9")).toBeGreaterThan(0);
});
it("installed: newer / older / same / not installed", () => {
expect(installedCheck("01.04", null).relation).toBe("not-installed");
expect(installedCheck("01.04", "01.02").relation).toBe("newer");
expect(installedCheck("01.02", "01.04").relation).toBe("older");
expect(installedCheck("01.04", "01.04").relation).toBe("same");
});
it("space: bad with the shortfall when it clearly doesn't fit; unknown without a reading", () => {
expect(spaceCheck(100, 40)).toEqual({ verdict: "bad", shortBy: 60 });
expect(spaceCheck(100, 400).verdict).toBe("ok");
expect(spaceCheck(100, null).verdict).toBe("unknown");
});
});
+51
View File
@@ -0,0 +1,51 @@
// Checks the package viewer shows against the selected console: can it run
// this package, is it already installed, and does it fit. Pure, so the panel
// stays a view and these stay tested.
export type Verdict = "ok" | "warn" | "bad" | "unknown";
/** "01.002.000" / "1.2" → [1, 2, 0] for comparison. */
function parts(v: string): number[] {
return v.split(".").map((p) => parseInt(p, 10) || 0);
}
/** Numeric, part-by-part; missing parts count as 0 ("01.002" = "01.002.000"). */
export function versionCompare(a: string, b: string): number {
const x = parts(a);
const y = parts(b);
for (let i = 0; i < Math.max(x.length, y.length); i++) {
const d = (x[i] ?? 0) - (y[i] ?? 0);
if (d !== 0) return d;
}
return 0;
}
/** Whether the console's firmware meets the package's minimum. */
export function firmwareCheck(minFw: string | null, consoleFw: string | null) {
const verdict: Verdict =
!minFw || !consoleFw
? "unknown"
: versionCompare(consoleFw, minFw) >= 0
? "ok"
: "bad";
return { verdict, minFw, consoleFw };
}
/** This package against the version installed on the console. */
export function installedCheck(pkgVer: string, installedVer: string | null) {
if (!installedVer) {
return { verdict: "ok" as Verdict, relation: "not-installed" as const };
}
const c = versionCompare(pkgVer, installedVer);
if (c > 0) return { verdict: "ok" as Verdict, relation: "newer" as const };
if (c < 0) return { verdict: "warn" as Verdict, relation: "older" as const };
return { verdict: "warn" as Verdict, relation: "same" as const };
}
/** Whether `size` fits in `freeBytes`; unknown when free space can't be read. */
export function spaceCheck(size: number, freeBytes: number | null) {
if (freeBytes == null) return { verdict: "unknown" as Verdict, shortBy: 0 };
return size <= freeBytes
? { verdict: "ok" as Verdict, shortBy: 0 }
: { verdict: "bad" as Verdict, shortBy: size - freeBytes };
}