diff --git a/Cargo.lock b/Cargo.lock index 6693f9639af701d9ece2c1a257d41cf237df16b9..d23485768fc9357a1b2ae00fa709f6747fc9525c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6091,6 +6091,7 @@ dependencies = [ "async-trait", "futures 0.3.28", "log", + "semver", "serde", "serde_json", "smol", diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index b44bf830172446d6910b3415da5e12281696940d..8c2596f11fa90f439144d4b5207a0f0c50513a5f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -336,12 +336,12 @@ pub trait LspAdapter: 'static + Send + Sync { name.clone(), LanguageServerBinaryStatus::CheckingForUpdate, ); - let version_info = self.fetch_latest_server_version(delegate.as_ref()).await?; + let latest_version = self.fetch_latest_server_version(delegate.as_ref()).await?; log::info!("downloading language server {:?}", name.0); delegate.update_status(self.name(), LanguageServerBinaryStatus::Downloading); let mut binary = self - .fetch_server_binary(version_info, container_dir.to_path_buf(), delegate.as_ref()) + .fetch_server_binary(latest_version, container_dir.to_path_buf(), delegate.as_ref()) .await; delegate.update_status(name.clone(), LanguageServerBinaryStatus::Downloaded); @@ -408,7 +408,7 @@ pub trait LspAdapter: 'static + Send + Sync { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, delegate: &dyn LspAdapterDelegate, ) -> Result; diff --git a/crates/languages/src/astro.rs b/crates/languages/src/astro.rs index 95aa150d61648154fa7295b28407b13c79d205b5..75db8e9e945b0a9baef6db91ef3835d5b07727fb 100644 --- a/crates/languages/src/astro.rs +++ b/crates/languages/src/astro.rs @@ -49,19 +49,22 @@ impl LspAdapter for AstroLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "@astrojs/language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_npm_package = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_npm_package { self.node - .npm_install_packages( - &container_dir, - &[("@astrojs/language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/css.rs b/crates/languages/src/css.rs index ba55ed31345993642aaf15bed77ffb41e06f1140..a91ff8befce50a7394cdc832c837c3f8f2b720e1 100644 --- a/crates/languages/src/css.rs +++ b/crates/languages/src/css.rs @@ -50,19 +50,22 @@ impl LspAdapter for CssLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "vscode-langservers-extracted"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("vscode-langservers-extracted", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/dockerfile.rs b/crates/languages/src/dockerfile.rs index 35ee844d89f3b4a4f2f696494799512a0b595df0..3d768c577d872f8d5cc6c5aa1accf4b23309375c 100644 --- a/crates/languages/src/dockerfile.rs +++ b/crates/languages/src/dockerfile.rs @@ -48,19 +48,22 @@ impl LspAdapter for DockerfileLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "dockerfile-language-server-nodejs"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("dockerfile-language-server-nodejs", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/elm.rs b/crates/languages/src/elm.rs index 5f99649ee937422066ad6de925f1730d16de0cb2..b82b92941e636633e0a4d6f956a7dae4cd271c05 100644 --- a/crates/languages/src/elm.rs +++ b/crates/languages/src/elm.rs @@ -53,19 +53,22 @@ impl LspAdapter for ElmLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "@elm-tooling/elm-language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("@elm-tooling/elm-language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/html.rs b/crates/languages/src/html.rs index 3935d20456bb733573bbfcfb78c6b7a58a81a345..a8dbfd47ba144236e5294a4d30669ef50883f543 100644 --- a/crates/languages/src/html.rs +++ b/crates/languages/src/html.rs @@ -50,19 +50,22 @@ impl LspAdapter for HtmlLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "vscode-langservers-extracted"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("vscode-langservers-extracted", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 1b32876155f3aebdf29c1b4e881ef05e9c33cdbf..2c9cc76ac4adcfd6b696587941478eac4c5ca66b 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -102,19 +102,22 @@ impl LspAdapter for JsonLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "vscode-json-languageserver"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("vscode-json-languageserver", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/php.rs b/crates/languages/src/php.rs index 0fcb2b3b23dab742787ebadd641792ba06d095e1..1e539826da65661f76bf595b0323888e31765ca8 100644 --- a/crates/languages/src/php.rs +++ b/crates/languages/src/php.rs @@ -51,18 +51,30 @@ impl LspAdapter for IntelephenseLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _delegate: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(Self::SERVER_PATH); - - if fs::metadata(&server_path).await.is_err() { + let package_name = "intelephense"; + + let should_install_language_server = self + .node + .should_install_npm_package( + package_name, + &server_path, + &container_dir, + latest_version.0.as_str(), + ) + .await; + + if should_install_language_server { self.node - .npm_install_packages(&container_dir, &[("intelephense", version.0.as_str())]) + .npm_install_packages(&container_dir, &[(package_name, latest_version.0.as_str())]) .await?; } + Ok(LanguageServerBinary { path: self.node.binary_path().await?, env: None, diff --git a/crates/languages/src/prisma.rs b/crates/languages/src/prisma.rs index 17fcb5fd3f26f7c9fb70c669cca748966a5e2136..40f65babf015cf861bf2a04b1b576cbfe95a0deb 100644 --- a/crates/languages/src/prisma.rs +++ b/crates/languages/src/prisma.rs @@ -48,19 +48,22 @@ impl LspAdapter for PrismaLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "@prisma/language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("@prisma/language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/purescript.rs b/crates/languages/src/purescript.rs index 8787826a188830bd07a5246b8dd3c8c4ecdff35b..e5a167f7ae9fa2e60f34f2ed66ed222abd7d74e6 100644 --- a/crates/languages/src/purescript.rs +++ b/crates/languages/src/purescript.rs @@ -52,19 +52,22 @@ impl LspAdapter for PurescriptLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "purescript-language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_npm_package = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_npm_package { self.node - .npm_install_packages( - &container_dir, - &[("purescript-language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index bd9eab3ced4f290fd6577afcfecc1060c1018836..48f5b29210181ef97ad775524bcc98c82b299679 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -3,7 +3,6 @@ use async_trait::async_trait; use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use lsp::LanguageServerBinary; use node_runtime::NodeRuntime; -use smol::fs; use std::{ any::Any, ffi::OsString, @@ -43,16 +42,22 @@ impl LspAdapter for PythonLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "pyright"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages(&container_dir, &[("pyright", version.as_str())]) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/svelte.rs b/crates/languages/src/svelte.rs index 721c2e6640d9892ccbcf32c8d7b09d7f047a9fdd..58d1dae2ea4cc313cc7834201e6a27f5a9b7011f 100644 --- a/crates/languages/src/svelte.rs +++ b/crates/languages/src/svelte.rs @@ -49,19 +49,22 @@ impl LspAdapter for SvelteLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "svelte-language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("svelte-language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/tailwind.rs b/crates/languages/src/tailwind.rs index c49f5d859036103e9f10a8b531f3a3d8a8d2ea69..49a60102ad61172226928721f99f0f2ea6ef7c1a 100644 --- a/crates/languages/src/tailwind.rs +++ b/crates/languages/src/tailwind.rs @@ -51,19 +51,22 @@ impl LspAdapter for TailwindLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "@tailwindcss/language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("@tailwindcss/language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index bf220130a9679ca3eb83c1c3d61f0499738e066f..de6d5b3f01fddb63813cc3dd3e64f0ffaa2afcf2 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -71,22 +71,33 @@ impl LspAdapter for TypeScriptLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(Self::NEW_SERVER_PATH); - - if fs::metadata(&server_path).await.is_err() { + let package_name = "typescript"; + + let should_install_language_server = self + .node + .should_install_npm_package( + package_name, + &server_path, + &container_dir, + latest_version.typescript_version.as_str(), + ) + .await; + + if should_install_language_server { self.node .npm_install_packages( &container_dir, &[ - ("typescript", version.typescript_version.as_str()), + (package_name, latest_version.typescript_version.as_str()), ( "typescript-language-server", - version.server_version.as_str(), + latest_version.server_version.as_str(), ), ], ) diff --git a/crates/languages/src/vue.rs b/crates/languages/src/vue.rs index e29516a5df2e5067908ca9ef2272caac2863b334..6c611d830a6523904ef486d38fff7920f2069cd0 100644 --- a/crates/languages/src/vue.rs +++ b/crates/languages/src/vue.rs @@ -86,6 +86,7 @@ impl super::LspAdapter for VueLspAdapter { let version = version.downcast::().unwrap(); let server_path = container_dir.join(Self::SERVER_PATH); let ts_path = container_dir.join(Self::TYPESCRIPT_PATH); + if fs::metadata(&server_path).await.is_err() { self.node .npm_install_packages( diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index 5c288c22b6111149babd246874c233464b2f49e3..ce8544e0120fd9e15398fa1adcf4dc00933937d2 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -52,19 +52,22 @@ impl LspAdapter for YamlLspAdapter { async fn fetch_server_binary( &self, - version: Box, + latest_version: Box, container_dir: PathBuf, _: &dyn LspAdapterDelegate, ) -> Result { - let version = version.downcast::().unwrap(); + let latest_version = latest_version.downcast::().unwrap(); let server_path = container_dir.join(SERVER_PATH); + let package_name = "yaml-language-server"; - if fs::metadata(&server_path).await.is_err() { + let should_install_language_server = self + .node + .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version) + .await; + + if should_install_language_server { self.node - .npm_install_packages( - &container_dir, - &[("yaml-language-server", version.as_str())], - ) + .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())]) .await?; } diff --git a/crates/node_runtime/Cargo.toml b/crates/node_runtime/Cargo.toml index 7e713a3e2d37904b30a20d7d26342354d2474c0e..1097f85f38954176dc50ee68eeb5f3af38fa1dcd 100644 --- a/crates/node_runtime/Cargo.toml +++ b/crates/node_runtime/Cargo.toml @@ -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 diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 7317635dd103ca3eaffcbe4478da8b485a4175c5..59f136d7ec5e9ab0d096b2583804bd9756328804 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -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 = 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",