Add .node-version file support

feature/lookup-installed
trivernis 1 year 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
[dependencies]
async-trait = "0.1.62"
clap = { version = "4.1.1", features = ["derive"] }
crossterm = "0.25.0"
dialoguer = "0.10.3"

@ -32,7 +32,7 @@ async fn main() -> Result<()> {
args::Command::Version => {
print_version();
Ok(())
},
}
args::Command::Install(v) => install_version(v.version).await,
args::Command::Default(v) => set_default_version(v.version).await,
args::Command::Exec(args) => {
@ -51,7 +51,8 @@ fn print_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() {
fs::remove_file(&*VERSION_FILE_PATH)
.await
@ -59,14 +60,16 @@ pub async fn install_version(version: NodeVersion) -> Result<()> {
}
let repo = get_repository().await?;
if repo.is_installed(&version)? && !Confirm::new()
if repo.is_installed(&version)?
&& !Confirm::new()
.with_prompt(format!(
"The version {} is already installed. Reinstall?",
version.to_string().bold()
))
.default(false)
.interact()
.unwrap() {
.unwrap()
{
return Ok(());
}
repo.install_version(&version).await?;
@ -75,7 +78,8 @@ pub async fn install_version(version: NodeVersion) -> Result<()> {
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?;
if !mapper.repository().is_installed(&version)?
@ -96,8 +100,9 @@ pub async fn set_default_version(version: NodeVersion) -> Result<()> {
Ok(())
}
/// Exectues a given command
#[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 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))
}
pub async fn refresh() -> Result<()> {
/// Refreshes the version cache and mapped binaries
async fn refresh() -> Result<()> {
get_mapper().await?.remap().await?;
fs::remove_file(&*VERSION_FILE_PATH)
.await
@ -119,7 +125,8 @@ pub async fn refresh() -> Result<()> {
Ok(())
}
pub async fn list_versions() -> Result<()> {
/// Lists all available node versions
async fn list_versions() -> Result<()> {
let mapper = get_mapper().await?;
let versions = mapper.repository().installed_versions().await?;
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;
@ -8,12 +8,16 @@ use crate::{
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};
mod mapped_command;
mod mapped_dir;
mod package_info;
mod version_detection;
/// Responsible for mapping to node executables
/// and managing node versions
@ -76,19 +80,10 @@ impl Mapper {
}
async fn get_version() -> Option<NodeVersion> {
if let Some(version) = PackageInfo::find()
ParallelDetector::detect_version()
.await
.ok()
.and_then(|i| i)
.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())
}
.and_then(|v| v)
}
/// 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 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)]
pub struct PackageInfo {
@ -24,25 +28,25 @@ pub struct EngineInfo {
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 {
pub async fn find() -> Result<Option<Self>> {
let mut 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?;
let dir = std::env::current_dir().into_diagnostic()?;
if let Some(path) = find_in_parents(dir, "package.json") {
let info = Self::load(&path).await?;
Ok(Some(info))
} 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)
}
}

@ -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,
"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)
} 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};
@ -26,3 +29,27 @@ pub fn progress_spinner() -> ProgressBar {
pb.enable_steady_tick(Duration::from_millis(50));
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