Add .node-version file support

feature/lookup-installed
trivernis 2 years ago
parent 835a366caa
commit a634f2be47
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -11,6 +11,7 @@ path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-trait = "0.1.62"
clap = { version = "4.1.1", features = ["derive"] } clap = { version = "4.1.1", features = ["derive"] }
crossterm = "0.25.0" crossterm = "0.25.0"
dialoguer = "0.10.3" dialoguer = "0.10.3"

@ -32,7 +32,7 @@ async fn main() -> Result<()> {
args::Command::Version => { args::Command::Version => {
print_version(); print_version();
Ok(()) Ok(())
}, }
args::Command::Install(v) => install_version(v.version).await, args::Command::Install(v) => install_version(v.version).await,
args::Command::Default(v) => set_default_version(v.version).await, args::Command::Default(v) => set_default_version(v.version).await,
args::Command::Exec(args) => { args::Command::Exec(args) => {
@ -51,7 +51,8 @@ fn print_version() {
println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
} }
pub async fn install_version(version: NodeVersion) -> Result<()> { /// Installs a given node version
async fn install_version(version: NodeVersion) -> Result<()> {
if VERSION_FILE_PATH.exists() { if VERSION_FILE_PATH.exists() {
fs::remove_file(&*VERSION_FILE_PATH) fs::remove_file(&*VERSION_FILE_PATH)
.await .await
@ -59,14 +60,16 @@ pub async fn install_version(version: NodeVersion) -> Result<()> {
} }
let repo = get_repository().await?; let repo = get_repository().await?;
if repo.is_installed(&version)? && !Confirm::new() if repo.is_installed(&version)?
&& !Confirm::new()
.with_prompt(format!( .with_prompt(format!(
"The version {} is already installed. Reinstall?", "The version {} is already installed. Reinstall?",
version.to_string().bold() version.to_string().bold()
)) ))
.default(false) .default(false)
.interact() .interact()
.unwrap() { .unwrap()
{
return Ok(()); return Ok(());
} }
repo.install_version(&version).await?; repo.install_version(&version).await?;
@ -75,7 +78,8 @@ pub async fn install_version(version: NodeVersion) -> Result<()> {
Ok(()) Ok(())
} }
pub async fn set_default_version(version: NodeVersion) -> Result<()> { /// Sets a default system wide node version
async fn set_default_version(version: NodeVersion) -> Result<()> {
let mut mapper = get_mapper().await?; let mut mapper = get_mapper().await?;
if !mapper.repository().is_installed(&version)? if !mapper.repository().is_installed(&version)?
@ -96,8 +100,9 @@ pub async fn set_default_version(version: NodeVersion) -> Result<()> {
Ok(()) Ok(())
} }
/// Exectues a given command
#[inline] #[inline]
pub async fn exec(command: String, args: Vec<OsString>) -> Result<i32> { async fn exec(command: String, args: Vec<OsString>) -> Result<i32> {
let mapper = get_mapper().await?; let mapper = get_mapper().await?;
let active_version = mapper.active_version(); let active_version = mapper.active_version();
@ -109,7 +114,8 @@ pub async fn exec(command: String, args: Vec<OsString>) -> Result<i32> {
Ok(exit_status.code().unwrap_or(0)) Ok(exit_status.code().unwrap_or(0))
} }
pub async fn refresh() -> Result<()> { /// Refreshes the version cache and mapped binaries
async fn refresh() -> Result<()> {
get_mapper().await?.remap().await?; get_mapper().await?.remap().await?;
fs::remove_file(&*VERSION_FILE_PATH) fs::remove_file(&*VERSION_FILE_PATH)
.await .await
@ -119,7 +125,8 @@ pub async fn refresh() -> Result<()> {
Ok(()) Ok(())
} }
pub async fn list_versions() -> Result<()> { /// Lists all available node versions
async fn list_versions() -> Result<()> {
let mapper = get_mapper().await?; let mapper = get_mapper().await?;
let versions = mapper.repository().installed_versions().await?; let versions = mapper.repository().installed_versions().await?;
let active_version = mapper let active_version = mapper

@ -1,4 +1,4 @@
use std::{env, ffi::OsString, process::ExitStatus, str::FromStr}; use std::{ffi::OsString, process::ExitStatus};
use tokio::fs; use tokio::fs;
@ -8,12 +8,16 @@ use crate::{
repository::{NodeVersion, Repository}, repository::{NodeVersion, Repository},
}; };
use self::{mapped_command::MappedCommand, mapped_dir::map_node_bin, package_info::PackageInfo}; use self::{
mapped_command::MappedCommand,
mapped_dir::map_node_bin,
version_detection::{ParallelDetector, VersionDetector},
};
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
mod mapped_command; mod mapped_command;
mod mapped_dir; mod mapped_dir;
mod package_info; mod version_detection;
/// Responsible for mapping to node executables /// Responsible for mapping to node executables
/// and managing node versions /// and managing node versions
@ -76,19 +80,10 @@ impl Mapper {
} }
async fn get_version() -> Option<NodeVersion> { async fn get_version() -> Option<NodeVersion> {
if let Some(version) = PackageInfo::find() ParallelDetector::detect_version()
.await .await
.ok() .ok()
.and_then(|i| i) .and_then(|v| v)
.and_then(|i| i.engines)
.and_then(|e| e.node)
{
Some(NodeVersion::Req(version))
} else {
env::var("NODE_VERSION")
.ok()
.and_then(|v| NodeVersion::from_str(&v).ok())
}
} }
/// creates wrapper scripts for the current version /// creates wrapper scripts for the current version

@ -0,0 +1,19 @@
use std::str::FromStr;
use miette::{Context, IntoDiagnostic};
use crate::repository::NodeVersion;
use super::VersionDetector;
pub struct EnvDetector;
#[async_trait::async_trait]
impl VersionDetector for EnvDetector {
async fn detect_version() -> miette::Result<Option<crate::repository::NodeVersion>> {
std::env::var("NODE_VERSION")
.into_diagnostic()
.context("Reading version from environment")
.map(|v| NodeVersion::from_str(&v).ok())
}
}

@ -0,0 +1,38 @@
use async_trait::async_trait;
use futures::future;
use miette::Result;
mod env_detector;
mod package_json_detector;
mod version_file_detector;
use crate::repository::NodeVersion;
use self::{
env_detector::EnvDetector, package_json_detector::PackageJsonDetector,
version_file_detector::VersionFileDetector,
};
#[async_trait]
pub trait VersionDetector {
async fn detect_version() -> Result<Option<NodeVersion>>;
}
pub struct ParallelDetector;
#[async_trait]
impl VersionDetector for ParallelDetector {
async fn detect_version() -> Result<Option<NodeVersion>> {
let version = future::join_all(vec![
VersionFileDetector::detect_version(),
PackageJsonDetector::detect_version(),
EnvDetector::detect_version(),
])
.await
.into_iter()
.filter_map(Result::ok)
.find_map(|v| v);
Ok(version)
}
}

@ -6,7 +6,11 @@ use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use tokio::fs; use tokio::fs;
use crate::error::ParseJsonError; use crate::{error::ParseJsonError, repository::NodeVersion, utils::find_in_parents};
use super::VersionDetector;
pub struct PackageJsonDetector;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PackageInfo { pub struct PackageInfo {
@ -24,25 +28,25 @@ pub struct EngineInfo {
other: HashMap<String, Value>, other: HashMap<String, Value>,
} }
#[async_trait::async_trait]
impl VersionDetector for PackageJsonDetector {
async fn detect_version() -> Result<Option<crate::repository::NodeVersion>> {
Ok(PackageInfo::find()
.await?
.and_then(|p| p.engines)
.and_then(|e| e.node)
.map(NodeVersion::Req))
}
}
impl PackageInfo { impl PackageInfo {
pub async fn find() -> Result<Option<Self>> { pub async fn find() -> Result<Option<Self>> {
let mut dir = std::env::current_dir().into_diagnostic()?; let dir = std::env::current_dir().into_diagnostic()?;
let file_path = dir.join("package.json");
if file_path.exists() {
let info = Self::load(&file_path).await?;
if let Some(path) = find_in_parents(dir, "package.json") {
let info = Self::load(&path).await?;
Ok(Some(info)) Ok(Some(info))
} else { } else {
while let Some(parent) = dir.parent() {
dir = parent.to_owned();
let file_path = dir.join("package.json");
if file_path.exists() {
let info = Self::load(&file_path).await?;
return Ok(Some(info));
}
}
Ok(None) Ok(None)
} }
} }

@ -0,0 +1,27 @@
use std::str::FromStr;
use miette::{Context, IntoDiagnostic};
use tokio::fs;
use crate::{repository::NodeVersion, utils::find_in_parents};
use super::VersionDetector;
pub struct VersionFileDetector;
#[async_trait::async_trait]
impl VersionDetector for VersionFileDetector {
async fn detect_version() -> miette::Result<Option<crate::repository::NodeVersion>> {
let dir = std::env::current_dir().into_diagnostic()?;
if let Some(path) = find_in_parents(dir, ".node-version") {
let version_string = fs::read_to_string(path)
.await
.into_diagnostic()
.context("Reading version file.")?;
Ok(NodeVersion::from_str(&version_string).ok())
} else {
Ok(None)
}
}
}

@ -46,10 +46,12 @@ impl FromStr for NodeVersion {
"latest" => Self::Latest, "latest" => Self::Latest,
"lts" => Self::LatestLts, "lts" => Self::LatestLts,
_ => { _ => {
if let Ok(req) = VersionReq::parse(s) { let version_string = s.trim().trim_start_matches('v');
if let Ok(req) = VersionReq::parse(version_string) {
Self::Req(req) Self::Req(req)
} else { } else {
Self::Lts(s.to_lowercase()) Self::Lts(version_string.to_lowercase())
} }
} }
}; };

@ -1,4 +1,7 @@
use std::time::Duration; use std::{
path::{Path, PathBuf},
time::Duration,
};
use indicatif::{ProgressBar, ProgressStyle}; use indicatif::{ProgressBar, ProgressStyle};
@ -26,3 +29,27 @@ pub fn progress_spinner() -> ProgressBar {
pb.enable_steady_tick(Duration::from_millis(50)); pb.enable_steady_tick(Duration::from_millis(50));
pb pb
} }
pub fn find_in_parents<P: AsRef<Path>>(origin: PathBuf, name: P) -> Option<PathBuf> {
for part in dir_parts(origin) {
let file = part.join(&name);
if file.exists() {
return Some(file);
}
}
None
}
/// Returns a list of paths for the current dir up to the very top
pub fn dir_parts(path: PathBuf) -> Vec<PathBuf> {
let mut current: &Path = &path;
let mut parts = vec![path.to_owned()];
while let Some(parent) = current.parent() {
current = parent;
parts.push(parent.to_owned())
}
parts
}

Loading…
Cancel
Save