fix: resolve TypeScript LSP initialization failure

typescript-language-server requires a valid TypeScript installation to
function. Previously, no initializationOptions were sent during the LSP
initialize request, causing the server to fail with:

  'Could not find a valid TypeScript installation. Please ensure that
  the typescript dependency is installed in the workspace or that a
  valid tsserver.path is specified.'

This fix:
- Adds initializationOptions.tsserver.path resolution that searches for
  TypeScript in: workspace node_modules, global npm install, and npx cache
- Wires initialization_options into the LSP startup flow via LSPServerType
- Updates the install step to proactively install TypeScript globally if
  not found locally
This commit is contained in:
Ryan Ward
2026-07-16 16:58:47 -05:00
parent 0db915712f
commit 4aa2c0d685
3 changed files with 158 additions and 1 deletions
+26
View File
@@ -6,6 +6,8 @@ use std::sync::Arc;
use command::r#async::Command;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
#[cfg(not(target_arch = "wasm32"))]
use serde_json::Value;
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
@@ -197,6 +199,30 @@ impl LSPServerType {
}
}
/// Returns server-specific `initializationOptions` to send during LSP initialization.
///
/// For most servers this returns `None`. For `typescript-language-server`, it resolves
/// the path to `tsserver` from the workspace's `node_modules` or a globally-installed
/// TypeScript package so the server doesn't fail with "Could not find a valid TypeScript
/// installation".
#[cfg(not(target_arch = "wasm32"))]
pub async fn initialization_options(
&self,
workspace_root: &std::path::Path,
path_env_var: Option<&str>,
) -> Option<Value> {
match self {
LSPServerType::TypeScriptLanguageServer => {
TypeScriptLanguageServerCandidate::resolve_initialization_options(
workspace_root,
path_env_var,
)
.await
}
_ => None,
}
}
pub fn candidate(&self, client: Arc<http_client::Client>) -> Box<dyn LanguageServerCandidate> {
match self {
LSPServerType::RustAnalyzer => Box::new(RustAnalyzerCandidate::new(client)),