mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-11 10:00:04 +08:00
fix: 重构版本源检测逻辑 + standalone 目录集中化 + Linux 平台检测补全 (#161)
* fix: Windows 版本检测错误——优先从活跃 CLI 读取版本,修复源判断和包检查顺序 - get_local_version() Windows 块新增活跃 CLI 优先检测(与 macOS 一致), 避免残留的 standalone 汉化版目录被优先扫描到 - detect_installed_source() 修复 standalone 被错误映射为 official(应为 chinese) - read_version_from_installation() 根据 classify_cli_source 动态决定 package.json 检查顺序,避免硬编码汉化版优先导致官方版用户看到错误版本 🤖 Generated with [Qoder][https://qoder.com] * fix: Windows 版本检测忽略残留文件,仅当 CLI 二进制存在时才读取版本 standalone 目录和 npm 全局目录中可能存在卸载后的残留 node_modules/ package.json,导致面板误判 OpenClaw 已安装并显示错误版本号。 现在在读取版本前先检查 openclaw.cmd 是否实际存在。 🤖 Generated with [Qoder][https://qoder.com] * fix: 重构版本源检测逻辑,修复跨源切换后显示旧源的问题 - 新增 detect_source_from_cmd_shim() 通过读取 Windows .cmd shim 内容判断 npm 包归属,替代不可靠的文件系统残留目录扫描 - 重写 detect_installed_source() Windows 检测块,优先使用 shim 内容信号 - upgrade_openclaw_inner() 跨源切换时清理 standalone 安装目录 - get_local_version() npm 段改用 shim 内容判断活跃包 - build_enhanced_path() 三平台添加 standalone 安装目录,避免 dashboard 超时 - 所有平台 fallback 从 "official" 改为 "unknown",支持非面板安装场景 - 前端 dashboard/about 支持 official/chinese/unknown 三源显示 - 新增 unknownSource i18n key(中/英/繁/日/韩) 🤖 Generated with [Qoder][https://qoder.com] * fix: 移除 config.rs 末尾多余的闭合括号,修复编译错误 🤖 Generated with [Qoder][https://qoder.com] * fix: 移除 config.rs 末尾重复的 configure_git_https 和 invalidate_path_cache 定义 🤖 Generated with [Qoder][https://qoder.com] * refactor: standalone 目录集中化、unsafe set_var 适配、Linux 平台检测补全 - 提升 all_standalone_dirs() / standalone_install_dir() 为 pub(crate), 作为全局唯一的 standalone 路径来源 - build_enhanced_path() 三平台块改为调用 config::all_standalone_dirs() - service.rs 三平台 candidate 函数改为调用 super::config::all_standalone_dirs() - utils.rs common_non_windows_cli_candidates() 改为调用集中函数 - std::env::set_var 包裹 unsafe 块,附 SAFETY 注释,适配 Rust 1.83+ - 补全 Linux detect_installed_source(): CLI 路径分类 -> symlink -> standalone -> npm list - 补全 Linux get_local_version(): 活跃 CLI -> standalone VERSION -> symlink package.json 🤖 Generated with [Qoder][https://qoder.com] * fix: service.rs 模块路径修正 super::config -> crate::commands::config mod platform 内部 super 指向 service 模块而非 commands, 需要完整路径 crate::commands::config 才能访问 all_standalone_dirs() 🤖 Generated with [Qoder][https://qoder.com] --------- Co-authored-by: SEVENTEEN-TAN <SEVENTEEN-TAN@users.noreply.github.com>
This commit is contained in:
@@ -192,7 +192,7 @@ fn standalone_archive_ext() -> &'static str {
|
||||
}
|
||||
|
||||
/// standalone 安装目录
|
||||
fn standalone_install_dir() -> Option<PathBuf> {
|
||||
pub(crate) fn standalone_install_dir() -> Option<PathBuf> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Inno Setup PrivilegesRequired=lowest 默认安装到 %LOCALAPPDATA%\Programs
|
||||
@@ -207,7 +207,7 @@ fn standalone_install_dir() -> Option<PathBuf> {
|
||||
}
|
||||
|
||||
/// 所有可能的 standalone 安装位置(用于检测和卸载)
|
||||
fn all_standalone_dirs() -> Vec<PathBuf> {
|
||||
pub(crate) fn all_standalone_dirs() -> Vec<PathBuf> {
|
||||
let mut dirs = Vec::new();
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
@@ -1194,7 +1194,22 @@ async fn get_local_version() -> Option<String> {
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// 优先从活跃 CLI 路径读取版本(与 macOS 逻辑一致)
|
||||
if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() {
|
||||
let cli_pb = PathBuf::from(&cli_path);
|
||||
let resolved = std::fs::canonicalize(&cli_pb).unwrap_or_else(|_| cli_pb.clone());
|
||||
if let Some(ver) = read_version_from_installation(&resolved)
|
||||
.or_else(|| read_version_from_installation(&cli_pb))
|
||||
{
|
||||
return Some(ver);
|
||||
}
|
||||
}
|
||||
|
||||
for sa_dir in all_standalone_dirs() {
|
||||
// 仅当 CLI 二进制实际存在时才读取版本,避免残留文件误判为已安装
|
||||
if !sa_dir.join("openclaw.cmd").exists() {
|
||||
continue;
|
||||
}
|
||||
let version_file = sa_dir.join("VERSION");
|
||||
if let Ok(content) = fs::read_to_string(&version_file) {
|
||||
for line in content.lines() {
|
||||
@@ -1222,21 +1237,87 @@ async fn get_local_version() -> Option<String> {
|
||||
}
|
||||
|
||||
if let Ok(appdata) = std::env::var("APPDATA") {
|
||||
let cli_is_zh = crate::utils::resolve_openclaw_cli_path()
|
||||
.map(|p| crate::utils::classify_cli_source(&p) == "npm-zh")
|
||||
.unwrap_or(false);
|
||||
let pkgs: &[&str] = if cli_is_zh {
|
||||
&["@qingchencloud/openclaw-zh", "openclaw"]
|
||||
let npm_bin = PathBuf::from(&appdata).join("npm");
|
||||
let shim_path = npm_bin.join("openclaw.cmd");
|
||||
// 仅当 npm 全局 CLI shim 存在时才读取版本
|
||||
if !shim_path.exists() {
|
||||
// npm 全局无 CLI shim,跳过
|
||||
} else {
|
||||
&["openclaw", "@qingchencloud/openclaw-zh"]
|
||||
};
|
||||
for pkg in pkgs {
|
||||
let pkg_json = PathBuf::from(&appdata)
|
||||
.join("npm")
|
||||
.join("node_modules")
|
||||
.join(pkg)
|
||||
.join("package.json");
|
||||
if let Ok(content) = fs::read_to_string(&pkg_json) {
|
||||
// 读 .cmd 内容判断活跃包,而非依赖 classify_cli_source(路径无法区分)
|
||||
let is_zh = detect_source_from_cmd_shim(&shim_path)
|
||||
.map(|s| s == "chinese")
|
||||
.unwrap_or(false);
|
||||
let pkgs: &[&str] = if is_zh {
|
||||
&["@qingchencloud/openclaw-zh", "openclaw"]
|
||||
} else {
|
||||
&["openclaw", "@qingchencloud/openclaw-zh"]
|
||||
};
|
||||
for pkg in pkgs {
|
||||
let pkg_json = npm_bin.join("node_modules").join(pkg).join("package.json");
|
||||
if let Ok(content) = fs::read_to_string(&pkg_json) {
|
||||
if let Some(ver) = serde_json::from_str::<Value>(&content)
|
||||
.ok()
|
||||
.and_then(|v| v.get("version")?.as_str().map(String::from))
|
||||
{
|
||||
return Some(ver);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linux: 参照 macOS/Windows 实现,完整检测链
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// 1. 活跃 CLI 优先
|
||||
if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() {
|
||||
let cli_pb = PathBuf::from(&cli_path);
|
||||
let resolved = std::fs::canonicalize(&cli_pb).unwrap_or_else(|_| cli_pb.clone());
|
||||
if let Some(ver) = read_version_from_installation(&resolved)
|
||||
.or_else(|| read_version_from_installation(&cli_pb))
|
||||
{
|
||||
return Some(ver);
|
||||
}
|
||||
}
|
||||
// 2. standalone 目录
|
||||
for sa_dir in all_standalone_dirs() {
|
||||
if !sa_dir.join("openclaw").exists() {
|
||||
continue;
|
||||
}
|
||||
let version_file = sa_dir.join("VERSION");
|
||||
if let Ok(content) = fs::read_to_string(&version_file) {
|
||||
for line in content.lines() {
|
||||
if let Some(ver) = line.strip_prefix("openclaw_version=") {
|
||||
let ver = ver.trim();
|
||||
if !ver.is_empty() {
|
||||
return Some(ver.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let sa_pkg = sa_dir
|
||||
.join("node_modules")
|
||||
.join("@qingchencloud")
|
||||
.join("openclaw-zh")
|
||||
.join("package.json");
|
||||
if let Ok(content) = fs::read_to_string(&sa_pkg) {
|
||||
if let Some(ver) = serde_json::from_str::<Value>(&content)
|
||||
.ok()
|
||||
.and_then(|v| v.get("version")?.as_str().map(String::from))
|
||||
{
|
||||
return Some(ver);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. symlink -> package.json
|
||||
if let Ok(target) = fs::read_link("/usr/local/bin/openclaw") {
|
||||
let pkg_json = PathBuf::from("/usr/local/bin")
|
||||
.join(&target)
|
||||
.parent()
|
||||
.map(|p| p.join("package.json"));
|
||||
if let Some(ref pkg_path) = pkg_json {
|
||||
if let Ok(content) = fs::read_to_string(pkg_path) {
|
||||
if let Some(ver) = serde_json::from_str::<Value>(&content)
|
||||
.ok()
|
||||
.and_then(|v| v.get("version")?.as_str().map(String::from))
|
||||
@@ -1298,9 +1379,27 @@ async fn get_latest_version_for(source: &str) -> Option<String> {
|
||||
.map(String::from)
|
||||
}
|
||||
|
||||
/// 从 Windows .cmd shim 文件内容判断实际关联的 npm 包来源
|
||||
/// npm 生成的 shim 末尾引用实际 JS 入口,据此区分官方版与汉化版
|
||||
#[cfg(target_os = "windows")]
|
||||
fn detect_source_from_cmd_shim(cmd_path: &std::path::Path) -> Option<String> {
|
||||
let content = std::fs::read_to_string(cmd_path).ok()?;
|
||||
let lower = content.to_lowercase();
|
||||
// 汉化版标记:@qingchencloud 或 openclaw-zh
|
||||
if lower.contains("openclaw-zh") || lower.contains("@qingchencloud") {
|
||||
return Some("chinese".into());
|
||||
}
|
||||
// 确认是 npm shim(含 node_modules 引用)→ 官方版
|
||||
if lower.contains("node_modules") {
|
||||
return Some("official".into());
|
||||
}
|
||||
// standalone 的 .cmd 可能不含 node_modules(自定义脚本),由 classify 处理
|
||||
None
|
||||
}
|
||||
|
||||
/// 检测当前安装的是官方版还是汉化版
|
||||
/// macOS: 优先检查 homebrew symlink,fallback 到 npm list
|
||||
/// Windows: 优先检查 npm 全局目录下的 package.json,避免调用 npm list 阻塞
|
||||
/// macOS: 优先检查 symlink 指向的实际路径
|
||||
/// Windows: 读取 .cmd shim 内容判断实际关联的包
|
||||
/// Linux: 直接用 npm list
|
||||
fn detect_installed_source() -> String {
|
||||
// macOS: 检查 openclaw bin 的 symlink 指向
|
||||
@@ -1332,49 +1431,73 @@ fn detect_installed_source() -> String {
|
||||
return "chinese".into();
|
||||
}
|
||||
}
|
||||
"official".into()
|
||||
"unknown".into()
|
||||
}
|
||||
// Windows: 优先通过 CLI 路径判断,fallback 到文件系统检测
|
||||
// Windows: 通过活跃 CLI 的 .cmd shim 内容判断来源
|
||||
// npm 生成的 .cmd shim 最后一行包含实际 JS 入口路径,例如:
|
||||
// "%dp0%\node_modules\openclaw\bin\openclaw.js" → 官方版
|
||||
// "%dp0%\node_modules\@qingchencloud\openclaw-zh\..." → 汉化版
|
||||
// 读取内容即可一锤定音,不依赖文件系统扫描(避免残留目录误判)
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// 优先通过实际 CLI 路径判断
|
||||
if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() {
|
||||
let source = crate::utils::classify_cli_source(&cli_path);
|
||||
if source == "npm-zh" {
|
||||
// 路径本身能确定的情况(standalone 目录、npm-zh 路径含 openclaw-zh)
|
||||
if source == "npm-zh" || source == "standalone" {
|
||||
return "chinese".into();
|
||||
}
|
||||
if source == "standalone" {
|
||||
return "official".into();
|
||||
}
|
||||
// npm-official/npm-global: Windows .cmd shim 路径不含包名,需继续检查文件系统
|
||||
}
|
||||
// 检查所有可能的 standalone 安装目录
|
||||
for sa_dir in all_standalone_dirs() {
|
||||
let sa_zh = sa_dir
|
||||
.join("node_modules")
|
||||
.join("@qingchencloud")
|
||||
.join("openclaw-zh");
|
||||
if sa_zh.exists() {
|
||||
return "chinese".into();
|
||||
// npm-official / npm-global / unknown: 路径不含包名,读 .cmd 内容判断
|
||||
if let Some(shim_source) = detect_source_from_cmd_shim(std::path::Path::new(&cli_path))
|
||||
{
|
||||
return shim_source;
|
||||
}
|
||||
}
|
||||
// 检查 npm 全局目录
|
||||
if let Some(appdata) = std::env::var_os("APPDATA") {
|
||||
let zh_dir = PathBuf::from(&appdata)
|
||||
.join("npm")
|
||||
.join("node_modules")
|
||||
.join("@qingchencloud")
|
||||
.join("openclaw-zh");
|
||||
if zh_dir.exists() {
|
||||
return "chinese".into();
|
||||
// 无活跃 CLI 时的兜底:仅检查 npm 全局目录中实际存在的 shim
|
||||
if let Ok(appdata) = std::env::var("APPDATA") {
|
||||
let shim = PathBuf::from(&appdata).join("npm").join("openclaw.cmd");
|
||||
if let Some(s) = detect_source_from_cmd_shim(&shim) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
// 默认返回官方版
|
||||
"official".into()
|
||||
// 确实无法判断
|
||||
"unknown".into()
|
||||
}
|
||||
// 所有平台通用: npm list 检测
|
||||
// Linux: 参照 macOS 实现,完整检测链
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||
{
|
||||
// 1. 活跃 CLI 路径分类(与 macOS 一致)
|
||||
if let Some(cli_path) = crate::utils::resolve_openclaw_cli_path() {
|
||||
let resolved = std::fs::canonicalize(&cli_path)
|
||||
.ok()
|
||||
.unwrap_or_else(|| PathBuf::from(&cli_path));
|
||||
let source = crate::utils::classify_cli_source(&resolved.to_string_lossy());
|
||||
if source == "npm-zh" || source == "standalone" {
|
||||
return "chinese".into();
|
||||
}
|
||||
if source == "npm-official" || source == "npm-global" {
|
||||
return "official".into();
|
||||
}
|
||||
}
|
||||
// 2. 检查 symlink 指向(/usr/local/bin/openclaw, ~/bin/openclaw)
|
||||
let home = dirs::home_dir().unwrap_or_default();
|
||||
for link in &[
|
||||
PathBuf::from("/usr/local/bin/openclaw"),
|
||||
home.join("bin").join("openclaw"),
|
||||
] {
|
||||
if let Ok(target) = std::fs::read_link(link) {
|
||||
if target.to_string_lossy().contains("openclaw-zh") {
|
||||
return "chinese".into();
|
||||
}
|
||||
return "official".into();
|
||||
}
|
||||
}
|
||||
// 3. standalone 目录检测
|
||||
for sa_dir in all_standalone_dirs() {
|
||||
if sa_dir.join("openclaw").exists() || sa_dir.join("VERSION").exists() {
|
||||
return "chinese".into();
|
||||
}
|
||||
}
|
||||
// 4. npm list 兜底
|
||||
if let Ok(o) = npm_command()
|
||||
.args(["list", "-g", "@qingchencloud/openclaw-zh", "--depth=0"])
|
||||
.output()
|
||||
@@ -1383,7 +1506,7 @@ fn detect_installed_source() -> String {
|
||||
return "chinese".into();
|
||||
}
|
||||
}
|
||||
"official".into()
|
||||
"unknown".into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1391,14 +1514,23 @@ fn detect_installed_source() -> String {
|
||||
pub async fn get_version_info() -> Result<VersionInfo, String> {
|
||||
let current = get_local_version().await;
|
||||
let mut source = detect_installed_source();
|
||||
// 兜底:版本号含 -zh 则一定是汉化版(文件系统检测可能误判)
|
||||
// 兜底:版本号含 -zh 则一定是汉化版
|
||||
if let Some(ref ver) = current {
|
||||
if ver.contains("-zh") && source != "chinese" {
|
||||
source = "chinese".to_string();
|
||||
}
|
||||
}
|
||||
let latest = get_latest_version_for(&source).await;
|
||||
let recommended = recommended_version_for(&source);
|
||||
// unknown 来源不查询 latest/recommended(无法确定对应哪个 npm 包)
|
||||
let latest = if source == "unknown" {
|
||||
None
|
||||
} else {
|
||||
get_latest_version_for(&source).await
|
||||
};
|
||||
let recommended = if source == "unknown" {
|
||||
None
|
||||
} else {
|
||||
recommended_version_for(&source)
|
||||
};
|
||||
let update_available = match (¤t, &recommended) {
|
||||
(Some(c), Some(r)) => recommended_is_newer(r, c),
|
||||
(None, Some(_)) => true,
|
||||
@@ -1546,8 +1678,16 @@ fn read_version_from_installation(cli_path: &std::path::Path) -> Option<String>
|
||||
}
|
||||
}
|
||||
}
|
||||
// 根据 CLI 路径判断来源,决定 package.json 检查顺序
|
||||
// 避免残留的另一来源包被优先读取
|
||||
let cli_source = crate::utils::classify_cli_source(&cli_path.to_string_lossy());
|
||||
let pkg_names: &[&str] = if cli_source == "npm-zh" || cli_source == "standalone" {
|
||||
&["@qingchencloud/openclaw-zh", "openclaw"]
|
||||
} else {
|
||||
&["openclaw", "@qingchencloud/openclaw-zh"]
|
||||
};
|
||||
// 尝试从 package.json 读取
|
||||
for pkg_name in &["@qingchencloud/openclaw-zh", "openclaw"] {
|
||||
for pkg_name in pkg_names {
|
||||
let pkg_json = dir.join("node_modules").join(pkg_name).join("package.json");
|
||||
if let Ok(content) = std::fs::read_to_string(&pkg_json) {
|
||||
if let Some(ver) = serde_json::from_str::<serde_json::Value>(&content)
|
||||
@@ -1560,7 +1700,7 @@ fn read_version_from_installation(cli_path: &std::path::Path) -> Option<String>
|
||||
}
|
||||
// npm shim 情况:向上查找 node_modules
|
||||
if let Some(parent) = dir.parent() {
|
||||
for pkg_name in &["@qingchencloud/openclaw-zh", "openclaw"] {
|
||||
for pkg_name in pkg_names {
|
||||
let pkg_json = parent
|
||||
.join("node_modules")
|
||||
.join(pkg_name)
|
||||
@@ -2010,6 +2150,14 @@ async fn try_standalone_install(
|
||||
])
|
||||
.creation_flags(0x08000000)
|
||||
.status();
|
||||
// 同步更新当前进程的 PATH 环境变量,使后续 resolve_openclaw_cli_path()
|
||||
// 和 build_enhanced_path() 能立即发现 standalone 安装的 CLI,
|
||||
// 无需重启应用(注册表写入仅对新进程生效)
|
||||
// SAFETY: 在 Tauri 命令处理器中单次调用,此时无其他线程并发读写 PATH。
|
||||
// enhanced_path 使用独立的 RwLock 缓存,不受影响。
|
||||
unsafe {
|
||||
std::env::set_var("PATH", format!("{};{}", current_path, install_str));
|
||||
}
|
||||
let _ = app.emit("upgrade-log", format!("已添加到 PATH: {install_str}"));
|
||||
}
|
||||
}
|
||||
@@ -2621,6 +2769,18 @@ async fn upgrade_openclaw_inner(
|
||||
if need_uninstall_old {
|
||||
let _ = app.emit("upgrade-log", format!("清理旧版本 ({old_pkg})..."));
|
||||
let _ = npm_command().args(["uninstall", "-g", old_pkg]).output();
|
||||
|
||||
// 清理 standalone 安装目录(不论从 standalone 切走还是切到 standalone,
|
||||
// npm 路径已经安装了新 CLI,standalone 残留会干扰源检测)
|
||||
for sa_dir in all_standalone_dirs() {
|
||||
if sa_dir.exists() {
|
||||
let _ = app.emit(
|
||||
"upgrade-log",
|
||||
format!("清理 standalone 残留: {}", sa_dir.display()),
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&sa_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换源后重装 Gateway 服务
|
||||
|
||||
@@ -258,6 +258,10 @@ fn build_enhanced_path() -> String {
|
||||
if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") {
|
||||
extra.push(format!("{}/bin", prefix));
|
||||
}
|
||||
// standalone 安装目录(集中管理,避免多处硬编码)
|
||||
for sa_dir in config::all_standalone_dirs() {
|
||||
extra.push(sa_dir.to_string_lossy().into_owned());
|
||||
}
|
||||
// 扫描 nvm 实际安装的版本目录(兼容无 current 符号链接的情况)
|
||||
// 按版本号倒序排列,确保最新版优先(修复 #143:v20 排在 v24 前面)
|
||||
let nvm_versions = home.join(".nvm/versions/node");
|
||||
@@ -326,6 +330,10 @@ fn build_enhanced_path() -> String {
|
||||
if let Ok(prefix) = std::env::var("NPM_CONFIG_PREFIX") {
|
||||
extra.push(format!("{}/bin", prefix));
|
||||
}
|
||||
// standalone 安装目录(集中管理,避免多处硬编码)
|
||||
for sa_dir in config::all_standalone_dirs() {
|
||||
extra.push(sa_dir.to_string_lossy().into_owned());
|
||||
}
|
||||
// NVM_DIR 环境变量(用户可能自定义了 nvm 安装目录)
|
||||
// 按版本号倒序排列,确保最新版优先(修复 #143:v20 排在 v24 前面)
|
||||
let nvm_dir = std::env::var("NVM_DIR")
|
||||
@@ -539,6 +547,14 @@ fn build_enhanced_path() -> String {
|
||||
extra.push(format!(r"{}\npm", appdata));
|
||||
}
|
||||
|
||||
// 6.5 standalone 安装目录(集中管理,避免多处硬编码)
|
||||
// standalone 安装后通过注册表写入用户 PATH,但当前进程的 PATH 环境变量不会
|
||||
// 实时更新,需要显式添加到 enhanced_path 以确保 resolve_openclaw_cli_path()
|
||||
// 能找到 standalone 安装的 openclaw.cmd
|
||||
for sa_dir in config::all_standalone_dirs() {
|
||||
extra.push(sa_dir.to_string_lossy().into_owned());
|
||||
}
|
||||
|
||||
// 7. 系统默认 Node.js 安装路径(优先级最低)
|
||||
extra.push(format!(r"{}\nodejs", pf));
|
||||
extra.push(format!(r"{}\nodejs", pf86));
|
||||
|
||||
@@ -329,10 +329,11 @@ mod platform {
|
||||
|
||||
fn common_cli_candidates() -> Vec<PathBuf> {
|
||||
let mut candidates = Vec::new();
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
candidates.push(home.join(".openclaw-bin").join("openclaw"));
|
||||
// standalone 安装目录(集中管理,避免多处硬编码)
|
||||
for sa_dir in crate::commands::config::all_standalone_dirs() {
|
||||
candidates.push(sa_dir.join("openclaw"));
|
||||
}
|
||||
candidates.push(PathBuf::from("/opt/openclaw/openclaw"));
|
||||
// Homebrew 路径(非 standalone,保留)
|
||||
candidates.push(PathBuf::from("/opt/homebrew/bin/openclaw"));
|
||||
candidates.push(PathBuf::from("/usr/local/bin/openclaw"));
|
||||
candidates
|
||||
@@ -849,23 +850,9 @@ mod platform {
|
||||
fn candidate_cli_paths() -> Vec<PathBuf> {
|
||||
let mut candidates = Vec::new();
|
||||
|
||||
// standalone 安装目录(优先检测,覆盖所有可能位置)
|
||||
if let Ok(localappdata) = env::var("LOCALAPPDATA") {
|
||||
// Inno Setup PrivilegesRequired=lowest 默认路径
|
||||
candidates.push(
|
||||
Path::new(&localappdata)
|
||||
.join("Programs")
|
||||
.join("OpenClaw")
|
||||
.join("openclaw.cmd"),
|
||||
);
|
||||
candidates.push(
|
||||
Path::new(&localappdata)
|
||||
.join("OpenClaw")
|
||||
.join("openclaw.cmd"),
|
||||
);
|
||||
}
|
||||
if let Ok(pf) = env::var("ProgramFiles") {
|
||||
candidates.push(Path::new(&pf).join("OpenClaw").join("openclaw.cmd"));
|
||||
// standalone 安装目录(集中管理,避免多处硬编码)
|
||||
for sa_dir in crate::commands::config::all_standalone_dirs() {
|
||||
candidates.push(sa_dir.join("openclaw.cmd"));
|
||||
}
|
||||
|
||||
if let Ok(appdata) = env::var("APPDATA") {
|
||||
@@ -1224,6 +1211,10 @@ mod platform {
|
||||
.join("openclaw"),
|
||||
);
|
||||
}
|
||||
// standalone 安装目录(集中管理,避免多处硬编码)
|
||||
for sa_dir in crate::commands::config::all_standalone_dirs() {
|
||||
candidates.push(sa_dir.join("openclaw"));
|
||||
}
|
||||
candidates.push(PathBuf::from("/usr/local/bin/openclaw"));
|
||||
candidates.push(PathBuf::from("/usr/bin/openclaw"));
|
||||
for segment in crate::commands::enhanced_path().split(':') {
|
||||
|
||||
@@ -38,11 +38,14 @@ fn find_openclaw_cmd() -> Option<std::path::PathBuf> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn common_non_windows_cli_candidates() -> Vec<std::path::PathBuf> {
|
||||
let mut candidates = Vec::new();
|
||||
// standalone 安装目录(集中管理,避免多处硬编码)
|
||||
for sa_dir in crate::commands::config::all_standalone_dirs() {
|
||||
candidates.push(sa_dir.join("openclaw"));
|
||||
}
|
||||
// 其他标准路径
|
||||
if let Some(home) = dirs::home_dir() {
|
||||
candidates.push(home.join(".openclaw-bin").join("openclaw"));
|
||||
candidates.push(home.join(".local").join("bin").join("openclaw"));
|
||||
}
|
||||
candidates.push(std::path::PathBuf::from("/opt/openclaw/openclaw"));
|
||||
candidates.push(std::path::PathBuf::from("/opt/homebrew/bin/openclaw"));
|
||||
candidates.push(std::path::PathBuf::from("/usr/local/bin/openclaw"));
|
||||
candidates.push(std::path::PathBuf::from("/usr/bin/openclaw"));
|
||||
|
||||
Reference in New Issue
Block a user