Improve mapping and add refresh command for env

feature/lookup-installed
trivernis 1 year ago
parent 75e80ade99
commit 50a42df15b
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -18,6 +18,7 @@ color-eyre = "0.6.2"
crossterm = "0.25.0"
dialoguer = "0.10.3"
dirs = "4.0.0"
futures = "0.3.25"
futures-util = "0.3.25"
indicatif = "0.17.3"
lazy_static = "1.4.0"
@ -26,6 +27,7 @@ miette = "5.5.0"
reqwest = { version = "0.11.14", features = ["json", "stream"] }
semver = { version = "1.0.16", features = ["std", "serde"] }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"
set_env = "1.3.4"
tar = "0.4.38"
thiserror = "1.0.38"

@ -12,34 +12,46 @@ pub struct Args {
#[derive(Clone, Debug, Subcommand)]
pub enum Command {
/// Returns the nenv version
#[command(short_flag = 'v', aliases = &["--version"])]
Version,
/// Installs the given node version
#[command()]
Install(InstallArgs),
/// Sets the specified version as the global default
#[command()]
Use(UseArgs),
Default(DefaultArgs),
#[command(short_flag = 'v', aliases = &["--version"])]
Version,
/// Refreshes the node environment mappings and cache
#[command()]
Refresh,
/// Executes the given version specific node executable
#[command()]
Exec(ExecArgs),
}
#[derive(Clone, Debug, Parser)]
pub struct ExecArgs {
/// The command to execute
#[arg()]
pub command: String,
/// The arguments for the command
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub args: Vec<OsString>,
}
#[derive(Clone, Debug, Parser)]
pub struct InstallArgs {
/// the version to install
pub version: NodeVersion,
}
#[derive(Clone, Debug, Parser)]
pub struct UseArgs {
#[arg()]
pub struct DefaultArgs {
/// The version to set as default
pub version: NodeVersion,
}

@ -14,6 +14,7 @@ lazy_static! {
.unwrap_or_else(|| PathBuf::from(".cache"))
.join(PathBuf::from("nenv"));
pub static ref CFG_FILE_PATH: PathBuf = CFG_DIR.join("config.toml");
pub static ref VERSION_FILE_PATH: PathBuf = DATA_DIR.join("versions.json");
pub static ref BIN_DIR: PathBuf = DATA_DIR.join("bin");
pub static ref NODE_VERSIONS_DIR: PathBuf = DATA_DIR.join("versions");
pub static ref NODE_ARCHIVE_SUFFIX: String = format!("-{OS}-{ARCH}.{ARCHIVE_TYPE}");

@ -44,6 +44,8 @@ pub enum Error {
#[diagnostic_source]
MapperError,
),
#[error("Failed to work with json: {0}")]
Json(#[from] serde_json::Error),
#[error("IO Error: {0}")]
Io(#[from] io::Error),

@ -1,5 +1,6 @@
use std::ffi::OsString;
use consts::{DATA_DIR, VERSION_FILE_PATH};
use crossterm::style::Stylize;
use mapper::Mapper;
use repository::{config::Config, NodeVersion, Repository};
@ -12,8 +13,10 @@ mod utils;
mod web_api;
use dialoguer::Confirm;
use error::Result;
use tokio::fs;
pub async fn install_version(version: NodeVersion) -> Result<()> {
fs::remove_file(&*VERSION_FILE_PATH).await?;
let repo = get_repository().await?;
if repo.is_installed(&version).await? {
@ -32,7 +35,7 @@ pub async fn install_version(version: NodeVersion) -> Result<()> {
Ok(())
}
pub async fn use_version(version: NodeVersion) -> Result<()> {
pub async fn set_default_version(version: NodeVersion) -> Result<()> {
let mut mapper = get_mapper().await?;
if !mapper.repository().is_installed(&version).await?
@ -47,7 +50,7 @@ pub async fn use_version(version: NodeVersion) -> Result<()> {
mapper.repository().install_version(&version).await?;
}
mapper.use_version(&version).await?;
mapper.set_default_version(&version).await?;
println!("Now using {}", version.to_string().bold());
Ok(())
@ -65,6 +68,13 @@ pub async fn exec(command: String, args: Vec<OsString>) -> Result<i32> {
Ok(exit_status.code().unwrap_or(0))
}
pub async fn refresh() -> Result<()> {
get_mapper().await?.remap().await?;
fs::remove_file(&*VERSION_FILE_PATH).await?;
Ok(())
}
async fn get_repository() -> Result<Repository> {
Repository::init(Config::load().await?).await
}

@ -6,18 +6,20 @@ use clap::Parser;
mod args;
#[tokio::main(flavor = "current_thread")]
async fn main() {
async fn main() -> nenv::error::Result<()> {
color_eyre::install().unwrap();
let args: Args = Args::parse();
match args.commmand {
args::Command::Install(v) => nenv::install_version(v.version).await.unwrap(),
args::Command::Use(v) => nenv::use_version(v.version).await.unwrap(),
args::Command::Version => print_version(),
args::Command::Version => Ok(print_version()),
args::Command::Install(v) => nenv::install_version(v.version).await,
args::Command::Default(v) => nenv::set_default_version(v.version).await,
args::Command::Exec(args) => {
let exit_code = nenv::exec(args.command, args.args).await.unwrap();
let exit_code = nenv::exec(args.command, args.args).await?;
process::exit(exit_code);
}
};
args::Command::Refresh => nenv::refresh().await,
}
}
fn print_version() {

@ -11,43 +11,31 @@ use super::error::MapperResult;
struct NodeApp {
info: DirEntry,
name: String,
}
impl NodeApp {
pub fn new(info: DirEntry) -> Self {
Self { info }
}
let path = info.path();
let name = path.file_stem().unwrap();
let name = name.to_string_lossy().into_owned();
/// returns the name of the application
pub fn name(&self) -> String {
let name = self.info.file_name();
name.to_string_lossy().into_owned()
Self { info, name }
}
pub async fn unmap(&self) -> MapperResult<()> {
fs::remove_file(self.info.path()).await?;
Ok(())
pub fn name(&self) -> &String {
&self.name
}
/// creates wrappers to map this application
pub async fn map_executable(&self) -> MapperResult<()> {
let src_path = BIN_DIR.join(self.info.file_name());
let name = self.info.file_name();
let name = name.to_string_lossy();
self.write_wrapper_script(&name, &src_path).await
self.write_wrapper_script(&src_path).await
}
#[cfg(not(target_os = "windows"))]
async fn write_wrapper_script(&self, name: &str, path: &Path) -> MapperResult<()> {
fs::write(
path,
format!(
r#"#!/bin/sh
nenv exec {name} "$@""#
),
)
.await?;
async fn write_wrapper_script(&self, path: &Path) -> MapperResult<()> {
fs::write(path, format!("#!/bin/sh\nnenv exec {} \"$@\"", self.name)).await?;
let src_metadata = self.info.metadata().await?;
fs::set_permissions(&path, src_metadata.permissions()).await?;
@ -55,8 +43,13 @@ impl NodeApp {
}
#[cfg(target_os = "windows")]
async fn write_wrapper_script(&self, name: &str, path: &Path) -> MapperResult<()> {
fs::write(path, format!("nenv exec {name} %*")).await?;
async fn write_wrapper_script(&self, path: &Path) -> MapperResult<()> {
fs::write(
path.with_extension("bat"),
format!("nenv exec {} %*", self.name),
)
.await?;
let src_metadata = self.info.metadata().await?;
fs::set_permissions(&path, src_metadata.permissions()).await?;
Ok(())
@ -64,19 +57,17 @@ impl NodeApp {
}
pub async fn map_node_bin(node_path: NodePath) -> MapperResult<()> {
let applications = get_applications(&node_path.bin()).await?;
let mapped_applications = get_applications(&*BIN_DIR).await?;
let mut new_mapped = HashSet::new();
let mapped_app_names = get_applications(&*BIN_DIR)
.await?
.iter()
.map(NodeApp::name)
.cloned()
.collect::<HashSet<_>>();
for application in applications {
application.map_executable().await?;
new_mapped.insert(application.name());
}
for app in mapped_applications {
if !new_mapped.contains(&app.name()) {
app.unmap().await?;
}
}
let mut applications = get_applications(&node_path.bin()).await?;
applications.retain(|app| !mapped_app_names.contains(app.name()));
futures::future::join_all(applications.iter().map(NodeApp::map_executable)).await;
Ok(())
}

@ -1,6 +1,9 @@
use std::{env, ffi::OsString, process::ExitStatus, str::FromStr};
use tokio::fs;
use crate::{
consts::BIN_DIR,
error::LibResult,
repository::{NodeVersion, Repository},
};
@ -32,7 +35,7 @@ impl Mapper {
}
/// Sets the given version as the default one
pub async fn use_version(&mut self, version: &NodeVersion) -> LibResult<()> {
pub async fn set_default_version(&mut self, version: &NodeVersion) -> LibResult<()> {
self.repo
.config
.set_default_version(version.clone())
@ -60,10 +63,20 @@ impl Mapper {
.run()
.await
.map_err(MapperError::from)?;
self.map_active_version().await?;
Ok(exit_status)
}
/// Recreates all environment mappings
pub async fn remap(&self) -> LibResult<()> {
fs::remove_dir_all(&*BIN_DIR).await?;
fs::create_dir_all(&*BIN_DIR).await?;
self.map_active_version().await?;
Ok(())
}
fn get_version() -> Option<NodeVersion> {
env::var("NODE_VERSION")
.ok()

@ -90,12 +90,20 @@ impl Repository {
pub async fn init(config: Config) -> LibResult<Self> {
Self::create_folders().await?;
let web_api = WebApi::new(&config.dist_base_url);
let all_versions = web_api.get_versions().await?;
let versions = if let Some(v) = Versions::load().await {
v
} else {
let all_versions = web_api.get_versions().await?;
let v = Versions::new(all_versions);
v.save().await?;
v
};
Ok(Self {
config,
web_api,
versions: Versions::new(all_versions),
versions,
})
}

@ -1,15 +1,29 @@
use std::collections::HashMap;
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};
use tokio::fs;
use crate::web_api::VersionInfo;
use crate::{consts::VERSION_FILE_PATH, error::LibResult, web_api::VersionInfo};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Versions {
lts_versions: HashMap<String, VersionReq>,
versions: HashMap<Version, VersionInfo>,
}
impl Versions {
/// Loads the versions from the cached versions.json file
pub(crate) async fn load() -> Option<Self> {
if !VERSION_FILE_PATH.exists() {
return None;
}
let versions_string = fs::read_to_string(&*VERSION_FILE_PATH).await.ok()?;
let versions = serde_json::from_str(&versions_string).ok()?;
Some(versions)
}
/// creates a new instance to access version information
pub fn new(all_versions: Vec<VersionInfo>) -> Self {
let lts_versions = all_versions
@ -32,6 +46,13 @@ impl Versions {
}
}
pub(crate) async fn save(&self) -> LibResult<()> {
let json_string = serde_json::to_string(&self)?;
fs::write(&*VERSION_FILE_PATH, json_string).await?;
Ok(())
}
/// Returns the latest known node version
pub fn latest(&self) -> &VersionInfo {
let mut versions = self.versions.keys().collect::<Vec<_>>();

@ -1,10 +1,10 @@
use std::borrow::Cow;
use serde::{Deserialize, Deserializer};
use serde::{Deserialize, Deserializer, Serialize};
/// Represents a single nodejs version info entry
/// as retrieved from nodejs.org
#[derive(Clone, Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VersionInfo {
#[serde(deserialize_with = "deserialize_prefixed_version")]
pub version: semver::Version,
@ -19,7 +19,7 @@ pub struct VersionInfo {
pub files: Vec<String>,
}
#[derive(Clone, Debug, Deserialize)]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ModuleVersions {
pub v8: String,
pub npm: Option<String>,

Loading…
Cancel
Save