diff --git a/crates/extension/src/wasm_host/wit/since_v0_0_7.rs b/crates/extension/src/wasm_host/wit/since_v0_0_7.rs index d962d5f7a9..b631113d4b 100644 --- a/crates/extension/src/wasm_host/wit/since_v0_0_7.rs +++ b/crates/extension/src/wasm_host/wit/since_v0_0_7.rs @@ -147,6 +147,24 @@ impl nodejs::Host for WasmState { #[async_trait] impl lsp::Host for WasmState {} +impl From for github::GithubRelease { + fn from(value: http::github::GithubRelease) -> Self { + Self { + version: value.tag_name, + assets: value.assets.into_iter().map(Into::into).collect(), + } + } +} + +impl From for github::GithubReleaseAsset { + fn from(value: http::github::GithubReleaseAsset) -> Self { + Self { + name: value.name, + download_url: value.browser_download_url, + } + } +} + #[async_trait] impl github::Host for WasmState { async fn latest_github_release( @@ -162,17 +180,22 @@ impl github::Host for WasmState { self.host.http_client.clone(), ) .await?; - Ok(github::GithubRelease { - version: release.tag_name, - assets: release - .assets - .into_iter() - .map(|asset| github::GithubReleaseAsset { - name: asset.name, - download_url: asset.browser_download_url, - }) - .collect(), - }) + Ok(release.into()) + }) + .await + .to_wasmtime_result() + } + + async fn github_release_by_tag_name( + &mut self, + repo: String, + tag: String, + ) -> wasmtime::Result> { + maybe!(async { + let release = + http::github::get_release_by_tag_name(&repo, &tag, self.host.http_client.clone()) + .await?; + Ok(release.into()) }) .await .to_wasmtime_result() diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index 21d41d1001..617a9337e8 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -16,7 +16,8 @@ pub use serde_json; pub use wit::{ download_file, make_file_executable, zed::extension::github::{ - latest_github_release, GithubRelease, GithubReleaseAsset, GithubReleaseOptions, + github_release_by_tag_name, latest_github_release, GithubRelease, GithubReleaseAsset, + GithubReleaseOptions, }, zed::extension::nodejs::{ node_binary_path, npm_install_package, npm_package_installed_version, diff --git a/crates/extension_api/wit/since_v0.0.7/github.wit b/crates/extension_api/wit/since_v0.0.7/github.wit index 53ecacb720..bb138f5d31 100644 --- a/crates/extension_api/wit/since_v0.0.7/github.wit +++ b/crates/extension_api/wit/since_v0.0.7/github.wit @@ -25,4 +25,9 @@ interface github { /// Returns the latest release for the given GitHub repository. latest-github-release: func(repo: string, options: github-release-options) -> result; + + /// Returns the GitHub release with the specified tag name for the given GitHub repository. + /// + /// Returns an error if a release with the given tag name does not exist. + github-release-by-tag-name: func(repo: string, tag: string) -> result; } diff --git a/crates/http/src/github.rs b/crates/http/src/github.rs index ff9646727d..f4d330090b 100644 --- a/crates/http/src/github.rs +++ b/crates/http/src/github.rs @@ -76,6 +76,47 @@ pub async fn latest_github_release( .ok_or(anyhow!("Failed to find a release")) } +pub async fn get_release_by_tag_name( + repo_name_with_owner: &str, + tag: &str, + http: Arc, +) -> Result { + let mut response = http + .get( + &format!("https://api.github.com/repos/{repo_name_with_owner}/releases/tags/{tag}"), + Default::default(), + true, + ) + .await + .context("error fetching latest release")?; + + let mut body = Vec::new(); + response + .body_mut() + .read_to_end(&mut body) + .await + .context("error reading latest release")?; + + if response.status().is_client_error() { + let text = String::from_utf8_lossy(body.as_slice()); + bail!( + "status error {}, response: {text:?}", + response.status().as_u16() + ); + } + + let release = serde_json::from_slice::(body.as_slice()).map_err(|err| { + log::error!("Error deserializing: {:?}", err); + log::error!( + "GitHub API response text: {:?}", + String::from_utf8_lossy(body.as_slice()) + ); + anyhow!("error deserializing GitHub release") + })?; + + Ok(release) +} + pub fn build_tarball_url(repo_name_with_owner: &str, tag: &str) -> Result { let mut url = Url::parse(&format!( "https://github.com/{repo_name_with_owner}/archive/refs/tags",