Implement updating for node-based language servers (#9361)

Fixes: https://github.com/zed-industries/zed/issues/9234

This doesn't address `vue` as it has a slightly different install code,
but it should be fairly simple to add - I'll add it in in a follow-up.

This PR will allow all (except `vue`) node-based language servers to
update. It is mostly just throwing in a method into the `NodeRuntime`
trait that is used for checking if a package doesn't exist locally, or
is out of date, by checking the version against what's newest, and
installing. If any parsing of the `package.json` data fails along the
way, it assumes something has gone awry on the users system, logs the
error, and then proceeds with trying to install the package, so that
users don't get stuck on version if their package has some bad data.
Outside of adding this method, it just adds that check in all of the
language server's individual `fetch_server_binary` methods.

Release Notes:

- Added updating for node-based language servers
([#9234](https://github.com/zed-industries/zed/issues/9234)).
This commit is contained in:
Joseph T. Lyons 2024-03-15 11:40:28 -04:00 committed by GitHub
parent cb16003133
commit 276139f792
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 213 additions and 94 deletions

View file

@ -19,6 +19,7 @@ async-tar.workspace = true
async-trait.workspace = true
futures.workspace = true
log.workspace = true
semver.workspace = true
serde.workspace = true
serde_json.workspace = true
smol.workspace = true

View file

@ -1,7 +1,10 @@
use anyhow::{anyhow, bail, Context, Result};
use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive;
use futures::AsyncReadExt;
use semver::Version;
use serde::Deserialize;
use serde_json::Value;
use smol::{fs, io::BufReader, lock::Mutex, process::Command};
use std::process::{Output, Stdio};
use std::{
@ -10,6 +13,7 @@ use std::{
sync::Arc,
};
use util::http::HttpClient;
use util::ResultExt;
const VERSION: &str = "v18.15.0";
@ -41,6 +45,56 @@ pub trait NodeRuntime: Send + Sync {
async fn npm_install_packages(&self, directory: &Path, packages: &[(&str, &str)])
-> Result<()>;
async fn should_install_npm_package(
&self,
package_name: &str,
local_executable_path: &Path,
local_package_directory: &PathBuf,
latest_version: &str,
) -> bool {
// In the case of the local system not having the package installed,
// or in the instances where we fail to parse package.json data,
// we attempt to install the package.
if fs::metadata(local_executable_path).await.is_err() {
return true;
}
let package_json_path = local_package_directory.join("package.json");
let mut contents = String::new();
let Some(mut file) = fs::File::open(package_json_path).await.log_err() else {
return true;
};
file.read_to_string(&mut contents).await.log_err();
let Some(package_json): Option<Value> = serde_json::from_str(&contents).log_err() else {
return true;
};
let installed_version = package_json
.get("dependencies")
.and_then(|deps| deps.get(package_name))
.and_then(|server_name| server_name.as_str());
let Some(installed_version) = installed_version else {
return true;
};
let Some(latest_version) = Version::parse(latest_version).log_err() else {
return true;
};
let installed_version = installed_version.trim_start_matches(|c: char| !c.is_ascii_digit());
let Some(installed_version) = Version::parse(installed_version).log_err() else {
return true;
};
installed_version < latest_version
}
}
pub struct RealNodeRuntime {
@ -239,6 +293,7 @@ impl NodeRuntime for RealNodeRuntime {
let mut arguments: Vec<_> = packages.iter().map(|p| p.as_str()).collect();
arguments.extend_from_slice(&[
"--save-exact",
"--fetch-retry-mintimeout",
"2000",
"--fetch-retry-maxtimeout",