diff --git a/Cargo.toml b/Cargo.toml index 46d8851..a615a52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ clap = { version = "4.1.1", features = ["derive"] } crossterm = "0.25.0" dialoguer = "0.10.3" dirs = "4.0.0" +envmnt = "0.10.4" futures = "0.3.25" futures-util = "0.3.25" indicatif = "0.17.3" diff --git a/src/consts.rs b/src/consts.rs index 7ab03d4..7ddc2fc 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -2,6 +2,10 @@ use lazy_static::lazy_static; use std::path::PathBuf; pub const NODE_DIST_URL: &str = "https://nodejs.org/dist"; +#[cfg(not(windows))] +pub const SEARCH_PATH_SEPARATOR: &str = ":"; +#[cfg(windows)] +pub const SEARCH_PATH_SEPARATOR: &str = ";"; lazy_static! { pub static ref CFG_DIR: PathBuf = dirs::config_dir() diff --git a/src/main.rs b/src/main.rs index 69a55f8..50efcc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::process; +use std::{env, process}; use args::Args; use clap::Parser; @@ -36,7 +36,7 @@ async fn main() -> Result<()> { return Ok(()); } - let mut nenv = get_nenv(args.use_version.as_ref()).await?; + let mut nenv = get_nenv(args.use_version.clone()).await?; match args.command { args::Command::Install(v) => nenv.install(v.version).await, @@ -63,7 +63,7 @@ fn print_version() { println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); } -async fn get_nenv(version_override: Option<&NodeVersion>) -> Result { +async fn get_nenv(version_override: Option) -> Result { Nenv::init(version_override).await } diff --git a/src/mapper/mapped_command.rs b/src/mapper/mapped_command.rs index 7227e9a..f5a92ad 100644 --- a/src/mapper/mapped_command.rs +++ b/src/mapper/mapped_command.rs @@ -1,4 +1,5 @@ use std::{ + env, ffi::OsString, path::PathBuf, process::{ExitStatus, Stdio}, @@ -8,6 +9,7 @@ use crate::error::CommandNotFoundError; use miette::{Context, IntoDiagnostic, Result}; use tokio::process::Command; +#[derive(Debug)] pub struct MappedCommand { name: String, path: PathBuf, @@ -19,10 +21,11 @@ impl MappedCommand { Self { name, path, args } } - #[tracing::instrument(skip_all, level = "debug")] + #[tracing::instrument(level = "debug")] pub async fn run(mut self) -> Result { self.adjust_path()?; let exit_status = Command::new(self.path) + .envs(env::vars_os()) .args(self.args) .stdin(Stdio::inherit()) .stdout(Stdio::inherit()) diff --git a/src/mapper/mapped_dir.rs b/src/mapper/mapped_dir.rs index 185991d..ae02dbb 100644 --- a/src/mapper/mapped_dir.rs +++ b/src/mapper/mapped_dir.rs @@ -40,7 +40,7 @@ impl NodeApp { .context("Creating executable wrapper script") } - #[cfg(not(target_os = "windows"))] + #[cfg(not(windows))] async fn write_wrapper_script(&self, path: &Path) -> Result<(), io::Error> { fs::write(path, format!("#!/bin/sh\nnenv exec {} \"$@\"", self.name)).await?; let src_metadata = self.path.metadata()?; @@ -49,7 +49,7 @@ impl NodeApp { Ok(()) } - #[cfg(target_os = "windows")] + #[cfg(windows)] async fn write_wrapper_script(&self, path: &Path) -> Result<(), io::Error> { fs::write( path.with_extension("bat"), @@ -117,12 +117,12 @@ async fn get_applications(path: &Path) -> Result> { Ok(files) } -#[cfg(not(target_os = "windows"))] +#[cfg(not(windows))] fn exclude_path(_path: &Path) -> bool { false } -#[cfg(target_os = "windows")] +#[cfg(windows)] fn exclude_path(path: &Path) -> bool { let Some(extension) = path.extension() else { return true; diff --git a/src/mapper/mod.rs b/src/mapper/mod.rs index 4b30e61..32cf225 100644 --- a/src/mapper/mod.rs +++ b/src/mapper/mod.rs @@ -1,8 +1,12 @@ -use std::{ffi::OsString, process::ExitStatus}; +use std::{env, ffi::OsString, process::ExitStatus}; +use envmnt::ListOptions; use tokio::fs; -use crate::{consts::BIN_DIR, repository::node_path::NodePath}; +use crate::{ + consts::{BIN_DIR, SEARCH_PATH_SEPARATOR}, + repository::node_path::NodePath, +}; use self::{ mapped_command::MappedCommand, @@ -26,6 +30,7 @@ impl Mapper { /// Executes a mapped command with the given node environment #[tracing::instrument(level = "debug", skip(self))] pub async fn exec(&self, command: String, args: Vec) -> Result { + self.set_env(); let executable = self.node_path.bin().join(&command); let exit_status = MappedCommand::new(command, executable, args).run().await?; self.remap_additive().await?; @@ -60,4 +65,18 @@ impl Mapper { ) .await } + + fn set_env(&self) { + env::set_var( + "NODE_PATH", + self.node_path.node_modules().to_string_lossy().to_string(), + ); + let list_options = ListOptions { + separator: Some(SEARCH_PATH_SEPARATOR.to_string()), + ignore_empty: true, + }; + let mut path_env = envmnt::get_list_with_options("PATH", &list_options).unwrap_or_default(); + path_env.insert(0, self.node_path.bin().to_string_lossy().to_string()); + envmnt::set_list_with_options("PATH", &path_env, &list_options); + } } diff --git a/src/nenv.rs b/src/nenv.rs index e53bb8a..5c6ed74 100644 --- a/src/nenv.rs +++ b/src/nenv.rs @@ -22,13 +22,13 @@ pub struct Nenv { impl Nenv { #[tracing::instrument(level = "debug")] - pub async fn init(version_override: Option<&NodeVersion>) -> Result { + pub async fn init(version_override: Option) -> Result { let config = ConfigAccess::load().await?; let repo = Repository::init(config.clone()).await?; let default_version = { config.get().await.node.default_version.to_owned() }; let active_version = if let Some(version) = version_override { - version.to_owned() + version } else { Self::get_active_version().await.unwrap_or(default_version) }; @@ -265,7 +265,8 @@ impl Nenv { for (bin, cfg) in &self.config.get().await.bins { let path = self .repo - .get_version_path(&cfg.node_version)? + .get_version_path(&cfg.node_version) + .await? .ok_or_else(|| VersionError::not_installed(&cfg.node_version))?; binaries_with_path.push((bin.to_owned(), path)); } diff --git a/src/repository/node_path.rs b/src/repository/node_path.rs index 95b2254..24e7ebd 100644 --- a/src/repository/node_path.rs +++ b/src/repository/node_path.rs @@ -12,7 +12,15 @@ impl NodePath { #[cfg(not(target_os = "windows"))] pub fn bin(&self) -> PathBuf { - self.base.join("bin") + self.base.join("bin").canonicalize().unwrap() + } + + pub fn lib(&self) -> PathBuf { + self.base.join("lib").canonicalize().unwrap() + } + + pub fn node_modules(&self) -> PathBuf { + self.lib().join("node_modules") } #[cfg(target_os = "windows")]