ADd exec command with remapping

feature/lookup-installed
trivernis 1 year ago
parent 1bd3e79cde
commit f517cd5469
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -29,7 +29,7 @@ serde = { version = "1.0.152", features = ["derive"] }
set_env = "1.3.4"
tar = "0.4.38"
thiserror = "1.0.38"
tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs", "time"] }
tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs", "time", "process"] }
toml = "0.5.11"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"

@ -1,8 +1,7 @@
use std::str::FromStr;
use std::ffi::OsString;
use clap::{Parser, Subcommand};
use nenv::repository::NodeVersion;
use semver::VersionReq;
#[derive(Clone, Debug, Parser)]
#[clap(infer_subcommands = true)]
@ -19,16 +18,23 @@ pub enum Command {
#[command()]
Use(UseArgs),
#[command()]
Default,
#[command(short_flag = 'v', aliases = &["--version"])]
Version,
#[command()]
Exec(ExecArgs),
}
#[derive(Clone, Debug, Parser)]
pub struct InstallArgs {
pub struct ExecArgs {
#[arg()]
pub command: String,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub args: Vec<OsString>,
}
#[derive(Clone, Debug, Parser)]
pub struct InstallArgs {
pub version: NodeVersion,
}

@ -10,7 +10,6 @@ use crate::{
};
pub(crate) type LibResult<T> = Result<T>;
pub(crate) type LibError = Error;
pub type Result<T> = std::result::Result<T, Error>;

@ -1,3 +1,5 @@
use std::ffi::OsString;
use crossterm::style::Stylize;
use mapper::Mapper;
use repository::{config::Config, NodeVersion, Repository};
@ -51,6 +53,18 @@ pub async fn use_version(version: NodeVersion) -> Result<()> {
Ok(())
}
pub async fn exec(command: String, args: Vec<OsString>) -> Result<i32> {
let mapper = get_mapper().await?;
let active_version = mapper.active_version();
if !mapper.repository().is_installed(active_version).await? {
mapper.repository().install_version(&active_version).await?;
}
let exit_status = mapper.exec(command, args).await?;
Ok(exit_status.code().unwrap_or(0))
}
async fn get_repository() -> Result<Repository> {
Repository::init(Config::load().await?).await
}

@ -1,8 +1,8 @@
use std::process;
use args::Args;
use clap::Parser;
use nenv::repository::NodeVersion;
mod args;
#[tokio::main(flavor = "current_thread")]
@ -12,7 +12,14 @@ async fn main() {
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::Default => todo!(),
args::Command::Version => todo!(),
args::Command::Version => print_version(),
args::Command::Exec(args) => {
let exit_code = nenv::exec(args.command, args.args).await.unwrap();
process::exit(exit_code);
}
};
}
fn print_version() {
println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
}

@ -3,6 +3,8 @@ use thiserror::Error;
use crate::repository::config::ConfigError;
use super::mapped_command::CommandError;
pub type MapperResult<T> = Result<T, MapperError>;
#[derive(Error, Diagnostic, Debug)]
@ -14,4 +16,7 @@ pub enum MapperError {
#[diagnostic_source]
ConfigError,
),
#[error("Failed to execute mapped command: {0}")]
Command(#[from] CommandError),
}

