mirror of https://github.com/Trivernis/nenv
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
8.8 KiB
Rust
273 lines
8.8 KiB
Rust
use std::{ffi::OsString, str::FromStr};
|
|
|
|
use crate::{
|
|
config::ConfigAccess,
|
|
consts::{BIN_DIR, CACHE_DIR, VERSION_FILE_PATH},
|
|
error::VersionError,
|
|
mapper::Mapper,
|
|
repository::{node_path::NodePath, NodeVersion, Repository},
|
|
utils::prompt,
|
|
version_detection::{self, VersionDetector},
|
|
};
|
|
use crossterm::style::Stylize;
|
|
use dialoguer::{theme::ColorfulTheme, Input, Select};
|
|
use miette::{Context, IntoDiagnostic, Result};
|
|
use tokio::fs;
|
|
|
|
pub struct Nenv {
|
|
config: ConfigAccess,
|
|
repo: Repository,
|
|
active_version: NodeVersion,
|
|
}
|
|
|
|
impl Nenv {
|
|
#[tracing::instrument(level = "debug")]
|
|
pub async fn init(version_override: Option<&NodeVersion>) -> Result<Self> {
|
|
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()
|
|
} else {
|
|
Self::get_active_version().await.unwrap_or(default_version)
|
|
};
|
|
|
|
Ok(Self {
|
|
config,
|
|
repo,
|
|
active_version,
|
|
})
|
|
}
|
|
|
|
/// Installs the given node version.
|
|
/// Prompts if that version already exists
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn install(&mut self, version: NodeVersion) -> Result<()> {
|
|
Self::clear_version_cache().await?;
|
|
|
|
if self.repo.is_installed(&version).await?
|
|
&& !prompt(
|
|
false,
|
|
format!(
|
|
"The version {} is already installed. Reinstall?",
|
|
version.to_string().bold()
|
|
),
|
|
)
|
|
{
|
|
println!("Nothing changed.");
|
|
Ok(())
|
|
} else {
|
|
self.repo.install_version(&version).await?;
|
|
self.active_version = version.to_owned();
|
|
let mapper = self.get_mapper().await?;
|
|
mapper.remap_additive().await?;
|
|
mapper
|
|
.map_bins(self.get_binaries_with_path().await?)
|
|
.await?;
|
|
|
|
println!("Installed {}", version.to_string().bold());
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn uninstall(&mut self, version: NodeVersion) -> Result<()> {
|
|
if prompt(
|
|
false,
|
|
format!(
|
|
"Do you really want to uninstall node {}?",
|
|
version.to_string().bold()
|
|
),
|
|
) {
|
|
self.repo.uninstall(&version).await?;
|
|
println!("Node {} has been removed.", version.to_string().bold())
|
|
} else {
|
|
println!("Nothing changed.");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Sets the system-wide default version
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> {
|
|
self.active_version = version.to_owned();
|
|
|
|
if !self.repo.is_installed(&version).await? {
|
|
if prompt(
|
|
false,
|
|
format!("The version {version} is not installed. Do you want to install it?"),
|
|
) {
|
|
self.repo.install_version(&version).await?;
|
|
self.config.get_mut().await.node.default_version = version.to_owned();
|
|
self.get_mapper().await?.remap_additive().await?;
|
|
println!("Now using {}", version.to_string().bold());
|
|
}
|
|
|
|
Ok(())
|
|
} else {
|
|
self.get_mapper().await?.remap_additive().await?;
|
|
self.config.get_mut().await.node.default_version = version.to_owned();
|
|
println!("Now using {}", version.to_string().bold());
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Executes a given node executable for the currently active version
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn exec(&mut self, command: String, args: Vec<OsString>) -> Result<i32> {
|
|
if let Some(cfg) = self.config.get().await.bins.get(&command) {
|
|
self.active_version = cfg.node_version.to_owned();
|
|
}
|
|
if !self.repo.is_installed(&self.active_version).await? {
|
|
self.repo.install_version(&self.active_version).await?;
|
|
}
|
|
let exit_status = self.get_mapper().await?.exec(command, args).await?;
|
|
|
|
Ok(exit_status.code().unwrap_or(0))
|
|
}
|
|
|
|
/// Clears the version cache and remaps all executables
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn remap(&mut self) -> Result<()> {
|
|
let mapper = self.get_mapper().await?;
|
|
mapper.remap().await?;
|
|
mapper.map_bins(self.get_binaries_with_path().await?).await
|
|
}
|
|
|
|
/// Lists the currently installed versions
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn list_versions(&mut self) -> Result<()> {
|
|
let versions = self.repo.installed_versions();
|
|
let active_version = self
|
|
.repo
|
|
.lookup_remote_version(&self.active_version)
|
|
.await?;
|
|
let active_version = active_version.version.into();
|
|
|
|
println!("{}", "Installed versions:".bold());
|
|
|
|
for version in versions {
|
|
let info = self
|
|
.repo
|
|
.all_versions()
|
|
.await?
|
|
.get(&version)
|
|
.ok_or_else(|| VersionError::unknown_version(version.to_string()))?;
|
|
let lts = info
|
|
.lts
|
|
.as_ref()
|
|
.map(|l| format!(" ({})", l.to_owned().green()))
|
|
.unwrap_or_default();
|
|
|
|
if version == active_version {
|
|
println!(" {}{} [current]", version.to_string().blue().bold(), lts)
|
|
} else {
|
|
println!(" {}{}", version.to_string().blue(), lts)
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Initializes nenv and prompts for a default version.
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn init_nenv(&mut self) -> Result<()> {
|
|
let items = vec!["latest", "lts", "custom"];
|
|
let selection = Select::with_theme(&ColorfulTheme::default())
|
|
.with_prompt("Select a default node version")
|
|
.items(&items)
|
|
.default(0)
|
|
.interact()
|
|
.into_diagnostic()?;
|
|
|
|
let version = if items[selection] == "custom" {
|
|
let version_string: String = Input::with_theme(&ColorfulTheme::default())
|
|
.with_prompt("Enter a version number: ")
|
|
.interact_text()
|
|
.into_diagnostic()?;
|
|
NodeVersion::from_str(&version_string).unwrap()
|
|
} else {
|
|
NodeVersion::from_str(items[selection]).unwrap()
|
|
};
|
|
|
|
self.repo.install_version(&version).await?;
|
|
|
|
println!("{}", "Initialized!".green());
|
|
println!(
|
|
"{}\n {}\n{}",
|
|
"Make sure to add".bold(),
|
|
BIN_DIR.to_string_lossy().yellow(),
|
|
"to your PATH environment variables.".bold()
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Clears the download cache
|
|
#[tracing::instrument(skip(self))]
|
|
pub async fn clear_cache(&self) -> Result<()> {
|
|
fs::remove_dir_all(&*CACHE_DIR)
|
|
.await
|
|
.into_diagnostic()
|
|
.context("Removing cache directory")?;
|
|
fs::create_dir_all(&*CACHE_DIR)
|
|
.await
|
|
.into_diagnostic()
|
|
.context("Creating cache directory")?;
|
|
println!("Cleared download cache.");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Persits all changes made that aren't written to the disk yet
|
|
#[tracing::instrument(level = "debug", skip(self))]
|
|
pub async fn persist(&self) -> Result<()> {
|
|
self.config.save().await
|
|
}
|
|
|
|
#[tracing::instrument(level = "debug")]
|
|
async fn get_active_version() -> Option<NodeVersion> {
|
|
version_detection::ParallelDetector::detect_version()
|
|
.await
|
|
.ok()
|
|
.and_then(|v| v)
|
|
}
|
|
|
|
#[tracing::instrument(level = "debug")]
|
|
async fn clear_version_cache() -> Result<()> {
|
|
if VERSION_FILE_PATH.exists() {
|
|
fs::remove_file(&*VERSION_FILE_PATH)
|
|
.await
|
|
.into_diagnostic()?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(level = "debug", skip(self))]
|
|
async fn get_mapper(&mut self) -> Result<Mapper> {
|
|
let node_path = self
|
|
.repo
|
|
.get_version_path(&self.active_version)?
|
|
.ok_or_else(|| VersionError::not_installed(self.active_version.to_owned()))?;
|
|
Ok(Mapper::new(node_path))
|
|
}
|
|
|
|
async fn get_binaries_with_path(&mut self) -> Result<Vec<(String, NodePath)>> {
|
|
let mut binaries_with_path = Vec::new();
|
|
|
|
for (bin, cfg) in &self.config.get().await.bins {
|
|
let path = self
|
|
.repo
|
|
.get_version_path(&cfg.node_version)?
|
|
.ok_or_else(|| VersionError::not_installed(&cfg.node_version))?;
|
|
binaries_with_path.push((bin.to_owned(), path));
|
|
}
|
|
|
|
Ok(binaries_with_path)
|
|
}
|
|
}
|