适配windows
This commit is contained in:
@@ -48,10 +48,30 @@ pub fn find_claude_binary(app_handle: &tauri::AppHandle) -> Result<String, Strin
|
|||||||
) {
|
) {
|
||||||
info!("Found stored claude path in database: {}", stored_path);
|
info!("Found stored claude path in database: {}", stored_path);
|
||||||
|
|
||||||
// Check if the path still exists
|
// Check if the path still exists and works
|
||||||
let path_buf = PathBuf::from(&stored_path);
|
let mut final_path = stored_path.clone();
|
||||||
|
let mut path_buf = PathBuf::from(&stored_path);
|
||||||
|
|
||||||
|
// On Windows, if stored path exists but is not executable (shell script), try .cmd version
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
if path_buf.exists() && !stored_path.ends_with(".cmd") && !stored_path.ends_with(".exe") {
|
||||||
|
// Test if the current path works by trying to get version
|
||||||
|
if let Err(_) = get_claude_version(&stored_path) {
|
||||||
|
// If it fails, try the .cmd version
|
||||||
|
let cmd_path = format!("{}.cmd", stored_path);
|
||||||
|
let cmd_path_buf = PathBuf::from(&cmd_path);
|
||||||
|
if cmd_path_buf.exists() {
|
||||||
|
if let Ok(_) = get_claude_version(&cmd_path) {
|
||||||
|
final_path = cmd_path.clone();
|
||||||
|
path_buf = cmd_path_buf;
|
||||||
|
info!("Using .cmd version instead of shell script: {}", cmd_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if path_buf.exists() && path_buf.is_file() {
|
if path_buf.exists() && path_buf.is_file() {
|
||||||
return Ok(stored_path);
|
return Ok(final_path);
|
||||||
} else {
|
} else {
|
||||||
warn!("Stored claude path no longer exists: {}", stored_path);
|
warn!("Stored claude path no longer exists: {}", stored_path);
|
||||||
}
|
}
|
||||||
@@ -146,10 +166,8 @@ fn source_preference(installation: &ClaudeInstallation) -> u8 {
|
|||||||
fn discover_system_installations() -> Vec<ClaudeInstallation> {
|
fn discover_system_installations() -> Vec<ClaudeInstallation> {
|
||||||
let mut installations = Vec::new();
|
let mut installations = Vec::new();
|
||||||
|
|
||||||
// 1. Try 'which' command first (now works in production)
|
// 1. Try system command first (now works in production and can return multiple installations)
|
||||||
if let Some(installation) = try_which_command() {
|
installations.extend(find_which_installations());
|
||||||
installations.push(installation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check NVM paths
|
// 2. Check NVM paths
|
||||||
installations.extend(find_nvm_installations());
|
installations.extend(find_nvm_installations());
|
||||||
@@ -164,48 +182,111 @@ fn discover_system_installations() -> Vec<ClaudeInstallation> {
|
|||||||
installations
|
installations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try using the 'which' command to find Claude
|
/// Try using the command to find Claude installations
|
||||||
fn try_which_command() -> Option<ClaudeInstallation> {
|
/// Returns multiple installations if found (Windows 'where' can return multiple paths)
|
||||||
debug!("Trying 'which claude' to find binary...");
|
fn find_which_installations() -> Vec<ClaudeInstallation> {
|
||||||
|
debug!("Trying to find claude binary...");
|
||||||
|
|
||||||
match Command::new("which").arg("claude").output() {
|
// Use 'where' on Windows, 'which' on Unix
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let command_name = "where";
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let command_name = "which";
|
||||||
|
|
||||||
|
let mut installations = Vec::new();
|
||||||
|
|
||||||
|
match Command::new(command_name).arg("claude").output() {
|
||||||
Ok(output) if output.status.success() => {
|
Ok(output) if output.status.success() => {
|
||||||
let output_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
let output_str = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||||
|
|
||||||
if output_str.is_empty() {
|
if output_str.is_empty() {
|
||||||
return None;
|
return installations;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each line (Windows 'where' can return multiple paths)
|
||||||
|
for line in output_str.lines() {
|
||||||
|
let mut path = line.trim().to_string();
|
||||||
|
|
||||||
|
if path.is_empty() {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse aliased output: "claude: aliased to /path/to/claude"
|
// Parse aliased output: "claude: aliased to /path/to/claude"
|
||||||
let path = if output_str.starts_with("claude:") && output_str.contains("aliased to") {
|
if path.starts_with("claude:") && path.contains("aliased to") {
|
||||||
output_str
|
if let Some(aliased_path) = path.split("aliased to").nth(1) {
|
||||||
.split("aliased to")
|
path = aliased_path.trim().to_string();
|
||||||
.nth(1)
|
|
||||||
.map(|s| s.trim().to_string())
|
|
||||||
} else {
|
} else {
|
||||||
Some(output_str)
|
continue;
|
||||||
}?;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug!("'which' found claude at: {}", path);
|
// Convert Unix-style path to Windows path if needed
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let path = {
|
||||||
|
if path.starts_with("/c/") {
|
||||||
|
// Convert /c/path to C:\path
|
||||||
|
let windows_path = path.replace("/c/", "C:\\").replace("/", "\\");
|
||||||
|
windows_path
|
||||||
|
} else if path.starts_with("/") && path.len() > 3 && path.chars().nth(2) == Some('/') {
|
||||||
|
// Convert /X/path to X:\path where X is drive letter
|
||||||
|
let drive = path.chars().nth(1).unwrap();
|
||||||
|
let rest = &path[3..];
|
||||||
|
format!("{}:\\{}", drive.to_uppercase(), rest.replace("/", "\\"))
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let path = path;
|
||||||
|
|
||||||
|
debug!("'{}' found claude at: {}", command_name, path);
|
||||||
|
|
||||||
|
// On Windows, prefer .cmd files over shell scripts
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
let final_path = {
|
||||||
|
if !path.ends_with(".cmd") && !path.ends_with(".exe") {
|
||||||
|
// Check if there's a .cmd file alongside
|
||||||
|
let cmd_path = format!("{}.cmd", path);
|
||||||
|
if PathBuf::from(&cmd_path).exists() {
|
||||||
|
// Only use .cmd if the original doesn't work
|
||||||
|
if let Err(_) = get_claude_version(&path) {
|
||||||
|
cmd_path
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
let final_path = path;
|
||||||
|
|
||||||
// Verify the path exists
|
// Verify the path exists
|
||||||
if !PathBuf::from(&path).exists() {
|
if !PathBuf::from(&final_path).exists() {
|
||||||
warn!("Path from 'which' does not exist: {}", path);
|
warn!("Path from '{}' does not exist: {}", command_name, final_path);
|
||||||
return None;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get version
|
// Get version
|
||||||
let version = get_claude_version(&path).ok().flatten();
|
let version = get_claude_version(&final_path).ok().flatten();
|
||||||
|
|
||||||
Some(ClaudeInstallation {
|
installations.push(ClaudeInstallation {
|
||||||
path,
|
path: final_path,
|
||||||
version,
|
version,
|
||||||
source: "which".to_string(),
|
source: command_name.to_string(),
|
||||||
installation_type: InstallationType::System,
|
installation_type: InstallationType::System,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
installations
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find Claude installations in NVM directories
|
/// Find Claude installations in NVM directories
|
||||||
|
@@ -35,17 +35,14 @@
|
|||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": [
|
"targets": [
|
||||||
"deb",
|
"msi"
|
||||||
"rpm",
|
|
||||||
"appimage",
|
|
||||||
"app",
|
|
||||||
"dmg"
|
|
||||||
],
|
],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns"
|
"icons/icon.icns",
|
||||||
|
"icons/icon.png"
|
||||||
],
|
],
|
||||||
"resources": [],
|
"resources": [],
|
||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
|
@@ -33,6 +33,12 @@ i18n
|
|||||||
zh: {
|
zh: {
|
||||||
common: zh,
|
common: zh,
|
||||||
},
|
},
|
||||||
|
'zh-CN': {
|
||||||
|
common: zh,
|
||||||
|
},
|
||||||
|
'zh-TW': {
|
||||||
|
common: zh,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// 命名空间配置
|
// 命名空间配置
|
||||||
@@ -48,7 +54,7 @@ i18n
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 白名单支持的语言
|
// 白名单支持的语言
|
||||||
supportedLngs: ['en', 'zh'],
|
supportedLngs: ['en', 'zh', 'zh-CN', 'zh-TW'],
|
||||||
|
|
||||||
// 非显式支持的语言回退到en
|
// 非显式支持的语言回退到en
|
||||||
nonExplicitSupportedLngs: true,
|
nonExplicitSupportedLngs: true,
|
||||||
|
Reference in New Issue
Block a user