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
This commit is contained in:
aecsocket
2026-07-10 11:17:41 +00:00
committed by GitHub
parent 323d088ebd
commit 57a977f7f3
10 changed files with 129 additions and 46 deletions
@@ -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[]
}
}
}
@@ -20,10 +20,13 @@ export class LauncherMetaManifestV0Module extends AbstractModule {
*
* @param loader - Loader platform (fabric, forge, quilt, neo)
*/
public async getManifest(loader: string): Promise<LauncherMeta.Manifest.v0.Manifest> {
public async getManifest(
loader: string,
formatVersion = 0,
): Promise<LauncherMeta.Manifest.v0.Manifest> {
return this.client.request<LauncherMeta.Manifest.v0.Manifest>('/manifest.json', {
api: LAUNCHER_META_BASE_URL,
version: `${loader}/v0`,
version: `${loader}/v${formatVersion}`,
method: 'GET',
skipAuth: true,
headers: { 'Content-Type': '' },
+3 -1
View File
@@ -22,8 +22,10 @@ pub async fn get_minecraft_versions() -> crate::Result<VersionManifest> {
// #[tracing::instrument]
pub async fn get_loader_versions(loader: &str) -> crate::Result<Manifest> {
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,
+33 -14
View File
@@ -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.
+16 -9
View File
@@ -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::<Vec<_>>();
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()
}
@@ -370,11 +370,13 @@ const gameVersionOptions = computed<ComboboxOption<string>[]>(() => {
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,
@@ -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<LauncherMeta.Manifest.v0.Manifest>
export type LoaderManifest = LauncherMeta.Manifest.v0.Manifest
export type LoaderManifestResolver = (loader: string) => Promise<LoaderManifest>
export interface LoaderVersionEntry {
id: string
stable: boolean
@@ -160,7 +161,7 @@ export interface CreationFlowContextValue {
hideLoaderChips: ComputedRef<boolean>
hideLoaderVersion: ComputedRef<boolean>
showSnapshots: Ref<boolean>
loaderVersionsCache: Ref<Record<string, { id: string; loaders: LoaderVersionEntry[] }[]>>
loaderVersionsCache: Ref<Record<string, LoaderManifest>>
paperSupportedVersions: Ref<Set<string> | null>
purpurSupportedVersions: Ref<Set<string> | null>
@@ -295,9 +296,7 @@ export function createCreationFlowContext(
const loaderVersionType = ref<LoaderVersionType>('stable')
const selectedLoaderVersion = ref<string | null>(null)
const showSnapshots = ref(false)
const loaderVersionsCache = ref<Record<string, { id: string; loaders: LoaderVersionEntry[] }[]>>(
{},
)
const loaderVersionsCache = ref<Record<string, LoaderManifest>>({})
const paperSupportedVersions = ref<Set<string> | null>(null)
const purpurSupportedVersions = ref<Set<string> | 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: [] }
}
}
@@ -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')
},