fix(browser): platform-aware error messages with WSL detection

- Add is_wsl() detection via /proc/version
- Add example_npx_path() returning platform-appropriate example path
- Replace all hardcoded macOS paths in error messages with dynamic ones
- Add WSL-specific hints: Node.js must be installed inside WSL, not Windows
- WSL hint includes nvm install instructions
- Clean build: 0 errors, 0 warnings
This commit is contained in:
2026-06-12 17:22:06 +05:30
parent 9954f28aa6
commit b5a5784707

View File

@@ -11,12 +11,19 @@
//! 3. Waits for the Unix socket to appear (up to 30 seconds)
//! 4. Returns a ready-to-use `BrowserClient`
//!
//! # Cross-platform npx resolution
//! Cross-platform npx resolution
//!
//! The daemon is started via Node.js's `npx`. Resolution order:
//! 1. `config.browser.node_path` — explicit path in config (e.g.
//! `/home/user/.nvm/versions/node/v22/bin/npx`)
//! 2. `"npx"` on Linux/macOS, `"npx.cmd"` on Windows — PATH lookup
//!
//! WSL Detection
//!
//! Windows Subsystem for Linux is detected by reading `/proc/version` for
//! the presence of "microsoft" or "wsl" (case-insensitive). WSL-specific
//! error messages guide users to ensure Node.js is installed via nvm or
//! fnm inside WSL (not via the Windows-side Node).
use std::time::Duration;
@@ -61,6 +68,28 @@ fn resolve_npx(node_path: Option<&str>) -> &str {
}
}
/// Detect if we're running inside Windows Subsystem for Linux (WSL).
///
/// Checks `/proc/version` for the presence of "microsoft" or "wsl"
/// (case-insensitive). Returns `true` if WSL is detected.
fn is_wsl() -> bool {
std::fs::read_to_string("/proc/version")
.map(|content| {
let lower = content.to_lowercase();
lower.contains("microsoft") || lower.contains("wsl")
})
.unwrap_or(false)
}
/// Get a platform-aware example npx path for error messages.
fn example_npx_path() -> &'static str {
if cfg!(target_os = "macos") {
"\"/Users/yourname/.nvm/versions/node/v22/bin/npx\""
} else {
"\"/home/user/.nvm/versions/node/v22/bin/npx\""
}
}
/// Initialize the camoufox-browser daemon and return a ready client.
///
/// `node_path` — optional explicit path to `npx`/`npx.cmd` (from
@@ -93,7 +122,7 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
}
Ok(false) => {
let msg = format!(
"Daemon did not start within {}s — socket not found at {}",
"Daemon did not start within {}s \u{2014} socket not found at {}",
DAEMON_STARTUP_TIMEOUT_SECS,
socket_path.display()
);
@@ -109,18 +138,33 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
}
Err(e) => {
let npx_name = resolve_npx(node_path);
let msg = format!(
let mut msg = format!(
"Could not start camoufox daemon: {}\n\n\
Make sure Node.js v20+ and the package are installed:\n \
npm install -g camoufox-browser\n \
{} camoufox-browser@latest install\n\n\
If Node is installed via nvm/fnm/asdf, set browser.node_path\n\
in your config, e.g.:\n \
in your config:\n \
[browser]\n \
node_path = \"/home/user/.nvm/versions/node/v22/bin/npx\"\n\n\
node_path = {}\n\n\
Then try again.",
e, npx_name
e, npx_name, example_npx_path()
);
// Add WSL-specific hints
if is_wsl() {
msg.push_str(
"\n\n\u{26a0}\u{fe0f} WSL detected: Make sure Node.js is installed INSIDE WSL\n\
(e.g., via nvm or fnm in your WSL terminal). The Windows-side\n\
Node.js is not accessible from WSL by default.\n\
Test with: which npx\n\
If 'which npx' returns empty, run:\n \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n \
nvm install 22\n\
Then run 'gyandoot run' again.",
);
}
warn!("{}", msg);
BrowserInitResult::Unavailable(msg)
}
@@ -148,11 +192,27 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
.await
.map_err(|e| {
let hint = if node_path.is_none() {
"\n → Set [browser].node_path in your config, e.g.:\n [browser]\n node_path = \"/Users/yourname/.nvm/versions/node/v22/bin/npx\"".to_string()
format!(
"\n \u{2192} Set [browser].node_path in your config:\n [browser]\n node_path = {}",
example_npx_path()
)
} else {
format!("\n → npx at '{}' not found or not executable", npx)
format!("\n \u{2192} npx at '{}' not found or not executable", npx)
};
format!("{} failed: {}. Is Node.js installed?{}", npx, e, hint)
let wsl_hint = if is_wsl() {
"\n\n\u{26a0}\u{fe0f} WSL detected: npx was not found. Install Node.js inside WSL:\n \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\n \
nvm install 22"
.to_string()
} else {
String::new()
};
format!(
"{} failed: {}. Is Node.js installed?{}{}",
npx, e, hint, wsl_hint
)
})?;
if !output.status.success() {
@@ -169,17 +229,27 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result<
));
let hint = if node_path.is_none() {
"\n → Try setting [browser].node_path in your config:\n [browser]\n node_path = \"/Users/yourname/.nvm/versions/node/v22/bin/npx\"".to_string()
format!(
"\n \u{2192} Try setting [browser].node_path in your config:\n [browser]\n node_path = {}",
example_npx_path()
)
} else {
String::new()
};
let wsl_hint = if is_wsl() {
"".to_string()
} else {
String::new()
};
return Err(format!(
"camoufox-browser failed (exit: {}):\n {}\n → Full log saved to {}{}",
"camoufox-browser failed (exit: {}):\n {}\n \u{2192} Full log saved to {}{}{}",
output.status.code().unwrap_or(-1),
details,
log_file.display(),
hint,
wsl_hint,
));
}