From 02266039cf9331f8ca00cda14cdf0de167e1a31d Mon Sep 17 00:00:00 2001 From: xintaofei Date: Sat, 28 Mar 2026 22:17:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BAnode=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/lib.rs | 5 +- src-tauri/src/process.rs | 103 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c9071f3..09d8207 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -34,7 +34,10 @@ fn get_folder_id_from_url(window: &tauri::Window) -> Option { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { - let _ = fix_path_env::fix(); + if let Err(err) = fix_path_env::fix() { + eprintln!("[PATH] fix_path_env failed: {err}"); + } + process::ensure_node_in_path(); tauri::Builder::default() .plugin(tauri_plugin_window_state::Builder::new().build()) diff --git a/src-tauri/src/process.rs b/src-tauri/src/process.rs index 22e1ea6..08b38f7 100644 --- a/src-tauri/src/process.rs +++ b/src-tauri/src/process.rs @@ -1,4 +1,5 @@ use std::ffi::{OsStr, OsString}; +use std::path::PathBuf; use std::process::Command; #[cfg(windows)] @@ -80,3 +81,105 @@ where configure_tokio_command(&mut command); command } + +/// If `node` is not already in PATH, detect common Node.js version manager +/// installations (nvm, fnm, volta) and prepend the best matching bin directory +/// to the process PATH so that **all** downstream code (`which`, `Command`, +/// child processes) can find node/npm/npx without any special handling. +/// +/// Call once at startup, after `fix_path_env::fix()`. +pub fn ensure_node_in_path() { + // Already reachable — nothing to do. + if which::which("node").is_ok() { + return; + } + + let home = match dirs::home_dir() { + Some(h) => h, + None => return, + }; + + if let Some(bin_dir) = find_node_bin_dir(&home) { + prepend_to_path(&bin_dir); + eprintln!("[PATH] node not in PATH, prepended {}", bin_dir.display()); + } +} + +/// Search common Node.js version manager directories for a `node` binary and +/// return the containing bin directory. +fn find_node_bin_dir(home: &std::path::Path) -> Option { + let mut candidates: Vec = Vec::new(); + + // ── nvm ────────────────────────────────────────────────────────────── + let nvm_dir = std::env::var("NVM_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| home.join(".nvm")); + if nvm_dir.is_dir() { + let versions_dir = nvm_dir.join("versions").join("node"); + + // Prefer the version pointed to by the "default" alias. + let default_alias = nvm_dir.join("alias").join("default"); + if let Ok(alias) = std::fs::read_to_string(&default_alias) { + let alias = alias.trim().to_string(); + if let Ok(entries) = std::fs::read_dir(&versions_dir) { + for entry in entries.flatten() { + let name = entry.file_name().to_string_lossy().to_string(); + let stripped = name.trim_start_matches('v'); + if stripped.starts_with(&alias) || name.starts_with(&alias) { + candidates.push(entry.path().join("bin")); + } + } + } + } + + // Fall back: all installed versions, newest first. + if let Ok(mut entries) = std::fs::read_dir(&versions_dir) + .map(|rd| rd.flatten().map(|e| e.path()).collect::>()) + { + entries.sort(); + entries.reverse(); + for entry in entries { + candidates.push(entry.join("bin")); + } + } + } + + // ── fnm ────────────────────────────────────────────────────────────── + let fnm_dir = std::env::var("FNM_DIR") + .map(PathBuf::from) + .unwrap_or_else(|_| home.join(".local").join("share").join("fnm")); + let fnm_versions = fnm_dir.join("node-versions"); + if fnm_versions.is_dir() { + if let Ok(mut entries) = std::fs::read_dir(&fnm_versions) + .map(|rd| rd.flatten().map(|e| e.path()).collect::>()) + { + entries.sort(); + entries.reverse(); + for entry in entries { + candidates.push(entry.join("installation").join("bin")); + } + } + } + + // ── volta ──────────────────────────────────────────────────────────── + let volta_home = std::env::var("VOLTA_HOME") + .map(PathBuf::from) + .unwrap_or_else(|_| home.join(".volta")); + let volta_bin = volta_home.join("bin"); + if volta_bin.is_dir() { + candidates.push(volta_bin); + } + + // Return the first candidate that actually contains a `node` binary. + candidates.into_iter().find(|dir| dir.join("node").is_file()) +} + +/// Prepend a directory to the process `PATH` environment variable. +fn prepend_to_path(dir: &std::path::Path) { + let sep = if cfg!(windows) { ";" } else { ":" }; + let current = std::env::var_os("PATH").unwrap_or_default(); + let mut new_path = OsString::from(dir); + new_path.push(sep); + new_path.push(current); + std::env::set_var("PATH", new_path); +}