@ -0,0 +1,56 @@
use std::{
ffi::OsString,
io::{stderr, stdin, stdout},
os::fd::{AsRawFd, FromRawFd},
path::PathBuf,
process::{ExitStatus, Stdio},
};
use thiserror::Error;
use tokio::{io, process::Command};
pub struct MappedCommand {
path: PathBuf,
args: Vec<OsString>,
}
pub type CommandResult<T> = Result<T, CommandError>;
#[derive(Error, Debug)]
pub enum CommandError {
#[error(transparent)]
Io(#[from] io::Error),
#[error("The command {0:?} could not be found")]
NotFound(PathBuf),
}
impl MappedCommand {
pub fn new(path: PathBuf, args: Vec<OsString>) -> Self {
Self { path, args }
}
#[tracing::instrument(skip_all, level = "debug")]
pub async fn run(self) -> CommandResult<ExitStatus> {
if !self.path.exists() {
return Err(CommandError::NotFound(self.path));
}
let (stdin, stdout, stderr) = unsafe {
(
Stdio::from_raw_fd(stdin().as_raw_fd()),
Stdio::from_raw_fd(stdout().as_raw_fd()),
Stdio::from_raw_fd(stderr().as_raw_fd()),
)
};
let exit_status = Command::new(self.path)
.args(self.args)
.stdin(stdin)
.stdout(stdout)
.stderr(stderr)
.spawn()?
.wait()
.await?;
Ok(exit_status)
}
}

@ -1,10 +1,17 @@
use std::{env, str::FromStr};
use std::{env, ffi::OsString, process::ExitStatus, str::FromStr};
use crate::repository::{NodeVersion, Repository};
use crate::{
error::LibResult,
repository::{NodeVersion, Repository},
};
use self::error::MapperResult;
use self::{
error::{MapperError, MapperResult},
mapped_command::MappedCommand,
};
pub mod error;
mod mapped_command;
/// Responsible for mapping to node executables
/// and managing node versions
pub struct Mapper {
@ -41,6 +48,22 @@ impl Mapper {
&self.active_version
}
/// Executes a mapped command with the given node environment
pub async fn exec(&self, command: String, args: Vec<OsString>) -> LibResult<ExitStatus> {
let node_path = self
.repo
.get_version_path(&self.active_version)
.await?
.expect("version not installed");
let executable = node_path.bin().join(command);
let exit_status = MappedCommand::new(executable, args)
.run()
.await
.map_err(MapperError::from)?;
Ok(exit_status)
}
fn get_version() -> Option<NodeVersion> {
env::var("NODE_VERSION")
.ok()

@ -1,16 +1,13 @@
use std::{
fs::{self, File},
fs::File,
io::{self, BufReader},
path::Path,
};
use libflate::gzip::Decoder;
use miette::Diagnostic;
use tar::Archive;
use thiserror::Error;
use zip::ZipArchive;
use crate::utils::{progress_bar, progress_spinner};
use crate::utils::progress_spinner;
type ExtractResult<T> = Result<T, ExtractError>;
#[derive(Error, Debug, Diagnostic)]
@ -38,9 +35,13 @@ pub fn extract_file(src: &Path, dst: &Path) -> ExtractResult<()> {
Ok(())
}
#[cfg(not(target_os = "windows"))]
fn extract_tar_gz(src: &Path, dst: &Path) -> ExtractResult<()> {
let mut reader = BufReader::new(File::open(src)?);
let mut decoder = Decoder::new(reader)?;
use libflate::gzip::Decoder;
use tar::Archive;
let reader = BufReader::new(File::open(src)?);
let decoder = Decoder::new(reader)?;
let mut archive = Archive::new(decoder);
let pb = progress_spinner();
pb.set_message("Extracting tar.gz archive");
@ -51,7 +52,11 @@ fn extract_tar_gz(src: &Path, dst: &Path) -> ExtractResult<()> {
Ok(())
}
#[cfg(target_os = "windows")]
fn extract_zip(src: &Path, dst: &Path) -> ExtractResult<()> {
use crate::utils::progress_bar;
use std::fs;
use zip::ZipArchive;
let mut archive = ZipArchive::new(File::open(src)?)?;
let pb = progress_bar(archive.len() as u64);

@ -12,15 +12,18 @@ use tokio::{
};
use crate::{
consts::{BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR},
consts::{
ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR, OS,
},
error::LibResult,
web_api::{VersionInfo, WebApi},
};
use self::{config::Config, versions::Versions};
use self::{config::Config, node_paths::NodePath, versions::Versions};
pub mod config;
pub(crate) mod extract;
mod node_paths;
pub mod versions;
#[derive(Clone, Debug)]
@ -113,6 +116,27 @@ impl Repository {
Ok(())
}
/// Returns the path for the given node version
pub async fn get_version_path(&self, version: &NodeVersion) -> LibResult<Option<NodePath>> {
let info = self.parse_req(&version);
let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await?;
let mut path = None;
while let Some(entry) = iter.next_entry().await? {
if let Ok(version) = Version::parse(entry.file_name().to_string_lossy().as_ref()) {
if version == info.version {
path = Some(entry.path());
}
};
}
let Some(path) = path else {
return Ok(None);
};
let path = path.join(format!("node-v{}-{}-{}", info.version, OS, ARCH));
Ok(Some(NodePath::new(path)))
}
/// Returns a list of installed versions
pub async fn installed_versions(&self) -> LibResult<Vec<Version>> {
let mut versions = Vec::new();

@ -0,0 +1,15 @@
use std::path::PathBuf;
pub struct NodePath {
base: PathBuf,
}
impl NodePath {
pub fn new(base: PathBuf) -> Self {
Self { base }
}
pub fn bin(&self) -> PathBuf {
self.base.join("bin")
}
}

@ -1,6 +1,5 @@
use std::io;
use lazy_static::__Deref;
use miette::Diagnostic;
use thiserror::Error;

@ -1,13 +1,12 @@
use std::{
cmp::min,
fmt::{Debug, Display},
time::Duration,
};
use crate::{consts::NODE_ARCHIVE_SUFFIX, utils::progress_bar};
use self::error::{ApiError, ApiResult};
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::Client;
pub mod error;

Loading…
Cancel
Save