From 12144ab32089d444b0b93deb37b0db46b1142a32 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 18 Sep 2025 14:44:19 +0200 Subject: [PATCH 01/19] feat: Normalize VCS provider names for cleaner display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract meaningful provider names from hostnames by taking the part immediately before the last dot. This provides cleaner names like "github" instead of "github.com" and "azure" instead of "dev.azure.com". Examples: - github.com → github - gitlab.com → gitlab - dev.azure.com → azure - git.mycompany.com → mycompany 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 2ec789f0e4..66dd883f97 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -201,12 +201,22 @@ impl VcsUrl { } VcsUrl { - provider: host.to_lowercase(), + provider: extract_provider_name(&host.to_lowercase()), id: strip_git_suffix(path).to_lowercase(), } } } +fn extract_provider_name(host: &str) -> String { + // Take just the part immediately before the last dot + let parts: Vec<&str> = host.split('.').collect(); + if parts.len() >= 2 { + parts[parts.len() - 2].to_string() + } else { + host.to_string() + } +} + fn is_matching_url(a: &str, b: &str) -> bool { VcsUrl::parse(a) == VcsUrl::parse(b) } From 38b17d54dbe8be1771904f2ff15f0f7ee350292a Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 18 Sep 2025 14:48:31 +0200 Subject: [PATCH 02/19] fix: Use to_owned() instead of to_string() for str slices --- src/utils/vcs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 66dd883f97..d167057b7b 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -211,9 +211,9 @@ fn extract_provider_name(host: &str) -> String { // Take just the part immediately before the last dot let parts: Vec<&str> = host.split('.').collect(); if parts.len() >= 2 { - parts[parts.len() - 2].to_string() + parts[parts.len() - 2].to_owned() } else { - host.to_string() + host.to_owned() } } From c1833812bf0adc00064b9ae8188c10a73e9d60fa Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Thu, 18 Sep 2025 15:03:15 +0200 Subject: [PATCH 03/19] test: Update VCS provider test expectations for normalized names Update all test expectations in test_url_parsing to match the new normalized provider names. Also ensure all special case paths use the extract_provider_name function for consistency. --- src/utils/vcs.rs | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index d167057b7b..80e2958329 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -156,13 +156,13 @@ impl VcsUrl { let username = &caps[1]; if let Some(caps) = VS_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: host.to_lowercase(), + provider: extract_provider_name(&host.to_lowercase()), id: format!("{}/{}", username.to_lowercase(), &caps[1].to_lowercase()), }; } if let Some(caps) = VS_TRAILING_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: host.to_lowercase(), + provider: extract_provider_name(&host.to_lowercase()), id: caps[1].to_lowercase(), }; } @@ -172,13 +172,13 @@ impl VcsUrl { let hostname = &caps[1]; if let Some(caps) = AZUREDEV_VERSION_PATH_RE.captures(path) { return VcsUrl { - provider: hostname.into(), + provider: extract_provider_name(hostname), id: format!("{}/{}", &caps[1].to_lowercase(), &caps[2].to_lowercase()), }; } if let Some(caps) = VS_TRAILING_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: hostname.to_lowercase(), + provider: extract_provider_name(hostname), id: caps[1].to_lowercase(), }; } @@ -187,7 +187,7 @@ impl VcsUrl { if host == GCB_DOMAIN { if let Some(caps) = GCB_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: host.into(), + provider: extract_provider_name(host), id: format!("{}/{}", &caps[1], &caps[2]), }; } @@ -195,7 +195,7 @@ impl VcsUrl { if let Some(caps) = BITBUCKET_SERVER_PATH_RE.captures(path) { return VcsUrl { - provider: host.to_lowercase(), + provider: extract_provider_name(&host.to_lowercase()), id: format!("{}/{}", &caps[1].to_lowercase(), &caps[2].to_lowercase()), }; } @@ -803,28 +803,28 @@ mod tests { assert_eq!( VcsUrl::parse("http://github.com/mitsuhiko/flask"), VcsUrl { - provider: "github.com".into(), + provider: "github".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("git@github.com:mitsuhiko/flask.git"), VcsUrl { - provider: "github.com".into(), + provider: "github".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("http://bitbucket.org/mitsuhiko/flask"), VcsUrl { - provider: "bitbucket.org".into(), + provider: "bitbucket".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("git@bitbucket.org:mitsuhiko/flask.git"), VcsUrl { - provider: "bitbucket.org".into(), + provider: "bitbucket".into(), id: "mitsuhiko/flask".into(), } ); @@ -833,84 +833,84 @@ mod tests { "https://bitbucket.example.com/projects/laurynsentry/repos/helloworld/browse" ), VcsUrl { - provider: "bitbucket.example.com".into(), + provider: "example".into(), id: "laurynsentry/helloworld".into(), } ); assert_eq!( VcsUrl::parse("https://neilmanvar.visualstudio.com/_git/sentry-demo"), VcsUrl { - provider: "neilmanvar.visualstudio.com".into(), + provider: "visualstudio".into(), id: "neilmanvar/sentry-demo".into(), } ); assert_eq!( VcsUrl::parse("https://project@mydomain.visualstudio.com/project/repo/_git"), VcsUrl { - provider: "mydomain.visualstudio.com".into(), + provider: "visualstudio".into(), id: "project/repo".into(), } ); assert_eq!( VcsUrl::parse("git@ssh.dev.azure.com:v3/project/repo/repo"), VcsUrl { - provider: "dev.azure.com".into(), + provider: "azure".into(), id: "project/repo".into(), } ); assert_eq!( VcsUrl::parse("git@ssh.dev.azure.com:v3/company/Repo%20Online/Repo%20Online"), VcsUrl { - provider: "dev.azure.com".into(), + provider: "azure".into(), id: "company/repo%20online".into(), } ); assert_eq!( VcsUrl::parse("https://dev.azure.com/project/repo/_git/repo"), VcsUrl { - provider: "dev.azure.com".into(), + provider: "azure".into(), id: "project/repo".into(), } ); assert_eq!( VcsUrl::parse("https://dev.azure.com/company/Repo%20Online/_git/Repo%20Online"), VcsUrl { - provider: "dev.azure.com".into(), + provider: "azure".into(), id: "company/repo%20online".into(), } ); assert_eq!( VcsUrl::parse("https://github.myenterprise.com/mitsuhiko/flask.git"), VcsUrl { - provider: "github.myenterprise.com".into(), + provider: "myenterprise".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("https://gitlab.example.com/gitlab-org/gitlab-ce"), VcsUrl { - provider: "gitlab.example.com".into(), + provider: "example".into(), id: "gitlab-org/gitlab-ce".into(), } ); assert_eq!( VcsUrl::parse("git@gitlab.example.com:gitlab-org/gitlab-ce.git"), VcsUrl { - provider: "gitlab.example.com".into(), + provider: "example".into(), id: "gitlab-org/gitlab-ce".into(), } ); assert_eq!( VcsUrl::parse("https://gitlab.com/gitlab-org/gitlab-ce"), VcsUrl { - provider: "gitlab.com".into(), + provider: "gitlab".into(), id: "gitlab-org/gitlab-ce".into(), } ); assert_eq!( VcsUrl::parse("git@gitlab.com:gitlab-org/gitlab-ce.git"), VcsUrl { - provider: "gitlab.com".into(), + provider: "gitlab".into(), id: "gitlab-org/gitlab-ce".into(), } ); @@ -919,14 +919,14 @@ mod tests { "https://source.developers.google.com/p/project-slug/r/github_org-slug_repo-slug" ), VcsUrl { - provider: "source.developers.google.com".into(), + provider: "google".into(), id: "org-slug/repo-slug".into(), } ); assert_eq!( VcsUrl::parse("git@gitlab.com:gitlab-org/GitLab-CE.git"), VcsUrl { - provider: "gitlab.com".into(), + provider: "gitlab".into(), id: "gitlab-org/gitlab-ce".into(), } ); From c36a45f251e38a6d8acf2f56b19f27db1e64ea49 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 13:54:22 +0200 Subject: [PATCH 04/19] refactor: Only normalize provider names in get_provider_from_remote - Revert all VcsUrl parsing logic to preserve internal consistency - Only apply provider name normalization in get_provider_from_remote function - This ensures VCS URL parsing and tests remain unchanged while still providing normalized names (github, gitlab, etc.) for CLI usage - Add dedicated test for get_provider_from_remote normalization --- src/utils/vcs.rs | 63 ++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 80e2958329..c055ffceb6 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -156,13 +156,13 @@ impl VcsUrl { let username = &caps[1]; if let Some(caps) = VS_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: extract_provider_name(&host.to_lowercase()), + provider: host.to_lowercase(), id: format!("{}/{}", username.to_lowercase(), &caps[1].to_lowercase()), }; } if let Some(caps) = VS_TRAILING_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: extract_provider_name(&host.to_lowercase()), + provider: host.to_lowercase(), id: caps[1].to_lowercase(), }; } @@ -172,13 +172,13 @@ impl VcsUrl { let hostname = &caps[1]; if let Some(caps) = AZUREDEV_VERSION_PATH_RE.captures(path) { return VcsUrl { - provider: extract_provider_name(hostname), + provider: hostname.into(), id: format!("{}/{}", &caps[1].to_lowercase(), &caps[2].to_lowercase()), }; } if let Some(caps) = VS_TRAILING_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: extract_provider_name(hostname), + provider: hostname.to_lowercase(), id: caps[1].to_lowercase(), }; } @@ -187,7 +187,7 @@ impl VcsUrl { if host == GCB_DOMAIN { if let Some(caps) = GCB_GIT_PATH_RE.captures(path) { return VcsUrl { - provider: extract_provider_name(host), + provider: host.into(), id: format!("{}/{}", &caps[1], &caps[2]), }; } @@ -195,13 +195,13 @@ impl VcsUrl { if let Some(caps) = BITBUCKET_SERVER_PATH_RE.captures(path) { return VcsUrl { - provider: extract_provider_name(&host.to_lowercase()), + provider: host.to_lowercase(), id: format!("{}/{}", &caps[1].to_lowercase(), &caps[2].to_lowercase()), }; } VcsUrl { - provider: extract_provider_name(&host.to_lowercase()), + provider: host.to_lowercase(), id: strip_git_suffix(path).to_lowercase(), } } @@ -228,7 +228,7 @@ pub fn get_repo_from_remote(repo: &str) -> String { pub fn get_provider_from_remote(remote: &str) -> String { let obj = VcsUrl::parse(remote); - obj.provider + extract_provider_name(&obj.provider) } pub fn git_repo_remote_url( @@ -803,28 +803,28 @@ mod tests { assert_eq!( VcsUrl::parse("http://github.com/mitsuhiko/flask"), VcsUrl { - provider: "github".into(), + provider: "github.com".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("git@github.com:mitsuhiko/flask.git"), VcsUrl { - provider: "github".into(), + provider: "github.com".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("http://bitbucket.org/mitsuhiko/flask"), VcsUrl { - provider: "bitbucket".into(), + provider: "bitbucket.org".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("git@bitbucket.org:mitsuhiko/flask.git"), VcsUrl { - provider: "bitbucket".into(), + provider: "bitbucket.org".into(), id: "mitsuhiko/flask".into(), } ); @@ -833,84 +833,84 @@ mod tests { "https://bitbucket.example.com/projects/laurynsentry/repos/helloworld/browse" ), VcsUrl { - provider: "example".into(), + provider: "bitbucket.example.com".into(), id: "laurynsentry/helloworld".into(), } ); assert_eq!( VcsUrl::parse("https://neilmanvar.visualstudio.com/_git/sentry-demo"), VcsUrl { - provider: "visualstudio".into(), + provider: "neilmanvar.visualstudio.com".into(), id: "neilmanvar/sentry-demo".into(), } ); assert_eq!( VcsUrl::parse("https://project@mydomain.visualstudio.com/project/repo/_git"), VcsUrl { - provider: "visualstudio".into(), + provider: "mydomain.visualstudio.com".into(), id: "project/repo".into(), } ); assert_eq!( VcsUrl::parse("git@ssh.dev.azure.com:v3/project/repo/repo"), VcsUrl { - provider: "azure".into(), + provider: "dev.azure.com".into(), id: "project/repo".into(), } ); assert_eq!( VcsUrl::parse("git@ssh.dev.azure.com:v3/company/Repo%20Online/Repo%20Online"), VcsUrl { - provider: "azure".into(), + provider: "dev.azure.com".into(), id: "company/repo%20online".into(), } ); assert_eq!( VcsUrl::parse("https://dev.azure.com/project/repo/_git/repo"), VcsUrl { - provider: "azure".into(), + provider: "dev.azure.com".into(), id: "project/repo".into(), } ); assert_eq!( VcsUrl::parse("https://dev.azure.com/company/Repo%20Online/_git/Repo%20Online"), VcsUrl { - provider: "azure".into(), + provider: "dev.azure.com".into(), id: "company/repo%20online".into(), } ); assert_eq!( VcsUrl::parse("https://github.myenterprise.com/mitsuhiko/flask.git"), VcsUrl { - provider: "myenterprise".into(), + provider: "github.myenterprise.com".into(), id: "mitsuhiko/flask".into(), } ); assert_eq!( VcsUrl::parse("https://gitlab.example.com/gitlab-org/gitlab-ce"), VcsUrl { - provider: "example".into(), + provider: "gitlab.example.com".into(), id: "gitlab-org/gitlab-ce".into(), } ); assert_eq!( VcsUrl::parse("git@gitlab.example.com:gitlab-org/gitlab-ce.git"), VcsUrl { - provider: "example".into(), + provider: "gitlab.example.com".into(), id: "gitlab-org/gitlab-ce".into(), } ); assert_eq!( VcsUrl::parse("https://gitlab.com/gitlab-org/gitlab-ce"), VcsUrl { - provider: "gitlab".into(), + provider: "gitlab.com".into(), id: "gitlab-org/gitlab-ce".into(), } ); assert_eq!( VcsUrl::parse("git@gitlab.com:gitlab-org/gitlab-ce.git"), VcsUrl { - provider: "gitlab".into(), + provider: "gitlab.com".into(), id: "gitlab-org/gitlab-ce".into(), } ); @@ -919,19 +919,30 @@ mod tests { "https://source.developers.google.com/p/project-slug/r/github_org-slug_repo-slug" ), VcsUrl { - provider: "google".into(), + provider: "source.developers.google.com".into(), id: "org-slug/repo-slug".into(), } ); assert_eq!( VcsUrl::parse("git@gitlab.com:gitlab-org/GitLab-CE.git"), VcsUrl { - provider: "gitlab".into(), + provider: "gitlab.com".into(), id: "gitlab-org/gitlab-ce".into(), } ); } + #[test] + fn test_get_provider_from_remote() { + // Test that get_provider_from_remote normalizes provider names + assert_eq!(get_provider_from_remote("https://github.com/user/repo"), "github"); + assert_eq!(get_provider_from_remote("git@gitlab.com:user/repo.git"), "gitlab"); + assert_eq!(get_provider_from_remote("https://bitbucket.org/user/repo"), "bitbucket"); + assert_eq!(get_provider_from_remote("https://dev.azure.com/user/repo"), "azure"); + assert_eq!(get_provider_from_remote("https://github.mycompany.com/user/repo"), "mycompany"); + assert_eq!(get_provider_from_remote("https://source.developers.google.com/p/project/r/repo"), "google"); + } + #[test] fn test_url_normalization() { assert!(!is_matching_url( From ce1bf3c5c4ff8af2335a016cbb035167a96e8f62 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 13:55:57 +0200 Subject: [PATCH 05/19] style: Format code with cargo fmt --- src/utils/vcs.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index c055ffceb6..71fc8b73fd 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -935,12 +935,30 @@ mod tests { #[test] fn test_get_provider_from_remote() { // Test that get_provider_from_remote normalizes provider names - assert_eq!(get_provider_from_remote("https://github.com/user/repo"), "github"); - assert_eq!(get_provider_from_remote("git@gitlab.com:user/repo.git"), "gitlab"); - assert_eq!(get_provider_from_remote("https://bitbucket.org/user/repo"), "bitbucket"); - assert_eq!(get_provider_from_remote("https://dev.azure.com/user/repo"), "azure"); - assert_eq!(get_provider_from_remote("https://github.mycompany.com/user/repo"), "mycompany"); - assert_eq!(get_provider_from_remote("https://source.developers.google.com/p/project/r/repo"), "google"); + assert_eq!( + get_provider_from_remote("https://github.com/user/repo"), + "github" + ); + assert_eq!( + get_provider_from_remote("git@gitlab.com:user/repo.git"), + "gitlab" + ); + assert_eq!( + get_provider_from_remote("https://bitbucket.org/user/repo"), + "bitbucket" + ); + assert_eq!( + get_provider_from_remote("https://dev.azure.com/user/repo"), + "azure" + ); + assert_eq!( + get_provider_from_remote("https://github.mycompany.com/user/repo"), + "mycompany" + ); + assert_eq!( + get_provider_from_remote("https://source.developers.google.com/p/project/r/repo"), + "google" + ); } #[test] From cd97c5d64a6bc3497726a68e432c848a956ba632 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 18:15:58 +0200 Subject: [PATCH 06/19] feat: Fix hostname parsing and add comprehensive tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix extract_provider_name to handle URLs with trailing dots - Improve logic to correctly extract provider from domains like dev.azure.com - Add comprehensive tests for edge cases including trailing dots - Use simple string split approach instead of complex regex 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 71fc8b73fd..e52bb74cea 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -208,12 +208,14 @@ impl VcsUrl { } fn extract_provider_name(host: &str) -> String { - // Take just the part immediately before the last dot - let parts: Vec<&str> = host.split('.').collect(); + let trimmed_host = host.trim_end_matches('.'); + + // Split by dots and take the second-to-last part if there are at least 2 parts + let parts: Vec<&str> = trimmed_host.split('.').collect(); if parts.len() >= 2 { parts[parts.len() - 2].to_owned() } else { - host.to_owned() + trimmed_host.to_owned() } } @@ -932,6 +934,30 @@ mod tests { ); } + #[test] + fn test_extract_provider_name() { + // Test basic provider name extraction + assert_eq!(extract_provider_name("github.com"), "github"); + assert_eq!(extract_provider_name("gitlab.com"), "gitlab"); + assert_eq!(extract_provider_name("bitbucket.org"), "bitbucket"); + + // Test edge case with trailing dots + assert_eq!(extract_provider_name("github.com."), "github"); + assert_eq!(extract_provider_name("gitlab.com.."), "gitlab"); + + // Test subdomain cases - we want the part before TLD, not the subdomain + assert_eq!(extract_provider_name("api.github.com"), "github"); + assert_eq!(extract_provider_name("ssh.dev.azure.com"), "azure"); + assert_eq!(extract_provider_name("dev.azure.com"), "azure"); + + // Test single component (no dots) + assert_eq!(extract_provider_name("localhost"), "localhost"); + assert_eq!(extract_provider_name("myserver"), "myserver"); + + // Test empty string + assert_eq!(extract_provider_name(""), ""); + } + #[test] fn test_get_provider_from_remote() { // Test that get_provider_from_remote normalizes provider names @@ -959,6 +985,11 @@ mod tests { get_provider_from_remote("https://source.developers.google.com/p/project/r/repo"), "google" ); + // Test edge case with trailing dot in hostname + assert_eq!( + get_provider_from_remote("https://github.com./user/repo"), + "github" + ); } #[test] From ca149da683f799014b48896eb0034f5100fb908e Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 18:18:42 +0200 Subject: [PATCH 07/19] Simplify extract_provider_name with rsplit iterator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use rsplit iterator instead of Vec collection for cleaner code 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/vcs.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index e52bb74cea..f950bdc935 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -208,15 +208,10 @@ impl VcsUrl { } fn extract_provider_name(host: &str) -> String { - let trimmed_host = host.trim_end_matches('.'); - - // Split by dots and take the second-to-last part if there are at least 2 parts - let parts: Vec<&str> = trimmed_host.split('.').collect(); - if parts.len() >= 2 { - parts[parts.len() - 2].to_owned() - } else { - trimmed_host.to_owned() - } + let trimmed = host.trim_end_matches('.'); + let mut iter = trimmed.rsplit('.'); + iter.next(); // skip TLD + iter.next().unwrap_or(trimmed).to_owned() } fn is_matching_url(a: &str, b: &str) -> bool { From 5f3415696e1ae4ae2d3e0606826b4cff14193035 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 18:50:05 +0200 Subject: [PATCH 08/19] Update src/utils/vcs.rs Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> --- src/utils/vcs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index f950bdc935..75f4c6cd27 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -938,7 +938,6 @@ mod tests { // Test edge case with trailing dots assert_eq!(extract_provider_name("github.com."), "github"); - assert_eq!(extract_provider_name("gitlab.com.."), "gitlab"); // Test subdomain cases - we want the part before TLD, not the subdomain assert_eq!(extract_provider_name("api.github.com"), "github"); From 4737dbb03f0a88ccda0465d8011153bf5ac8544c Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 12:57:13 -0400 Subject: [PATCH 09/19] Update src/utils/vcs.rs Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> --- src/utils/vcs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 75f4c6cd27..06143cb182 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -207,7 +207,7 @@ impl VcsUrl { } } -fn extract_provider_name(host: &str) -> String { +fn extract_provider_name(host: &str) -> &str { let trimmed = host.trim_end_matches('.'); let mut iter = trimmed.rsplit('.'); iter.next(); // skip TLD From 702f95d3faef0e7c49acd15a9ea77333d543f6af Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 12:57:44 -0400 Subject: [PATCH 10/19] Update src/utils/vcs.rs Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> --- src/utils/vcs.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 06143cb182..9102076347 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -209,9 +209,7 @@ impl VcsUrl { fn extract_provider_name(host: &str) -> &str { let trimmed = host.trim_end_matches('.'); - let mut iter = trimmed.rsplit('.'); - iter.next(); // skip TLD - iter.next().unwrap_or(trimmed).to_owned() + trimmed.rsplit('.').nth(1).unwrap_or(trimmed) } fn is_matching_url(a: &str, b: &str) -> bool { From fa0522d45a91a61eeba26778357ed3f02198f136 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:36:51 +0200 Subject: [PATCH 11/19] ci(release): `npm install` in `test_node.yml` on release (#2768) `npm ci` will fail here, as the new versions of the optional dependencies are not published yet. Additionally, add a script to bump the optional dependencies in the package-lock.json after a release is created. Otherwise, `npm ci` will continue to fail after the release, until someone updates the package-lock.json manually. --- .craft.yml | 1 + .github/workflows/ci.yml | 2 ++ .github/workflows/test_node.yml | 33 +++++++++++++++++++++++++-------- scripts/post-release.sh | 22 ++++++++++++++++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 scripts/post-release.sh diff --git a/.craft.yml b/.craft.yml index 56e81e4ab2..0b9c80d94a 100644 --- a/.craft.yml +++ b/.craft.yml @@ -1,5 +1,6 @@ minVersion: 0.23.1 changelogPolicy: auto +postReleaseCommand: bash scripts/post-release.sh targets: - name: gcs bucket: sentry-sdk-assets diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09f95c9941..6b5e2bfaa7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,8 @@ jobs: test_node: name: Test Node uses: ./.github/workflows/test_node.yml + with: + triggered-by-release: ${{ github.event_name == 'push' && startsWith(github.ref_name, 'release/') }} test_swift: name: Test Swift diff --git a/.github/workflows/test_node.yml b/.github/workflows/test_node.yml index a7a42c1163..848bb43121 100644 --- a/.github/workflows/test_node.yml +++ b/.github/workflows/test_node.yml @@ -2,6 +2,11 @@ name: Test Node on: workflow_call: + inputs: + triggered-by-release: + type: boolean + description: Whether the workflow was triggered by a release + default: false outputs: matrix-result: description: 'Matrix job result' @@ -19,10 +24,16 @@ jobs: with: node-version-file: package.json - # We need to skip the fallback download because downloading will fail on release branches because the new version isn't available yet. - # We have to use npm here because yarn fails on the non-existing existing optionalDependency version: - # https://github.com/yarnpkg/berry/issues/2425#issuecomment-1627807326 - - run: SENTRYCLI_SKIP_DOWNLOAD=1 npm ci + - name: Install dependencies via npm ci + if: ${{ !inputs.triggered-by-release }} + run: npm ci + + # For pushes to the release branch, we need to install the dependencies via `npm install` + # because the `package-lock.json` is not updated with the new versions of the optional + # dependencies yet. We also must skip the fallback download via --ignore-scripts. + - name: Install dependencies via npm install (for pushes to release branch) + if: ${{ inputs.triggered-by-release }} + run: npm install --omit=optional --ignore-scripts - run: npm run check:types @@ -43,10 +54,16 @@ jobs: with: node-version: ${{ matrix.node-version }} - # We need to skip the fallback download because downloading will fail on release branches because the new version isn't available yet. - # We have to use npm here because yarn fails on the non-existing existing optionalDependency version: - # https://github.com/yarnpkg/berry/issues/2425#issuecomment-1627807326 - - run: SENTRYCLI_SKIP_DOWNLOAD=1 npm ci + - name: Install dependencies via npm ci + if: ${{ !inputs.triggered-by-release }} + run: npm ci + + # For pushes to the release branch, we need to install the dependencies via `npm install` + # because the `package-lock.json` is not updated with the new versions of the optional + # dependencies yet. We also must skip the fallback download via --ignore-scripts. + - name: Install dependencies via npm install (for pushes to release branch) + if: ${{ inputs.triggered-by-release }} + run: npm install --omit=optional --ignore-scripts # older node versions need an older nft - run: SENTRYCLI_SKIP_DOWNLOAD=1 npm install @vercel/nft@0.22.1 diff --git a/scripts/post-release.sh b/scripts/post-release.sh new file mode 100644 index 0000000000..f4249cc94c --- /dev/null +++ b/scripts/post-release.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# This script is run by Craft after a release is created. +# We currently use it to bump the platform-specific optional dependencies to their new versions +# in the package-lock.json, immediately after a release is created. This is needed for CI to +# pass after the release is created.c + +set -eux +OLD_VERSION="${1}" +NEW_VERSION="${2}" + +git checkout master + +# We need to update the package-lock.json to include the new version of the optional dependencies. +npm install --package-lock-only + +git add package-lock.json + +# Only commit if there are changes +git diff --staged --quiet || git commit -m "build(npm): 🤖 Bump optional dependencies to ${NEW_VERSION}" +git pull --rebase +git push From 4188d8c504970109ae5f92b33e1fb5e4c6a28910 Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Thu, 18 Sep 2025 14:58:11 +0000 Subject: [PATCH 12/19] release: 2.54.0 --- CHANGELOG.md | 31 +++++++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- npm-binary-distributions/darwin/package.json | 2 +- .../linux-arm/package.json | 2 +- .../linux-arm64/package.json | 2 +- .../linux-i686/package.json | 2 +- .../linux-x64/package.json | 2 +- .../win32-arm64/package.json | 2 +- .../win32-i686/package.json | 2 +- .../win32-x64/package.json | 2 +- package-lock.json | 2 +- package.json | 18 +++++------ 13 files changed, 51 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5716e6380..ca1a7fb527 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,37 @@ "You know what they say. Fool me once, strike one, but fool me twice... strike three." — Michael Scott +## 2.54.0 + +### Various fixes & improvements + +- ci(release): `npm install` in `test_node.yml` on release (#2768) by @szokeasaurusrex +- Fix: symlinks in normalized upload (#2744) by @noahsmartin +- build(deps): bump github/codeql-action from 3.30.1 to 3.30.3 (#2760) by @dependabot +- build(deps): bump actions/create-github-app-token from 2.1.1 to 2.1.4 (#2761) by @dependabot +- ci(rust): Auto-update rust-toolchain.toml (#2746) by @szokeasaurusrex +- ci(node): Test newer node versions (#2756) by @szokeasaurusrex +- build(node): Use `npm` in Volta (#2755) by @szokeasaurusrex +- ci(node): Resolve `browserslist` to `4.25.4` (#2754) by @szokeasaurusrex +- build: Pin Rust toolchain minor version (#2724) by @szokeasaurusrex +- ci(build): Cache dependencies on release build (#2745) by @szokeasaurusrex +- feat(vcs): Prefer upstream remote over origin for base repo name (#2737) by @runningcode +- ref(send-event): Introduce constant for max breadcrumbs (#2716) by @szokeasaurusrex +- docs: Add cargo fmt reminder to CLAUDE.md (#2738) by @runningcode +- feat(build): Add auto-detection of base_repo_name from git remote (#2735) by @runningcode +- ref(clippy): Enable `tests-outside-test-module` lint (#2736) by @szokeasaurusrex +- build(macos): Allow compiling without Xcode (#2733) by @szokeasaurusrex +- feat(build): Add auto-detection of PR number from GitHub Actions (#2722) by @runningcode +- feat(build): Auto-detect base_ref from git merge-base (#2720) by @runningcode +- ci(release): Use `container` in workflow instead of `build-in-docker.sh` (#2727) by @szokeasaurusrex +- build(deps): bump github/codeql-action from 3.29.11 to 3.30.1 (#2730) by @dependabot +- build(deps): bump actions/setup-node from 4.4.0 to 5.0.0 (#2731) by @dependabot +- meta(claude): Add `CLAUDE.md` (#2728) by @szokeasaurusrex +- build(deps): bump github/codeql-action from 3.29.8 to 3.29.11 (#2718) by @dependabot +- feat(logs): support log streaming (#2666) by @vgrozdanic + +_Plus 1 more_ + ## 2.53.0 ### Various fixes & improvements diff --git a/Cargo.lock b/Cargo.lock index 0de926a2eb..f215718d11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2724,7 +2724,7 @@ dependencies = [ [[package]] name = "sentry-cli" -version = "2.53.0" +version = "2.54.0" dependencies = [ "anyhow", "anylog", diff --git a/Cargo.toml b/Cargo.toml index e78eb975f7..2e9ebcf4f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ authors = ["Armin Ronacher "] build = "build.rs" name = "sentry-cli" -version = "2.53.0" +version = "2.54.0" edition = "2021" rust-version = "1.86" diff --git a/npm-binary-distributions/darwin/package.json b/npm-binary-distributions/darwin/package.json index 61304d2356..ab812d0cd5 100644 --- a/npm-binary-distributions/darwin/package.json +++ b/npm-binary-distributions/darwin/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-darwin", - "version": "2.53.0", + "version": "2.54.0", "description": "The darwin distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/linux-arm/package.json b/npm-binary-distributions/linux-arm/package.json index 2f42411b98..aceabe6c23 100644 --- a/npm-binary-distributions/linux-arm/package.json +++ b/npm-binary-distributions/linux-arm/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-linux-arm", - "version": "2.53.0", + "version": "2.54.0", "description": "The linux arm distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/linux-arm64/package.json b/npm-binary-distributions/linux-arm64/package.json index 1f6f8b3318..27b3365a55 100644 --- a/npm-binary-distributions/linux-arm64/package.json +++ b/npm-binary-distributions/linux-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-linux-arm64", - "version": "2.53.0", + "version": "2.54.0", "description": "The linux arm64 distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/linux-i686/package.json b/npm-binary-distributions/linux-i686/package.json index 2b2771ef55..76c510d2ba 100644 --- a/npm-binary-distributions/linux-i686/package.json +++ b/npm-binary-distributions/linux-i686/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-linux-i686", - "version": "2.53.0", + "version": "2.54.0", "description": "The linux x86 and ia32 distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/linux-x64/package.json b/npm-binary-distributions/linux-x64/package.json index d002208f39..7dbfd28856 100644 --- a/npm-binary-distributions/linux-x64/package.json +++ b/npm-binary-distributions/linux-x64/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-linux-x64", - "version": "2.53.0", + "version": "2.54.0", "description": "The linux x64 distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/win32-arm64/package.json b/npm-binary-distributions/win32-arm64/package.json index 0e47eeb9a6..e2292d4677 100644 --- a/npm-binary-distributions/win32-arm64/package.json +++ b/npm-binary-distributions/win32-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-win32-arm64", - "version": "2.53.0", + "version": "2.54.0", "description": "The windows arm64 distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/win32-i686/package.json b/npm-binary-distributions/win32-i686/package.json index b3b6998b12..40905190ad 100644 --- a/npm-binary-distributions/win32-i686/package.json +++ b/npm-binary-distributions/win32-i686/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-win32-i686", - "version": "2.53.0", + "version": "2.54.0", "description": "The windows x86 and ia32 distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/npm-binary-distributions/win32-x64/package.json b/npm-binary-distributions/win32-x64/package.json index 71a3c2d653..f773a4c6d1 100644 --- a/npm-binary-distributions/win32-x64/package.json +++ b/npm-binary-distributions/win32-x64/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli-win32-x64", - "version": "2.53.0", + "version": "2.54.0", "description": "The windows x64 distribution of the Sentry CLI binary.", "repository": "https://github.com/getsentry/sentry-cli", "license": "BSD-3-Clause", diff --git a/package-lock.json b/package-lock.json index 6131ae6dfa..d8a31d12f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli", - "version": "2.53.0", + "version": "2.54.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 328115642b..44732f13b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sentry/cli", - "version": "2.53.0", + "version": "2.54.0", "description": "A command line utility to work with Sentry. https://docs.sentry.io/hosted/learn/cli/", "repository": "git://github.com/getsentry/sentry-cli.git", "homepage": "https://docs.sentry.io/hosted/learn/cli/", @@ -32,14 +32,14 @@ "typescript": "~5.8.3" }, "optionalDependencies": { - "@sentry/cli-darwin": "2.53.0", - "@sentry/cli-linux-arm": "2.53.0", - "@sentry/cli-linux-arm64": "2.53.0", - "@sentry/cli-linux-i686": "2.53.0", - "@sentry/cli-linux-x64": "2.53.0", - "@sentry/cli-win32-i686": "2.53.0", - "@sentry/cli-win32-x64": "2.53.0", - "@sentry/cli-win32-arm64": "2.53.0" + "@sentry/cli-darwin": "2.54.0", + "@sentry/cli-linux-arm": "2.54.0", + "@sentry/cli-linux-arm64": "2.54.0", + "@sentry/cli-linux-i686": "2.54.0", + "@sentry/cli-linux-x64": "2.54.0", + "@sentry/cli-win32-i686": "2.54.0", + "@sentry/cli-win32-x64": "2.54.0", + "@sentry/cli-win32-arm64": "2.54.0" }, "scripts": { "postinstall": "node ./scripts/install.js", From 571ada9bcc48fef63a74d28852fecfdafc892405 Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Thu, 18 Sep 2025 17:07:54 +0200 Subject: [PATCH 13/19] meta: Update CHANGELOG.md --- CHANGELOG.md | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca1a7fb527..5bad9617b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,32 +6,12 @@ ### Various fixes & improvements -- ci(release): `npm install` in `test_node.yml` on release (#2768) by @szokeasaurusrex - Fix: symlinks in normalized upload (#2744) by @noahsmartin -- build(deps): bump github/codeql-action from 3.30.1 to 3.30.3 (#2760) by @dependabot -- build(deps): bump actions/create-github-app-token from 2.1.1 to 2.1.4 (#2761) by @dependabot -- ci(rust): Auto-update rust-toolchain.toml (#2746) by @szokeasaurusrex -- ci(node): Test newer node versions (#2756) by @szokeasaurusrex -- build(node): Use `npm` in Volta (#2755) by @szokeasaurusrex -- ci(node): Resolve `browserslist` to `4.25.4` (#2754) by @szokeasaurusrex -- build: Pin Rust toolchain minor version (#2724) by @szokeasaurusrex -- ci(build): Cache dependencies on release build (#2745) by @szokeasaurusrex - feat(vcs): Prefer upstream remote over origin for base repo name (#2737) by @runningcode -- ref(send-event): Introduce constant for max breadcrumbs (#2716) by @szokeasaurusrex -- docs: Add cargo fmt reminder to CLAUDE.md (#2738) by @runningcode - feat(build): Add auto-detection of base_repo_name from git remote (#2735) by @runningcode -- ref(clippy): Enable `tests-outside-test-module` lint (#2736) by @szokeasaurusrex -- build(macos): Allow compiling without Xcode (#2733) by @szokeasaurusrex - feat(build): Add auto-detection of PR number from GitHub Actions (#2722) by @runningcode - feat(build): Auto-detect base_ref from git merge-base (#2720) by @runningcode -- ci(release): Use `container` in workflow instead of `build-in-docker.sh` (#2727) by @szokeasaurusrex -- build(deps): bump github/codeql-action from 3.29.11 to 3.30.1 (#2730) by @dependabot -- build(deps): bump actions/setup-node from 4.4.0 to 5.0.0 (#2731) by @dependabot -- meta(claude): Add `CLAUDE.md` (#2728) by @szokeasaurusrex -- build(deps): bump github/codeql-action from 3.29.8 to 3.29.11 (#2718) by @dependabot -- feat(logs): support log streaming (#2666) by @vgrozdanic - -_Plus 1 more_ +- feat(logs): support log streaming (#2666) by @vgrozdanic ## 2.53.0 @@ -53,7 +33,7 @@ Please note, the `build` commands are still experimental, and are therefore subj ## 2.53.0-alpha -This release reintroduces the `build` (previously named `mobile-app`) commands. +This release reintroduces the `build` (previously named `mobile-app`) commands. ### Various fixes & improvements From 96ec728ea14770190dbfb8d026cb4907167f5855 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:34:03 +0200 Subject: [PATCH 14/19] build(npm): Pin latest optional deps in `package-lock.json` (#2772) This did not get done automatically on release. I will put a PR on top of this to fix the automatic script, so we do not need to do this manually again. --- package-lock.json | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8a31d12f5..cabc441493 100644 --- a/package-lock.json +++ b/package-lock.json @@ -767,51 +767,51 @@ } }, "@sentry/cli-darwin": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.53.0.tgz", - "integrity": "sha512-NNPfpILMwKgpHiyJubHHuauMKltkrgLQ5tvMdxNpxY60jBNdo5VJtpESp4XmXlnidzV4j1z61V4ozU6ttDgt5Q==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.54.0.tgz", + "integrity": "sha512-bQZCC6vLtlnOlDUBZdvvO3Hbzf3keAOI9Ho3IPooaUtkwkKmJbHkOprMJ8WuEDb8JyUOBMFLT7T83bWA7CBJow==", "optional": true }, "@sentry/cli-linux-arm": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.53.0.tgz", - "integrity": "sha512-NdRzQ15Ht83qG0/Lyu11ciy/Hu/oXbbtJUgwzACc7bWvHQA8xEwTsehWexqn1529Kfc5EjuZ0Wmj3MHmp+jOWw==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.54.0.tgz", + "integrity": "sha512-Brx/MsIBXmMuP/rRZos8pMxW5mSZoYmR0tDO483RR9hfE6PnyxhvNOTkLLm6fMd3pGmiG4sr25jYeYQglhIpRA==", "optional": true }, "@sentry/cli-linux-arm64": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.53.0.tgz", - "integrity": "sha512-xY/CZ1dVazsSCvTXzKpAgXaRqfljVfdrFaYZRUaRPf1ZJRGa3dcrivoOhSIeG/p5NdYtMvslMPY9Gm2MT0M83A==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.54.0.tgz", + "integrity": "sha512-ocE6gBD2GiMH8Vm0OdlD8tz9eq4uiTmG2Nb0sQshcTDZ7DkOGEmtbg2Je2F1Eug6wR/zQWzD/t0bMUm6L0X0Rg==", "optional": true }, "@sentry/cli-linux-i686": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.53.0.tgz", - "integrity": "sha512-0REmBibGAB4jtqt9S6JEsFF4QybzcXHPcHtJjgMi5T0ueh952uG9wLzjSxQErCsxTKF+fL8oG0Oz5yKBuCwCCQ==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.54.0.tgz", + "integrity": "sha512-GZyLbZjDX8e635O8iVbzkHs9KGUo5UK0PGbTsjxlKHNgWAf1SY+y+wtWLrF46AhxUveeV/ydEUOJJjcDgXW3+g==", "optional": true }, "@sentry/cli-linux-x64": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.53.0.tgz", - "integrity": "sha512-9UGJL+Vy5N/YL1EWPZ/dyXLkShlNaDNrzxx4G7mTS9ywjg+BIuemo6rnN7w43K1NOjObTVO6zY0FwumJ1pCyLg==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.54.0.tgz", + "integrity": "sha512-NyTM6dp+/cFiULUTGlxlaa83pL+FdWHwPE5IkQ6EiqpsO0auacVwWJIIvj4EbFS7XQ8bgjUA3Rf83lZeI+ZPvQ==", "optional": true }, "@sentry/cli-win32-arm64": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.53.0.tgz", - "integrity": "sha512-G1kjOjrjMBY20rQcJV2GA8KQE74ufmROCDb2GXYRfjvb1fKAsm4Oh8N5+Tqi7xEHdjQoLPkE4CNW0aH68JSUDQ==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.54.0.tgz", + "integrity": "sha512-oVsdo7yWAokGtnl2cbuxvPv3Pu3ge8n9Oyp+iNT1S98XJ/QtRGT8L2ZClNllzEeVFFoZWlK7RVT5xh7OI4ge/w==", "optional": true }, "@sentry/cli-win32-i686": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.53.0.tgz", - "integrity": "sha512-qbGTZUzesuUaPtY9rPXdNfwLqOZKXrJRC1zUFn52hdo6B+Dmv0m/AHwRVFHZP53Tg1NCa8bDei2K/uzRN0dUZw==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.54.0.tgz", + "integrity": "sha512-YvBUq5ky80j2fulllft6ZCtLslwrNc5s8dXV7Jr7IUUmTcVcvkOdgWwAsGRjTmZxXZbfaRaYE2fEvfFwjmciTA==", "optional": true }, "@sentry/cli-win32-x64": { - "version": "2.53.0", - "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.53.0.tgz", - "integrity": "sha512-1TXYxYHtwgUq5KAJt3erRzzUtPqg7BlH9T7MdSPHjJatkrr/kwZqnVe2H6Arr/5NH891vOlIeSPHBdgJUAD69g==", + "version": "2.54.0", + "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.54.0.tgz", + "integrity": "sha512-Jn5abpRrbdcQrec+8QTgGdX8wxTdQr4bm81I/suJ3bXpID+fsiAnp+yclKlq8LWlj5q7uATnGJ4QpajDT9qBRQ==", "optional": true }, "@sinonjs/commons": { From 8e5d60a718772f713ee9665a967f84ce9cafe4a9 Mon Sep 17 00:00:00 2001 From: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> Date: Thu, 18 Sep 2025 18:59:45 +0200 Subject: [PATCH 15/19] ci: `--ignore-scripts` on post-release `npm install` (#2773) As we are doing `--package-lock-only` (which does not actually install packages), we don't have the dependencies needed to run the `install.js` script, which is auto-run when installing. By passing `--ignore-scripts`, we skip running `install.js`, and only end up bumping the `package-lock.json`, which is what we want to do. --- scripts/post-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/post-release.sh b/scripts/post-release.sh index f4249cc94c..03bf6fa436 100644 --- a/scripts/post-release.sh +++ b/scripts/post-release.sh @@ -12,7 +12,7 @@ NEW_VERSION="${2}" git checkout master # We need to update the package-lock.json to include the new version of the optional dependencies. -npm install --package-lock-only +npm install --package-lock-only --ignore-scripts git add package-lock.json From e5eeb8b71e63bfde16e274699fd912344f07d828 Mon Sep 17 00:00:00 2001 From: Sofia Rest <68917129+srest2021@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:11:44 -0700 Subject: [PATCH 16/19] fix(releases): handle partial SHAs correctly in commit resolution (#2734) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes commit resolution failures that caused "Unable to fetch commits" errors in Sentry when using partial SHAs with `sentry-cli releases set-commits`. - Partial SHAs like `f915d32` are now passed to the API without padding, allowing single partial SHAs and ranges of partial SHAs to be successfully associated with a release. - Ensures SHAs are >=64 chars long, same as the backend (no validation for minimum length or hexademical). - Drop support for specifying commits via branch names (an undocumented feature that relied on the repo being checked out locally). Fixes #2064 Fixes CLI-55 Fixes REPLAY-413 Before (commit associations with partial SHAs fail, as denoted by the lack of profile pic for the top 2 releases where I used partial SHAs): Screenshot 2025-09-15 at 5 00 46 PM After (commit associations with partial SHAs now work; note the profile pics for the top 2 releases are now present): Screenshot 2025-09-15 at 5 11 59 PM --------- Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> --- src/commands/releases/set_commits.rs | 81 +++++++++++++++++----------- src/constants.rs | 2 + 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/commands/releases/set_commits.rs b/src/commands/releases/set_commits.rs index c227913701..81b462d82f 100644 --- a/src/commands/releases/set_commits.rs +++ b/src/commands/releases/set_commits.rs @@ -5,8 +5,9 @@ use clap::{Arg, ArgAction, ArgMatches, Command}; use lazy_static::lazy_static; use regex::Regex; -use crate::api::{Api, NewRelease, NoneReleaseInfo, OptionalReleaseInfo, UpdatedRelease}; +use crate::api::{Api, NewRelease, NoneReleaseInfo, OptionalReleaseInfo, Ref, UpdatedRelease}; use crate::config::Config; +use crate::constants::MAX_COMMIT_SHA_LENGTH; use crate::utils::args::ArgExt as _; use crate::utils::formatting::Table; use crate::utils::vcs::{ @@ -53,19 +54,16 @@ pub fn make_command(command: Command) -> Command { .short('c') .value_name("SPEC") .action(ArgAction::Append) - .help("Defines a single commit for a repo as \ + .help(format!("Defines a single commit for a repo as \ identified by the repo name in the remote Sentry config. \ - If no commit has been specified sentry-cli will attempt \ - to auto discover that repository in the local git repo \ - and then use the HEAD commit. This will either use the \ - current git repository or attempt to auto discover a \ - submodule with a compatible URL.\n\n\ - The value can be provided as `REPO` in which case sentry-cli \ - will auto-discover the commit based on reachable repositories. \ - Alternatively it can be provided as `REPO#PATH` in which case \ - the current commit of the repository at the given PATH is \ - assumed. To override the revision `@REV` can be appended \ - which will force the revision to a certain value.")) + The value must be provided as `REPO@SHA` where SHA is \ + at most {MAX_COMMIT_SHA_LENGTH} characters. To specify a range, use `REPO@PREV_SHA..SHA` \ + format.\n\n\ + Note: You must specify a previous commit when setting commits for the first release.\n\n\ + Examples:\ + \n - `my-repo@abc123` (partial SHA)\ + \n - `my-repo@62aaca3ed186edc7671b4cca0ab6ec53cb7de8b5` (full SHA)\ + \n - `my-repo@abc123..def456` (commit range)"))) // Legacy flag that has no effect, left hidden for backward compatibility .arg(Arg::new("ignore-empty") .long("ignore-empty") @@ -83,6 +81,7 @@ fn strip_sha(sha: &str) -> &str { sha } } + pub fn execute(matches: &ArgMatches) -> Result<()> { let config = Config::current(); let api = Api::current(); @@ -90,7 +89,6 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { let version = matches.get_one::("version").unwrap(); let org = config.get_org(matches)?; let repos = authenticated_api.list_organization_repos(&org)?; - let mut commit_specs = vec![]; let heads = if repos.is_empty() { None @@ -105,30 +103,49 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { Some(vec![]) } else if matches.get_flag("local") { None - } else { - if let Some(commits) = matches.get_many::("commits") { - for spec in commits { - let commit_spec = CommitSpec::parse(spec)?; - if repos - .iter() - .any(|r| r.name.to_lowercase() == commit_spec.repo.to_lowercase()) - { - commit_specs.push(commit_spec); - } else { - bail!("Unknown repo '{}'", commit_spec.repo); + } else if let Some(commits) = matches.get_many::("commits") { + let mut refs = vec![]; + for spec in commits { + let commit_spec = CommitSpec::parse(spec)?; + + if !repos + .iter() + .any(|r| r.name.to_lowercase() == commit_spec.repo.to_lowercase()) + { + bail!("Unknown repo '{}'", commit_spec.repo); + } + + if commit_spec.rev.len() > MAX_COMMIT_SHA_LENGTH { + bail!( + "Invalid commit SHA '{}'. Commit SHAs must be {} characters or less.", + commit_spec.rev, + MAX_COMMIT_SHA_LENGTH + ); + } + + if let Some(ref prev_rev) = commit_spec.prev_rev { + if prev_rev.len() > MAX_COMMIT_SHA_LENGTH { + bail!( + "Invalid previous commit SHA '{}'. Commit SHAs must be {} characters or less.", + prev_rev, + MAX_COMMIT_SHA_LENGTH + ); } } + + refs.push(Ref { + repo: commit_spec.repo, + rev: commit_spec.rev, + prev_rev: commit_spec.prev_rev, + }); } - let commits = find_heads( - Some(commit_specs), - &repos, - Some(config.get_cached_vcs_remote()), - )?; - if commits.is_empty() { + if refs.is_empty() { None } else { - Some(commits) + Some(refs) } + } else { + None }; // make sure the release exists if projects are given diff --git a/src/constants.rs b/src/constants.rs index 8a300e883a..ee6a76ffa7 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -47,5 +47,7 @@ pub const DEFAULT_MAX_DIF_ITEM_SIZE: u64 = 1024 * 1024; // 1MB pub const DEFAULT_MAX_DIF_UPLOAD_SIZE: u64 = 35 * 1024 * 1024; // 35MB /// Default maximum time to wait for file assembly. pub const DEFAULT_MAX_WAIT: Duration = Duration::from_secs(5 * 60); +/// Maximum length for commit SHA values, enforced in backend. +pub const MAX_COMMIT_SHA_LENGTH: usize = 64; include!(concat!(env!("OUT_DIR"), "/constants.gen.rs")); From 45790c98b9908cfef832a12af6e1320f50b7c917 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Fri, 19 Sep 2025 05:45:07 -0400 Subject: [PATCH 17/19] fix: Safer asset catalog reader for liquid glass (#2771) While we still have this in two codebases we need to copy over the emerge change from: https://github.com/EmergeTools/emerge/pull/11525 --- .../AssetCatalogParser/AssetCatalogReader.swift | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift b/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift index ab6c1d0577..d69c666029 100644 --- a/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift +++ b/apple-catalog-parsing/native/swift/AssetCatalogParser/Sources/AssetCatalogParser/AssetCatalogReader.swift @@ -84,7 +84,9 @@ enum AssetUtil { key.perform(Selector(("keyList"))), to: UnsafeMutableRawPointer.self ) - let rendition = createRendition(from: structuredThemeStore, keyList) + guard let rendition = createRendition(from: structuredThemeStore, keyList) else { + continue + } let data = rendition.value(forKey: "_srcData") as! Data let length = UInt(data.count) @@ -198,13 +200,13 @@ enum AssetUtil { } private static func createRendition(from themeStore: NSObject, _ keyList: UnsafeMutableRawPointer) - -> NSObject + -> NSObject? { let renditionWithKeySelector = Selector(("renditionWithKey:")) let renditionWithKeyMethod = themeStore.method(for: renditionWithKeySelector)! let renditionWithKeyImp = unsafeBitCast(renditionWithKeyMethod, to: objectiveCMethodImp.self) - return renditionWithKeyImp(themeStore, renditionWithKeySelector, keyList)!.takeUnretainedValue() - as! NSObject + return renditionWithKeyImp(themeStore, renditionWithKeySelector, keyList)?.takeUnretainedValue() + as? NSObject } private static func handleReferenceKey( @@ -222,7 +224,9 @@ enum AssetUtil { referenceKey.perform(Selector(("keyList"))), to: UnsafeMutableRawPointer.self ) - let referenceRendition = createRendition(from: themeStore, referenceKeyList) + guard let referenceRendition = createRendition(from: themeStore, referenceKeyList) else { + return false + } if let result = referenceRendition.perform(Selector(("unslicedImage"))) { let image = result.takeUnretainedValue() as! CGImage From fbcc493f90f2e46bc6015f7def72fd3838d46d03 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 11:47:40 +0200 Subject: [PATCH 18/19] feat: Improve upload error message to show cause (#2765) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Improves error handling for invalid `--vcs-provider` parameter values - When backend returns “400 Unsupported provider" error, shows user-friendly message instead of generic API error Before this PR: ``` WARN 2025-09-18 07:49:04.315821 +02:00 EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. > Preparing for upload completed in 0.114s WARN 2025-09-18 07:49:05.135782 +02:00 Failed to upload 1 file: WARN 2025-09-18 07:49:05.135865 +02:00 - /Users/nelsonosacky/workspace/hackernews/android/app/build/outputs/apk/debug/app-debug.apk (API request failed) error: Failed to upload any files ``` After this PR: ``` WARN 2025-09-18 09:40:30.389061 +02:00 EXPERIMENTAL: The build subcommand is experimental. The command is subject to breaking changes and may be removed without notice in any release. > Preparing for upload completed in 1.233s WARN 2025-09-18 09:40:32.341187 +02:00 Failed to upload 1 file: WARN 2025-09-18 09:40:32.342033 +02:00 - /Users/nelsonosacky/workspace/hackernews/android/app/build/outputs/apk/debug/app-debug.apk WARN 2025-09-18 09:40:32.342108 +02:00 Error: API request failed WARN 2025-09-18 09:40:32.342144 +02:00 Cause: sentry reported an error: Unsupported provider (http status: 400) error: Failed to upload any files ``` Closes EME-301 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com> --- src/commands/build/upload.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/build/upload.rs b/src/commands/build/upload.rs index 81be6fc1b2..260767b4b0 100644 --- a/src/commands/build/upload.rs +++ b/src/commands/build/upload.rs @@ -325,7 +325,8 @@ pub fn execute(matches: &ArgMatches) -> Result<()> { } ); for (path, reason) in errored_paths_and_reasons { - warn!(" - {} ({})", path.display(), reason); + warn!(" - {}", path.display()); + warn!(" Error: {reason:#}"); } } From 0e78d99dc63e4ff1197b32b7cfb7e0a39741ac12 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 19 Sep 2025 19:03:58 +0200 Subject: [PATCH 19/19] Fix clippy warning: use to_owned() instead of to_string() --- src/utils/vcs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/vcs.rs b/src/utils/vcs.rs index 9102076347..07cc944219 100644 --- a/src/utils/vcs.rs +++ b/src/utils/vcs.rs @@ -223,7 +223,7 @@ pub fn get_repo_from_remote(repo: &str) -> String { pub fn get_provider_from_remote(remote: &str) -> String { let obj = VcsUrl::parse(remote); - extract_provider_name(&obj.provider) + extract_provider_name(&obj.provider).to_owned() } pub fn git_repo_remote_url(