Allow beta-typed Modrinth builds for EasyAuth (easyauth:beta)

EasyAuth publishes its 26.x builds as beta on Modrinth, and the itzg
resolver only accepts release-typed versions by default, so the mc
container failed to start. Pin easyauth:beta in the default mod list and
teach the panel's mod manager to compare entries by base slug so version
qualifiers don't break protected/duplicate checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Fable 5 committed 2026-07-10 12:26:33 -05:00
1 parent 279d137ce5
commit 795c2af50b
6 files changed
+32 -11

No files matched your search

+8 -4
View File
@@ -31,6 +31,10 @@ type SearchHit = {
const PROTECTED = new Set(["fabric-api", "easyauth"]);
// Managed entries may carry a version qualifier ("easyauth:beta") — compare
// by the base slug.
const baseSlug = (entry: string) => entry.split(":")[0].toLowerCase();
export default function ModsPage() {
const { data, refresh } = useApi<ModsData>("/api/mods", 0);
const [query, setQuery] = useState("");
@@ -82,7 +86,7 @@ export default function ModsPage() {
}
};
const managedSet = new Set((data?.managed ?? []).map((s) => s.toLowerCase()));
const managedSet = new Set((data?.managed ?? []).map(baseSlug));
return (
<div className="space-y-4">
@@ -121,7 +125,7 @@ export default function ModsPage() {
)}
{hits?.length === 0 && <p className="py-6 text-center text-xs text-muted">No compatible mods found.</p>}
{hits?.map((h) => {
const already = managedSet.has(h.slug.toLowerCase());
const already = managedSet.has(baseSlug(h.slug));
return (
<div key={h.slug} className="flex items-center gap-3 rounded-lg border border-border bg-surface2 p-2.5">
{h.icon_url ? (
@@ -166,13 +170,13 @@ export default function ModsPage() {
{(data?.managed ?? []).map((slug) => (
<li key={slug} className="flex items-center gap-2 rounded-lg border border-border bg-surface2 px-3 py-2">
<span className="font-mono text-sm">{slug}</span>
{PROTECTED.has(slug.toLowerCase()) && (
{PROTECTED.has(baseSlug(slug)) && (
<Badge color="info">
<Lock size={9} /> required
</Badge>
)}
<span className="ml-auto">
{!PROTECTED.has(slug.toLowerCase()) && (
{!PROTECTED.has(baseSlug(slug)) && (
<Button
size="sm"
variant="danger"
+11 -3
View File
@@ -40,6 +40,10 @@ export async function searchMods(query: string): Promise<ModSearchResult[]> {
return data.hits;
}
/**
* Entries may carry a version qualifier after a colon (e.g. "easyauth:beta"
* allows beta-typed builds); comparisons always use the base slug.
*/
export function managedSlugs(): string[] {
return (readEnvFile()["MODRINTH_PROJECTS"] ?? "")
.split(",")
@@ -47,22 +51,26 @@ export function managedSlugs(): string[] {
.filter(Boolean);
}
export function baseSlug(entry: string): string {
return entry.split(":")[0].toLowerCase();
}
const PROTECTED_SLUGS = new Set(["fabric-api", "easyauth"]);
export function addManagedMod(slug: string): string[] {
if (!/^[a-z0-9][a-z0-9\-_]{0,63}$/i.test(slug)) throw new Error("Invalid Modrinth slug");
const slugs = managedSlugs();
if (slugs.some((s) => s.toLowerCase() === slug.toLowerCase())) return slugs;
if (slugs.some((s) => baseSlug(s) === slug.toLowerCase())) return slugs;
const next = [...slugs, slug.toLowerCase()];
writeEnvFile({ MODRINTH_PROJECTS: next.join(",") });
return next;
}
export function removeManagedMod(slug: string): string[] {
if (PROTECTED_SLUGS.has(slug.toLowerCase())) {
if (PROTECTED_SLUGS.has(baseSlug(slug))) {
throw new Error(`${slug} is required by the panel (auth/loader) and can't be removed here`);
}
const next = managedSlugs().filter((s) => s.toLowerCase() !== slug.toLowerCase());
const next = managedSlugs().filter((s) => baseSlug(s) !== baseSlug(slug));
writeEnvFile({ MODRINTH_PROJECTS: next.join(",") });
return next;
}