From 57a977f7f3e00d40538c72cae2e9d71f04c292b6 Mon Sep 17 00:00:00 2001 From: aecsocket <43144841+aecsocket@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:17:41 +0100 Subject: [PATCH] feat: use v1 version manifest for Quilt (#6650) * wip: app-side logic * lints * fmt * fmt * Split Daedalus changes out of frontend branch * Remove Daedalus changes from frontend branch --- .../InstallationSettings.vue | 23 +++++++-- apps/app-frontend/src/helpers/types.d.ts | 7 +++ .../src/modules/launcher-meta/types.ts | 9 ++++ .../src/modules/launcher-meta/v0.ts | 7 ++- packages/app-lib/src/api/metadata.rs | 4 +- packages/app-lib/src/launcher/mod.rs | 47 +++++++++++++------ packages/app-lib/src/state/cache.rs | 25 ++++++---- .../components/CustomSetupStage.vue | 29 +++++++++--- .../creation-flow-context.ts | 13 +++-- .../server-settings/pages/installation.vue | 11 ++++- 10 files changed, 129 insertions(+), 46 deletions(-) diff --git a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue index 6d908eab2b..fa1a4d0312 100644 --- a/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue +++ b/apps/app-frontend/src/components/ui/instance_settings/InstallationSettings.vue @@ -269,16 +269,31 @@ provideInstallationSettings({ debug('resolveLoaderVersions: no manifest', { loader, gameVersion }) return [] } - if (loader === 'fabric' || loader === 'quilt') { - const result = manifest.gameVersions[0]?.loaders ?? [] - debug('resolveLoaderVersions: fabric/quilt result', { + const entry = manifest.gameVersions?.find((item) => item.id === gameVersion) + if (entry?.versionGroup) { + const result = + manifest.versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? [] + debug('resolveLoaderVersions: version group result', { + loader, + gameVersion, + versionGroup: entry.versionGroup, + count: result.length, + }) + return result + } + const placeholder = manifest.gameVersions?.find((item) => item.id === '${modrinth.gameVersion}') + if (placeholder) { + const result = manifest.gameVersions?.some((item) => item.id === gameVersion) + ? placeholder.loaders + : [] + debug('resolveLoaderVersions: placeholder result', { loader, gameVersion, count: result.length, }) return result } - const result = manifest.gameVersions?.find((item) => item.id === gameVersion)?.loaders ?? [] + const result = entry?.loaders ?? [] debug('resolveLoaderVersions: result', { loader, gameVersion, count: result.length }) return result }, diff --git a/apps/app-frontend/src/helpers/types.d.ts b/apps/app-frontend/src/helpers/types.d.ts index 0bd0101461..761226eb91 100644 --- a/apps/app-frontend/src/helpers/types.d.ts +++ b/apps/app-frontend/src/helpers/types.d.ts @@ -133,11 +133,18 @@ type Hooks = { type Manifest = { gameVersions: ManifestGameVersion[] + versionGroups?: ManifestVersionGroup[] } type ManifestGameVersion = { id: string stable: boolean + versionGroup?: string + loaders: ManifestLoaderVersion[] +} + +type ManifestVersionGroup = { + id: string loaders: ManifestLoaderVersion[] } diff --git a/packages/api-client/src/modules/launcher-meta/types.ts b/packages/api-client/src/modules/launcher-meta/types.ts index 309bf1eca0..20e0dcee76 100644 --- a/packages/api-client/src/modules/launcher-meta/types.ts +++ b/packages/api-client/src/modules/launcher-meta/types.ts @@ -3,16 +3,25 @@ export namespace LauncherMeta { export namespace v0 { export type LoaderVersion = { id: string + url: string stable: boolean } export type GameVersionEntry = { + id: string + stable: boolean + versionGroup?: string + loaders: LoaderVersion[] + } + + export type VersionGroup = { id: string loaders: LoaderVersion[] } export type Manifest = { gameVersions: GameVersionEntry[] + versionGroups?: VersionGroup[] } } } diff --git a/packages/api-client/src/modules/launcher-meta/v0.ts b/packages/api-client/src/modules/launcher-meta/v0.ts index f4d6ff0e3b..391d22f60f 100644 --- a/packages/api-client/src/modules/launcher-meta/v0.ts +++ b/packages/api-client/src/modules/launcher-meta/v0.ts @@ -20,10 +20,13 @@ export class LauncherMetaManifestV0Module extends AbstractModule { * * @param loader - Loader platform (fabric, forge, quilt, neo) */ - public async getManifest(loader: string): Promise { + public async getManifest( + loader: string, + formatVersion = 0, + ): Promise { return this.client.request('/manifest.json', { api: LAUNCHER_META_BASE_URL, - version: `${loader}/v0`, + version: `${loader}/v${formatVersion}`, method: 'GET', skipAuth: true, headers: { 'Content-Type': '' }, diff --git a/packages/app-lib/src/api/metadata.rs b/packages/app-lib/src/api/metadata.rs index 59b3b89c56..befad43b9f 100644 --- a/packages/app-lib/src/api/metadata.rs +++ b/packages/app-lib/src/api/metadata.rs @@ -22,8 +22,10 @@ pub async fn get_minecraft_versions() -> crate::Result { // #[tracing::instrument] pub async fn get_loader_versions(loader: &str) -> crate::Result { let state = State::get().await?; + let cache_key = + daedalus::modded::loader_manifest_metadata(loader).cache_key; let loaders = CachedEntry::get_loader_manifest( - loader, + &cache_key, None, &state.pool, &state.api_semaphore, diff --git a/packages/app-lib/src/launcher/mod.rs b/packages/app-lib/src/launcher/mod.rs index a3d4a0a8b1..ea50297f9c 100644 --- a/packages/app-lib/src/launcher/mod.rs +++ b/packages/app-lib/src/launcher/mod.rs @@ -24,7 +24,7 @@ use crate::{State, get_resource_file, process}; use chrono::Utc; use daedalus as d; use daedalus::minecraft::{LoggingSide, RuleAction, VersionInfo}; -use daedalus::modded::LoaderVersion; +use daedalus::modded::{LoaderVersion, Manifest}; use regex::Regex; use serde::Deserialize; use std::fmt::Write; @@ -175,19 +175,18 @@ pub async fn get_loader_version_from_profile( let versions = crate::api::metadata::get_loader_versions(loader.as_meta_str()).await?; - let loaders = versions.game_versions.into_iter().find(|x| { - x.id.replace(daedalus::modded::DUMMY_REPLACE_STRING, game_version) - == game_version - }); - - if let Some(loaders) = loaders { - let loader_version = loaders.loaders.iter().find(|x| filter(x)).or( - if version == "stable" { - loaders.loaders.first() - } else { - None - }, - ); + if let Some(loaders) = + loader_versions_for_game_version(&versions, game_version) + { + let loader_version = + loaders + .iter() + .find(|x| filter(x)) + .or(if version == "stable" { + loaders.first() + } else { + None + }); Ok(loader_version.cloned()) } else { @@ -195,6 +194,26 @@ pub async fn get_loader_version_from_profile( } } +fn loader_versions_for_game_version<'a>( + manifest: &'a Manifest, + game_version: &str, +) -> Option<&'a [LoaderVersion]> { + let version = manifest.game_versions.iter().find(|x| { + x.id.replace(daedalus::modded::DUMMY_REPLACE_STRING, game_version) + == game_version + })?; + + if let Some(version_group) = &version.version_group { + manifest + .version_groups + .iter() + .find(|group| group.id == *version_group) + .map(|group| group.loaders.as_slice()) + } else { + Some(version.loaders.as_slice()) + } +} + /// Resolves the Minecraft version manifest and finds the index for the given /// game version. If the version isn't found in the cache, forces a manifest /// refresh to pick up newly-released versions. diff --git a/packages/app-lib/src/state/cache.rs b/packages/app-lib/src/state/cache.rs index e04104cbae..a65c2844fa 100644 --- a/packages/app-lib/src/state/cache.rs +++ b/packages/app-lib/src/state/cache.rs @@ -1396,19 +1396,25 @@ impl CachedEntry { let fetch_urls = keys .iter() .map(|x| { + let metadata = + daedalus::modded::loader_manifest_metadata_from_cache_key( + &x.key().to_string(), + ); + ( - x.key().to_string(), + metadata.cache_key, + metadata.loader, format!( - "{}{}/v0/manifest.json", + "{}{}", env!("MODRINTH_LAUNCHER_META_URL"), - x.key() + metadata.path, ), ) }) .collect::>(); futures::future::try_join_all(fetch_urls.iter().map( - |(_, url)| { + |(_, _, url)| { fetch_json( Method::GET, url, @@ -1424,14 +1430,15 @@ impl CachedEntry { .into_iter() .enumerate() .map(|(index, metadata)| { - ( + let mut entry = CacheValue::LoaderManifest(CachedLoaderManifest { - loader: fetch_urls[index].0.to_string(), + loader: fetch_urls[index].1.to_string(), manifest: metadata, }) - .get_entry(), - true, - ) + .get_entry(); + entry.id.clone_from(&fetch_urls[index].0); + + (entry, true) }) .collect() } diff --git a/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue b/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue index fa5125ef99..2d20d1a1a0 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue +++ b/packages/ui/src/components/flows/creation-flow-modal/components/CustomSetupStage.vue @@ -370,11 +370,13 @@ const gameVersionOptions = computed[]>(() => { const manifest = ctx.loaderVersionsCache.value[apiLoader] if (!manifest) return [] - const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}') + const hasPlaceholder = manifest.gameVersions.some((x) => x.id === '${modrinth.gameVersion}') const supportedVersions = new Set( - manifest + manifest.gameVersions .filter( - (x) => x.id !== '${modrinth.gameVersion}' && (hasPlaceholder || x.loaders.length > 0), + (x) => + x.id !== '${modrinth.gameVersion}' && + (hasPlaceholder || x.loaders.length > 0 || !!x.versionGroup), ) .map((x) => x.id), ) @@ -466,14 +468,14 @@ function getLoaderVersionsForGameVersion( apiLoader, gameVersion, hasManifest: !!manifest, - manifestLength: manifest?.length, + manifestLength: manifest?.gameVersions.length, }) if (!manifest) return [] // Some loaders (e.g. Fabric) list all versions under a placeholder entry - const placeholder = manifest.find((x) => x.id === '${modrinth.gameVersion}') + const placeholder = manifest.gameVersions.find((x) => x.id === '${modrinth.gameVersion}') if (placeholder) { - if (!manifest.some((x) => x.id === gameVersion)) return [] + if (!manifest.gameVersions.some((x) => x.id === gameVersion)) return [] debug( 'getLoaderVersionsForGameVersion: using placeholder, loaders:', placeholder.loaders.length, @@ -481,7 +483,20 @@ function getLoaderVersionsForGameVersion( return placeholder.loaders } - const entry = manifest.find((x) => x.id === gameVersion) + const entry = manifest.gameVersions.find((x) => x.id === gameVersion) + if (entry?.versionGroup) { + const loaders = + manifest.versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? [] + debug( + 'getLoaderVersionsForGameVersion: version group for', + gameVersion, + ':', + entry.versionGroup, + loaders.length + ' loaders', + ) + return loaders + } + debug( 'getLoaderVersionsForGameVersion: entry for', gameVersion, diff --git a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts index 9582c4df8a..e63935c56a 100644 --- a/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts +++ b/packages/ui/src/components/flows/creation-flow-modal/creation-flow-context.ts @@ -24,7 +24,8 @@ export type Gamemode = 'survival' | 'creative' | 'hardcore' export type Difficulty = 'peaceful' | 'easy' | 'normal' | 'hard' export type LoaderVersionType = 'stable' | 'latest' | 'other' export type GeneratorSettingsMode = 'default' | 'flat' | 'custom' -export type LoaderManifestResolver = (loader: string) => Promise +export type LoaderManifest = LauncherMeta.Manifest.v0.Manifest +export type LoaderManifestResolver = (loader: string) => Promise export interface LoaderVersionEntry { id: string stable: boolean @@ -160,7 +161,7 @@ export interface CreationFlowContextValue { hideLoaderChips: ComputedRef hideLoaderVersion: ComputedRef showSnapshots: Ref - loaderVersionsCache: Ref> + loaderVersionsCache: Ref> paperSupportedVersions: Ref | null> purpurSupportedVersions: Ref | null> @@ -295,9 +296,7 @@ export function createCreationFlowContext( const loaderVersionType = ref('stable') const selectedLoaderVersion = ref(null) const showSnapshots = ref(false) - const loaderVersionsCache = ref>( - {}, - ) + const loaderVersionsCache = ref>({}) const paperSupportedVersions = ref | null>(null) const purpurSupportedVersions = ref | null>(null) @@ -364,11 +363,11 @@ export function createCreationFlowContext( (await client.launchermeta.manifest_v0.getManifest(apiLoader)), staleTime: Infinity, }) - loaderVersionsCache.value[apiLoader] = data.gameVersions + loaderVersionsCache.value[apiLoader] = data debug('fetchLoaderManifest: loaded', apiLoader, 'gameVersions:', data.gameVersions.length) } catch (error) { debug('fetchLoaderManifest: failed', apiLoader, error) - loaderVersionsCache.value[apiLoader] = [] + loaderVersionsCache.value[apiLoader] = { gameVersions: [] } } } diff --git a/packages/ui/src/layouts/shared/server-settings/pages/installation.vue b/packages/ui/src/layouts/shared/server-settings/pages/installation.vue index 6a3609984b..3d943bd823 100644 --- a/packages/ui/src/layouts/shared/server-settings/pages/installation.vue +++ b/packages/ui/src/layouts/shared/server-settings/pages/installation.vue @@ -376,12 +376,17 @@ function getLoaderVersionsForGameVersion( } const manifest = manifestQuery.data.value?.gameVersions + const versionGroups = manifestQuery.data.value?.versionGroups if (!manifest) return [] const placeholder = manifest.find((x) => x.id === '${modrinth.gameVersion}') if (placeholder) return placeholder.loaders const entry = manifest.find((x) => x.id === gameVersion) + if (entry?.versionGroup) { + return versionGroups?.find((group) => group.id === entry.versionGroup)?.loaders ?? [] + } + return entry?.loaders ?? [] } @@ -505,7 +510,7 @@ provideInstallationSettings({ const hasPlaceholder = manifest.some((x) => x.id === '${modrinth.gameVersion}') if (!hasPlaceholder) { const supportedVersions = new Set( - manifest.filter((x) => x.loaders.length > 0).map((x) => x.id), + manifest.filter((x) => x.loaders.length > 0 || !!x.versionGroup).map((x) => x.id), ) return versions .filter((v) => supportedVersions.has(v.version)) @@ -547,7 +552,9 @@ provideInstallationSettings({ if (hasPlaceholder) { return tags.gameVersions.value.some((v) => v.version_type !== 'release') } - const supportedVersions = new Set(manifest.filter((x) => x.loaders.length > 0).map((x) => x.id)) + const supportedVersions = new Set( + manifest.filter((x) => x.loaders.length > 0 || !!x.versionGroup).map((x) => x.id), + ) const supported = tags.gameVersions.value.filter((v) => supportedVersions.has(v.version)) return supported.some((v) => v.version_type !== 'release') },