Resolve connected library branch fallbacks
Some checks failed
Codex Template Compliance / template-compliance (push) Successful in 7s
Build / build-windows (push) Has been cancelled

This commit is contained in:
MrSphay
2026-05-15 22:51:53 +02:00
parent cbaaa09998
commit f3685cace3
2 changed files with 68 additions and 24 deletions

View File

@@ -9,3 +9,4 @@ All notable Modrinth Plus changes are documented here.
- Added Windows installer publishing to the Gitea generic package registry. - Added Windows installer publishing to the Gitea generic package registry.
- Added Codex repository context and release/security documentation. - Added Codex repository context and release/security documentation.
- Fixed startup compatibility with existing official Modrinth App databases that recorded different checksums for historical SQLite migrations. - Fixed startup compatibility with existing official Modrinth App databases that recorded different checksums for historical SQLite migrations.
- Fixed Connected Library Git repository URL resolution for repositories that use `master` instead of `main`.

View File

@@ -157,9 +157,7 @@ pub async fn list() -> crate::Result<Vec<ConnectedPack>> {
pub async fn connect(source_url: String) -> crate::Result<ConnectedPack> { pub async fn connect(source_url: String) -> crate::Result<ConnectedPack> {
let state = State::get().await?; let state = State::get().await?;
let manifest_url = normalize_manifest_url(&source_url)?; let (manifest_url, manifest) = resolve_manifest(&source_url).await?;
let manifest = fetch_manifest(&manifest_url).await?;
validate_manifest(&manifest)?;
upsert_manifest(&state.pool, None, &source_url, &manifest_url, &manifest) upsert_manifest(&state.pool, None, &source_url, &manifest_url, &manifest)
.await?; .await?;
get_by_source(&state.pool, &source_url).await get_by_source(&state.pool, &source_url).await
@@ -197,14 +195,17 @@ pub async fn set_auto_update(
pub async fn check(id: String) -> crate::Result<ConnectedCheckResult> { pub async fn check(id: String) -> crate::Result<ConnectedCheckResult> {
let state = State::get().await?; let state = State::get().await?;
let pack = get_by_id(&state.pool, &id).await?; let pack = get_by_id(&state.pool, &id).await?;
let manifest = fetch_manifest(&pack.manifest_url).await?; let (manifest_url, manifest) = match fetch_manifest(&pack.manifest_url).await
validate_manifest(&manifest)?; {
Ok(manifest) => (pack.manifest_url.clone(), manifest),
Err(_) => resolve_manifest(&pack.source_url).await?,
};
upsert_manifest( upsert_manifest(
&state.pool, &state.pool,
Some(&pack.id), Some(&pack.id),
&pack.source_url, &pack.source_url,
&pack.manifest_url, &manifest_url,
&manifest, &manifest,
) )
.await?; .await?;
@@ -306,7 +307,7 @@ pub async fn install(id: String) -> crate::Result<ConnectedPack> {
get_by_id(&state.pool, &id).await get_by_id(&state.pool, &id).await
} }
fn normalize_manifest_url(source_url: &str) -> crate::Result<String> { fn manifest_url_candidates(source_url: &str) -> crate::Result<Vec<String>> {
let source = source_url.trim(); let source = source_url.trim();
let parsed = Url::parse(source)?; let parsed = Url::parse(source)?;
@@ -318,7 +319,7 @@ fn normalize_manifest_url(source_url: &str) -> crate::Result<String> {
} }
if source.ends_with(MANIFEST_FILE_NAME) || source.contains("/raw/") { if source.ends_with(MANIFEST_FILE_NAME) || source.contains("/raw/") {
return Ok(source.to_string()); return Ok(vec![source.to_string()]);
} }
let host = parsed.host_str().unwrap_or_default(); let host = parsed.host_str().unwrap_or_default();
@@ -329,30 +330,52 @@ fn normalize_manifest_url(source_url: &str) -> crate::Result<String> {
if host == "github.com" && segments.len() >= 2 { if host == "github.com" && segments.len() >= 2 {
let repo = segments[1].trim_end_matches(".git"); let repo = segments[1].trim_end_matches(".git");
return Ok(format!( return Ok(vec![
"https://raw.githubusercontent.com/{}/{}/main/{}", format!(
segments[0], repo, MANIFEST_FILE_NAME "https://raw.githubusercontent.com/{}/{}/main/{}",
)); segments[0], repo, MANIFEST_FILE_NAME
),
format!(
"https://raw.githubusercontent.com/{}/{}/master/{}",
segments[0], repo, MANIFEST_FILE_NAME
),
]);
} }
if host == "gitlab.com" && segments.len() >= 2 { if host == "gitlab.com" && segments.len() >= 2 {
let repo = segments[1].trim_end_matches(".git"); let repo = segments[1].trim_end_matches(".git");
return Ok(format!( return Ok(vec![
"https://gitlab.com/{}/{}/-/raw/main/{}", format!(
segments[0], repo, MANIFEST_FILE_NAME "https://gitlab.com/{}/{}/-/raw/main/{}",
)); segments[0], repo, MANIFEST_FILE_NAME
),
format!(
"https://gitlab.com/{}/{}/-/raw/master/{}",
segments[0], repo, MANIFEST_FILE_NAME
),
]);
} }
if segments.len() >= 2 { if segments.len() >= 2 {
let repo = segments[1].trim_end_matches(".git"); let repo = segments[1].trim_end_matches(".git");
return Ok(format!( return Ok(vec![
"{}://{}/{}/{}/raw/branch/main/{}", format!(
parsed.scheme(), "{}://{}/{}/{}/raw/branch/main/{}",
host, parsed.scheme(),
segments[0], host,
repo, segments[0],
MANIFEST_FILE_NAME repo,
)); MANIFEST_FILE_NAME
),
format!(
"{}://{}/{}/{}/raw/branch/master/{}",
parsed.scheme(),
host,
segments[0],
repo,
MANIFEST_FILE_NAME
),
]);
} }
Err(ErrorKind::InputError( Err(ErrorKind::InputError(
@@ -362,6 +385,26 @@ fn normalize_manifest_url(source_url: &str) -> crate::Result<String> {
.into()) .into())
} }
async fn resolve_manifest(
source_url: &str,
) -> crate::Result<(String, ConnectedManifest)> {
let candidates = manifest_url_candidates(source_url)?;
let mut errors = Vec::new();
for manifest_url in candidates {
match fetch_manifest(&manifest_url).await {
Ok(manifest) => return Ok((manifest_url, manifest)),
Err(err) => errors.push(format!("{manifest_url}: {err}")),
}
}
Err(ErrorKind::InputError(format!(
"Could not fetch {MANIFEST_FILE_NAME}. Tried: {}",
errors.join("; ")
))
.into())
}
async fn fetch_manifest(url: &str) -> crate::Result<ConnectedManifest> { async fn fetch_manifest(url: &str) -> crate::Result<ConnectedManifest> {
let manifest = reqwest::get(url) let manifest = reqwest::get(url)
.await? .await?