installation.rs

  1use anyhow::{anyhow, Context, Result};
  2use client::http::HttpClient;
  3
  4use serde::Deserialize;
  5use smol::io::AsyncReadExt;
  6use std::{path::Path, sync::Arc};
  7
  8pub struct GitHubLspBinaryVersion {
  9    pub name: String,
 10    pub url: String,
 11}
 12
 13#[derive(Deserialize)]
 14#[serde(rename_all = "kebab-case")]
 15struct NpmInfo {
 16    #[serde(default)]
 17    dist_tags: NpmInfoDistTags,
 18    versions: Vec<String>,
 19}
 20
 21#[derive(Deserialize, Default)]
 22struct NpmInfoDistTags {
 23    latest: Option<String>,
 24}
 25
 26#[derive(Deserialize)]
 27pub(crate) struct GithubRelease {
 28    pub name: String,
 29    pub assets: Vec<GithubReleaseAsset>,
 30}
 31
 32#[derive(Deserialize)]
 33pub(crate) struct GithubReleaseAsset {
 34    pub name: String,
 35    pub browser_download_url: String,
 36}
 37
 38pub async fn npm_package_latest_version(name: &str) -> Result<String> {
 39    let output = smol::process::Command::new("npm")
 40        .args(["info", name, "--json"])
 41        .output()
 42        .await
 43        .context("failed to run npm info")?;
 44    if !output.status.success() {
 45        Err(anyhow!(
 46            "failed to execute npm info:\nstdout: {:?}\nstderr: {:?}",
 47            String::from_utf8_lossy(&output.stdout),
 48            String::from_utf8_lossy(&output.stderr)
 49        ))?;
 50    }
 51    let mut info: NpmInfo = serde_json::from_slice(&output.stdout)?;
 52    info.dist_tags
 53        .latest
 54        .or_else(|| info.versions.pop())
 55        .ok_or_else(|| anyhow!("no version found for npm package {}", name))
 56}
 57
 58pub async fn npm_install_packages(
 59    packages: impl IntoIterator<Item = (&str, &str)>,
 60    directory: &Path,
 61) -> Result<()> {
 62    let output = smol::process::Command::new("npm")
 63        .arg("install")
 64        .arg("--prefix")
 65        .arg(directory)
 66        .args(
 67            packages
 68                .into_iter()
 69                .map(|(name, version)| format!("{name}@{version}")),
 70        )
 71        .output()
 72        .await
 73        .context("failed to run npm install")?;
 74    if !output.status.success() {
 75        Err(anyhow!(
 76            "failed to execute npm install:\nstdout: {:?}\nstderr: {:?}",
 77            String::from_utf8_lossy(&output.stdout),
 78            String::from_utf8_lossy(&output.stderr)
 79        ))?;
 80    }
 81    Ok(())
 82}
 83
 84pub(crate) async fn latest_github_release(
 85    repo_name_with_owner: &str,
 86    http: Arc<dyn HttpClient>,
 87) -> Result<GithubRelease, anyhow::Error> {
 88    let mut response = http
 89        .get(
 90            &format!("https://api.github.com/repos/{repo_name_with_owner}/releases/latest"),
 91            Default::default(),
 92            true,
 93        )
 94        .await
 95        .context("error fetching latest release")?;
 96    let mut body = Vec::new();
 97    response
 98        .body_mut()
 99        .read_to_end(&mut body)
100        .await
101        .context("error reading latest release")?;
102    let release: GithubRelease =
103        serde_json::from_slice(body.as_slice()).context("error deserializing latest release")?;
104    Ok(release)
105}