mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-04-25 11:28:46 +08:00
Compare commits
2 Commits
fix/proxy-
...
fix/misc-u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49481a259 | ||
|
|
92a07bdb2b |
@@ -69,8 +69,6 @@ pub struct ToolVersion {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_tool_versions() -> Result<Vec<ToolVersion>, String> {
|
||||
use std::process::Command;
|
||||
|
||||
let tools = vec!["claude", "codex", "gemini"];
|
||||
let mut results = Vec::new();
|
||||
|
||||
@@ -81,39 +79,16 @@ pub async fn get_tool_versions() -> Result<Vec<ToolVersion>, String> {
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
for tool in tools {
|
||||
// 1. 获取本地版本 (保持不变)
|
||||
// 1. 获取本地版本 - 先尝试直接执行,失败则扫描常见路径
|
||||
let (local_version, local_error) = {
|
||||
let output = if cfg!(target_os = "windows") {
|
||||
Command::new("cmd")
|
||||
.args(["/C", &format!("{tool} --version")])
|
||||
.output()
|
||||
} else {
|
||||
Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("{tool} --version"))
|
||||
.output()
|
||||
};
|
||||
// 先尝试直接执行
|
||||
let direct_result = try_get_version(tool);
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if out.status.success() {
|
||||
(
|
||||
Some(String::from_utf8_lossy(&out.stdout).trim().to_string()),
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
||||
(
|
||||
None,
|
||||
Some(if err.is_empty() {
|
||||
"未安装或无法执行".to_string()
|
||||
} else {
|
||||
err
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
Err(e) => (None, Some(e.to_string())),
|
||||
if direct_result.0.is_some() {
|
||||
direct_result
|
||||
} else {
|
||||
// 扫描常见的 npm 全局安装路径
|
||||
scan_cli_version(tool)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -153,3 +128,124 @@ async fn fetch_npm_latest_version(client: &reqwest::Client, package: &str) -> Op
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 从版本输出中提取纯版本号
|
||||
fn extract_version(raw: &str) -> String {
|
||||
// 匹配 semver 格式: x.y.z 或 x.y.z-xxx
|
||||
let re = regex::Regex::new(r"\d+\.\d+\.\d+(-[\w.]+)?").unwrap();
|
||||
re.find(raw)
|
||||
.map(|m| m.as_str().to_string())
|
||||
.unwrap_or_else(|| raw.to_string())
|
||||
}
|
||||
|
||||
/// 尝试直接执行命令获取版本
|
||||
fn try_get_version(tool: &str) -> (Option<String>, Option<String>) {
|
||||
use std::process::Command;
|
||||
|
||||
let output = if cfg!(target_os = "windows") {
|
||||
Command::new("cmd")
|
||||
.args(["/C", &format!("{tool} --version")])
|
||||
.output()
|
||||
} else {
|
||||
Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(format!("{tool} --version"))
|
||||
.output()
|
||||
};
|
||||
|
||||
match output {
|
||||
Ok(out) => {
|
||||
if out.status.success() {
|
||||
let raw = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
(Some(extract_version(&raw)), None)
|
||||
} else {
|
||||
let err = String::from_utf8_lossy(&out.stderr).trim().to_string();
|
||||
(
|
||||
None,
|
||||
Some(if err.is_empty() {
|
||||
"未安装或无法执行".to_string()
|
||||
} else {
|
||||
err
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
Err(e) => (None, Some(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 扫描常见路径查找 CLI
|
||||
fn scan_cli_version(tool: &str) -> (Option<String>, Option<String>) {
|
||||
use std::process::Command;
|
||||
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
|
||||
// 常见的 npm 全局安装路径
|
||||
let mut search_paths: Vec<std::path::PathBuf> = vec![
|
||||
home.join(".npm-global/bin"),
|
||||
home.join(".local/bin"),
|
||||
home.join("n/bin"), // n version manager
|
||||
];
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
search_paths.push(std::path::PathBuf::from("/opt/homebrew/bin"));
|
||||
search_paths.push(std::path::PathBuf::from("/usr/local/bin"));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
search_paths.push(std::path::PathBuf::from("/usr/local/bin"));
|
||||
search_paths.push(std::path::PathBuf::from("/usr/bin"));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if let Some(appdata) = dirs::data_dir() {
|
||||
search_paths.push(appdata.join("npm"));
|
||||
}
|
||||
search_paths.push(std::path::PathBuf::from("C:\\Program Files\\nodejs"));
|
||||
}
|
||||
|
||||
// 扫描 nvm 目录下的所有 node 版本
|
||||
let nvm_base = home.join(".nvm/versions/node");
|
||||
if nvm_base.exists() {
|
||||
if let Ok(entries) = std::fs::read_dir(&nvm_base) {
|
||||
for entry in entries.flatten() {
|
||||
let bin_path = entry.path().join("bin");
|
||||
if bin_path.exists() {
|
||||
search_paths.push(bin_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 在每个路径中查找工具
|
||||
for path in &search_paths {
|
||||
let tool_path = if cfg!(target_os = "windows") {
|
||||
path.join(format!("{tool}.cmd"))
|
||||
} else {
|
||||
path.join(tool)
|
||||
};
|
||||
|
||||
if tool_path.exists() {
|
||||
// 构建 PATH 环境变量,确保 node 可被找到
|
||||
let current_path = std::env::var("PATH").unwrap_or_default();
|
||||
let new_path = format!("{}:{}", path.display(), current_path);
|
||||
|
||||
let output = Command::new(&tool_path)
|
||||
.arg("--version")
|
||||
.env("PATH", &new_path)
|
||||
.output();
|
||||
|
||||
if let Ok(out) = output {
|
||||
if out.status.success() {
|
||||
let raw = String::from_utf8_lossy(&out.stdout).trim().to_string();
|
||||
return (Some(extract_version(&raw)), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(None, Some("未安装或无法执行".to_string()))
|
||||
}
|
||||
|
||||
@@ -559,7 +559,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
<div
|
||||
key={entry.id}
|
||||
onClick={() => handleSelect(entry.url)}
|
||||
className={`group flex cursor-pointer items-center justify-between px-3 py-2.5 rounded-lg border transition ${
|
||||
className={`group flex cursor-pointer items-center justify-between px-3 py-2.5 rounded-lg border transition text-foreground ${
|
||||
isSelected
|
||||
? "border-primary/70 bg-primary/5 shadow-sm"
|
||||
: "border-border-default bg-background hover:bg-muted"
|
||||
@@ -577,7 +577,7 @@ const EndpointSpeedTest: React.FC<EndpointSpeedTestProps> = ({
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-sm text-gray-900 dark:text-gray-100">
|
||||
<div className="truncate text-sm text-foreground">
|
||||
{entry.url}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-border-default bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"flex h-9 w-full rounded-md border border-border-default bg-background text-foreground px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-blue-500/20 dark:focus:ring-blue-400/20 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
Reference in New Issue
Block a user