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}