Detailed changes
@@ -147,6 +147,24 @@ impl nodejs::Host for WasmState {
#[async_trait]
impl lsp::Host for WasmState {}
+impl From<http::github::GithubRelease> 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<http::github::GithubReleaseAsset> 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<Result<github::GithubRelease, String>> {
+ 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()
@@ -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,
@@ -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<github-release, string>;
+
+ /// 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<github-release, string>;
}
@@ -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<dyn HttpClient>,
+) -> Result<GithubRelease, anyhow::Error> {
+ 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::<GithubRelease>(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<String> {
let mut url = Url::parse(&format!(
"https://github.com/{repo_name_with_owner}/archive/refs/tags",