debugger/setup/adapters/
js_debug.rs1use crate::common::{Error, Result};
6use crate::setup::installer::{
7 adapters_dir, ensure_adapters_dir, run_command_args, write_version_file,
8 InstallMethod, InstallOptions, InstallResult, InstallStatus, Installer,
9};
10use crate::setup::registry::{DebuggerInfo, Platform};
11use crate::setup::verifier::{verify_dap_adapter_tcp, VerifyResult};
12use async_trait::async_trait;
13use std::path::PathBuf;
14
15static INFO: DebuggerInfo = DebuggerInfo {
16 id: "js-debug",
17 name: "js-debug",
18 languages: &["javascript", "typescript"],
19 platforms: &[Platform::Linux, Platform::MacOS, Platform::Windows],
20 description: "Microsoft's JavaScript/TypeScript debugger",
21 primary: true,
22};
23
24pub struct JsDebugInstaller;
25
26#[async_trait]
27impl Installer for JsDebugInstaller {
28 fn info(&self) -> &DebuggerInfo {
29 &INFO
30 }
31
32 async fn status(&self) -> Result<InstallStatus> {
33 let adapter_dir = adapters_dir().join("js-debug");
34 let dap_path = get_dap_executable(&adapter_dir);
35
36 if dap_path.exists() {
37 let version = read_package_version(&adapter_dir);
38 return Ok(InstallStatus::Installed {
39 path: dap_path,
40 version,
41 });
42 }
43
44 Ok(InstallStatus::NotInstalled)
45 }
46
47 async fn best_method(&self) -> Result<InstallMethod> {
48 if which::which("npm").is_err() {
49 return Err(Error::Internal(
50 "npm not found. Please install Node.js and npm first.".to_string(),
51 ));
52 }
53
54 Ok(InstallMethod::LanguagePackage {
55 tool: "npm".to_string(),
56 package: "@vscode/js-debug".to_string(),
57 })
58 }
59
60 async fn install(&self, opts: InstallOptions) -> Result<InstallResult> {
61 install_js_debug(&opts).await
62 }
63
64 async fn uninstall(&self) -> Result<()> {
65 let adapter_dir = adapters_dir().join("js-debug");
66 if adapter_dir.exists() {
67 std::fs::remove_dir_all(&adapter_dir)?;
68 println!("Removed {}", adapter_dir.display());
69 } else {
70 println!("js-debug managed installation not found");
71 }
72 Ok(())
73 }
74
75 async fn verify(&self) -> Result<VerifyResult> {
76 let status = self.status().await?;
77
78 match status {
79 InstallStatus::Installed { path, .. } => {
80 let node_path = which::which("node").map_err(|_| {
82 Error::Internal("node not found in PATH".to_string())
83 })?;
84 verify_dap_adapter_tcp(&node_path, &[path.to_string_lossy().to_string()], crate::common::config::TcpSpawnStyle::TcpPortArg).await
86 }
87 InstallStatus::Broken { reason, .. } => Ok(VerifyResult {
88 success: false,
89 capabilities: None,
90 error: Some(reason),
91 }),
92 InstallStatus::NotInstalled => Ok(VerifyResult {
93 success: false,
94 capabilities: None,
95 error: Some("Not installed".to_string()),
96 }),
97 }
98 }
99}
100
101fn get_dap_executable(adapter_dir: &PathBuf) -> PathBuf {
102 let js_path = adapter_dir.join("node_modules/@vscode/js-debug/src/dapDebugServer.js");
104 if js_path.exists() {
105 return js_path;
106 }
107 adapter_dir.join("node_modules/@vscode/js-debug/dist/src/dapDebugServer.js")
108}
109
110fn read_package_version(adapter_dir: &PathBuf) -> Option<String> {
111 let package_json = adapter_dir.join("node_modules/@vscode/js-debug/package.json");
112 if !package_json.exists() {
113 return None;
114 }
115
116 let content = std::fs::read_to_string(&package_json).ok()?;
117 let parsed: serde_json::Value = serde_json::from_str(&content).ok()?;
118 parsed.get("version")
119 .and_then(|v| v.as_str())
120 .map(|s| s.to_string())
121}
122
123async fn install_js_debug(opts: &InstallOptions) -> Result<InstallResult> {
124 println!("Checking for existing installation... not found");
125
126 let npm_path = which::which("npm").map_err(|_| {
127 Error::Internal("npm not found in PATH".to_string())
128 })?;
129 let node_path = which::which("node").map_err(|_| {
130 Error::Internal("node not found in PATH".to_string())
131 })?;
132 println!("Using npm: {}", npm_path.display());
133
134 let adapter_dir = ensure_adapters_dir()?.join("js-debug");
135
136 if opts.force && adapter_dir.exists() {
137 std::fs::remove_dir_all(&adapter_dir)?;
138 }
139
140 std::fs::create_dir_all(&adapter_dir)?;
141
142 let package = if let Some(version) = &opts.version {
143 format!("@vscode/js-debug@{}", version)
144 } else {
145 "@vscode/js-debug".to_string()
146 };
147
148 println!("Installing {}...", package);
149 run_command_args(
150 &npm_path,
151 &["install", "--prefix", adapter_dir.to_str().unwrap_or("."), &package]
152 ).await?;
153
154 let dap_path = get_dap_executable(&adapter_dir);
155 if !dap_path.exists() {
156 return Err(Error::Internal(
157 "@vscode/js-debug installation succeeded but dapDebugServer.js not found".to_string(),
158 ));
159 }
160
161 let version = read_package_version(&adapter_dir);
162
163 if let Some(v) = &version {
164 write_version_file(&adapter_dir, v)?;
165 }
166
167 println!("js-debug installation completed.");
168
169 Ok(InstallResult {
172 path: node_path,
173 version,
174 args: vec![dap_path.to_string_lossy().to_string()],
175 })
176}