diff --git a/CHANGELOG.md b/CHANGELOG.md index c0f382aac..2644ad3f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,3 +9,4 @@ All notable Modrinth Plus changes are documented here. - Added Windows installer publishing to the Gitea generic package registry. - 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 Connected Library Git repository URL resolution for repositories that use `master` instead of `main`. diff --git a/packages/app-lib/src/api/connected_library.rs b/packages/app-lib/src/api/connected_library.rs index fa30eaef1..11656c02a 100644 --- a/packages/app-lib/src/api/connected_library.rs +++ b/packages/app-lib/src/api/connected_library.rs @@ -157,9 +157,7 @@ pub async fn list() -> crate::Result> { pub async fn connect(source_url: String) -> crate::Result { let state = State::get().await?; - let manifest_url = normalize_manifest_url(&source_url)?; - let manifest = fetch_manifest(&manifest_url).await?; - validate_manifest(&manifest)?; + let (manifest_url, manifest) = resolve_manifest(&source_url).await?; upsert_manifest(&state.pool, None, &source_url, &manifest_url, &manifest) .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 { let state = State::get().await?; let pack = get_by_id(&state.pool, &id).await?; - let manifest = fetch_manifest(&pack.manifest_url).await?; - validate_manifest(&manifest)?; + let (manifest_url, manifest) = match fetch_manifest(&pack.manifest_url).await + { + Ok(manifest) => (pack.manifest_url.clone(), manifest), + Err(_) => resolve_manifest(&pack.source_url).await?, + }; upsert_manifest( &state.pool, Some(&pack.id), &pack.source_url, - &pack.manifest_url, + &manifest_url, &manifest, ) .await?; @@ -306,7 +307,7 @@ pub async fn install(id: String) -> crate::Result { get_by_id(&state.pool, &id).await } -fn normalize_manifest_url(source_url: &str) -> crate::Result { +fn manifest_url_candidates(source_url: &str) -> crate::Result> { let source = source_url.trim(); let parsed = Url::parse(source)?; @@ -318,7 +319,7 @@ fn normalize_manifest_url(source_url: &str) -> crate::Result { } 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(); @@ -329,30 +330,52 @@ fn normalize_manifest_url(source_url: &str) -> crate::Result { if host == "github.com" && segments.len() >= 2 { let repo = segments[1].trim_end_matches(".git"); - return Ok(format!( - "https://raw.githubusercontent.com/{}/{}/main/{}", - segments[0], repo, MANIFEST_FILE_NAME - )); + return Ok(vec![ + format!( + "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 { let repo = segments[1].trim_end_matches(".git"); - return Ok(format!( - "https://gitlab.com/{}/{}/-/raw/main/{}", - segments[0], repo, MANIFEST_FILE_NAME - )); + return Ok(vec![ + format!( + "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 { let repo = segments[1].trim_end_matches(".git"); - return Ok(format!( - "{}://{}/{}/{}/raw/branch/main/{}", - parsed.scheme(), - host, - segments[0], - repo, - MANIFEST_FILE_NAME - )); + return Ok(vec![ + format!( + "{}://{}/{}/{}/raw/branch/main/{}", + parsed.scheme(), + host, + segments[0], + repo, + MANIFEST_FILE_NAME + ), + format!( + "{}://{}/{}/{}/raw/branch/master/{}", + parsed.scheme(), + host, + segments[0], + repo, + MANIFEST_FILE_NAME + ), + ]); } Err(ErrorKind::InputError( @@ -362,6 +385,26 @@ fn normalize_manifest_url(source_url: &str) -> crate::Result { .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 { let manifest = reqwest::get(url) .await?