From 14ba587c2c451ec9fd04095400b635cd84126ebe Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 21 Jan 2023 20:22:23 +0100 Subject: [PATCH] Add workflow and improve error handling --- .github/workflows/release.yml | 52 +++++++++++++++++++++++++++++++++++ src/error.rs | 38 +++++++++++++++++++++---- src/lib.rs | 10 ++++--- src/main.rs | 6 ++-- src/mapper/mapped_command.rs | 15 ++-------- src/mapper/mod.rs | 8 +++--- src/repository/mod.rs | 33 ++++++++++++---------- 7 files changed, 120 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fbb5440 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,52 @@ +name: "Build and Release" +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Set up toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: Set up MinGW + uses: egor-tensin/setup-mingw@v1 + with: + platform: x64 + - name: Cache cargo builds + uses: actions/cache@v2 + with: + path: | + target + ~/.cargo/ + key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + - name: Build Release + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: build + args: --release --all-features -Zmultitarget --target x86_64-unknown-linux-gnu --target x86_64-pc-windows-gnu + - name: Move binaries + run: mv target/x86_64-unknown-linux-gnu/release/nenv target/nenv-linux-x86_64 && mv target/x86_64-pc-windows-gnu/release/nenv.exe target/nenv-windows-x86_64.exe + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: nenv + path: target/nenv* + - name: publish release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false + files: | + LICENSE + target/nenv* diff --git a/src/error.rs b/src/error.rs index 9d9b196..1457385 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,7 @@ use std::io; use miette::Diagnostic; +use semver::VersionReq; use thiserror::Error; use crate::{ @@ -15,14 +16,15 @@ pub type Result = std::result::Result; #[derive(Debug, Error, Diagnostic)] pub enum Error { - #[error("Failed to call nodejs.com api: {0}")] + #[error("Failed to call nodejs.com api.")] Web( #[from] #[source] #[diagnostic_source] ApiError, ), - #[error("Failed to extract archive: {0}")] + + #[error("The node archive could not be extracted")] Extract( #[from] #[source] @@ -30,23 +32,47 @@ pub enum Error { ExtractError, ), - #[error("Failed to load config file: {0}")] + #[error("The config file could not be loaded")] Config( #[from] #[source] #[diagnostic_source] ConfigError, ), - #[error("Mapper failed: {0}")] + + #[error("Mapping failed")] Mapper( #[from] #[source] #[diagnostic_source] MapperError, ), - #[error("Failed to work with json: {0}")] + + #[error("The passed is invalid")] + Version( + #[from] + #[diagnostic_source] + VersionError, + ), + + #[error("Failed to work with json")] Json(#[from] serde_json::Error), - #[error("IO Error: {0}")] + #[error("Error during IO operation")] Io(#[from] io::Error), } + +#[derive(Debug, Error, Diagnostic)] +pub enum VersionError { + #[error("Invalid version string `{0}`")] + ParseVersion(#[source_code] String), + + #[error("Unknown Version `{0}`")] + UnkownVersion(#[source_code] String), + + #[error("The version `{0}` is not installed")] + NotInstalled(#[source_code] String), + + #[error("The version requirement `{0}` cannot be fulfilled")] + Unfulfillable(VersionReq), +} diff --git a/src/lib.rs b/src/lib.rs index 41ac881..122f868 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,12 @@ use error::Result; use tokio::fs; pub async fn install_version(version: NodeVersion) -> Result<()> { - fs::remove_file(&*VERSION_FILE_PATH).await?; + if VERSION_FILE_PATH.exists() { + fs::remove_file(&*VERSION_FILE_PATH).await?; + } let repo = get_repository().await?; - if repo.is_installed(&version) { + if repo.is_installed(&version)? { if !Confirm::new() .with_prompt("The version {version} is already installed. Reinstall?") .default(false) @@ -38,7 +40,7 @@ pub async fn install_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) + if !mapper.repository().is_installed(&version)? && Confirm::new() .with_prompt(format!( "The version {version} is not installed. Do you want to install it?" @@ -61,7 +63,7 @@ pub async fn exec(command: String, args: Vec) -> Result { let mapper = get_mapper().await?; let active_version = mapper.active_version(); - if !mapper.repository().is_installed(active_version) { + if !mapper.repository().is_installed(active_version)? { mapper.repository().install_version(&active_version).await?; } let exit_status = mapper.exec(command, args).await?; diff --git a/src/main.rs b/src/main.rs index c4791d3..5256f8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use clap::Parser; mod args; #[tokio::main(flavor = "current_thread")] -async fn main() -> nenv::error::Result<()> { +async fn main() -> color_eyre::eyre::Result<()> { color_eyre::install().unwrap(); let args: Args = Args::parse(); @@ -20,7 +20,9 @@ async fn main() -> nenv::error::Result<()> { process::exit(exit_code); } args::Command::Refresh => nenv::refresh().await, - } + }?; + + Ok(()) } fn print_version() { diff --git a/src/mapper/mapped_command.rs b/src/mapper/mapped_command.rs index 12e0281..5bb6e9a 100644 --- a/src/mapper/mapped_command.rs +++ b/src/mapper/mapped_command.rs @@ -1,7 +1,5 @@ use std::{ ffi::OsString, - io::{stderr, stdin, stdout}, - os::fd::{AsRawFd, FromRawFd}, path::PathBuf, process::{ExitStatus, Stdio}, }; @@ -35,18 +33,11 @@ impl MappedCommand { 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) + .stdin(Stdio::inherit()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) .spawn()? .wait() .await?; diff --git a/src/mapper/mod.rs b/src/mapper/mod.rs index 82eeb16..3f03271 100644 --- a/src/mapper/mod.rs +++ b/src/mapper/mod.rs @@ -4,7 +4,7 @@ use tokio::fs; use crate::{ consts::BIN_DIR, - error::LibResult, + error::{LibResult, VersionError}, repository::{NodeVersion, Repository}, }; @@ -60,7 +60,7 @@ impl Mapper { pub async fn exec(&self, command: String, args: Vec) -> LibResult { let node_path = self .repo - .get_version_path(&self.active_version) + .get_version_path(&self.active_version)? .expect("version not installed"); let executable = node_path.bin().join(command); let exit_status = MappedCommand::new(executable, args) @@ -101,8 +101,8 @@ impl Mapper { async fn map_active_version(&self) -> LibResult<()> { let dir = self .repo - .get_version_path(&self.active_version) - .expect("missing version"); + .get_version_path(&self.active_version)? + .ok_or_else(|| VersionError::NotInstalled(self.active_version.to_string()))?; map_node_bin(dir).await?; Ok(()) diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 2fda370..a685d74 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -15,7 +15,7 @@ use crate::{ consts::{ ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR, OS, }, - error::LibResult, + error::{LibResult, VersionError}, web_api::{VersionInfo, WebApi}, }; @@ -117,15 +117,15 @@ impl Repository { } /// Returns the path for the given node version - pub fn get_version_path(&self, version: &NodeVersion) -> Option { - let info = self.parse_req(&version); + pub fn get_version_path(&self, version: &NodeVersion) -> LibResult> { + let info = self.parse_req(&version)?; let path = build_version_path(&info.version); - if path.exists() { + Ok(if path.exists() { Some(NodePath::new(path)) } else { None - } + }) } /// Returns a list of installed versions @@ -143,15 +143,15 @@ impl Repository { } /// Returns if the given version is installed - pub fn is_installed(&self, version: &NodeVersion) -> bool { - let info = self.parse_req(version); + pub fn is_installed(&self, version: &NodeVersion) -> LibResult { + let info = self.parse_req(version)?; - build_version_path(&info.version).exists() + Ok(build_version_path(&info.version).exists()) } /// Installs a specified node version pub async fn install_version(&self, version_req: &NodeVersion) -> LibResult<()> { - let info = self.parse_req(&version_req); + let info = self.parse_req(&version_req)?; let archive_path = self.download_version(&info.version).await?; self.extract_archive(info, &archive_path)?; @@ -179,16 +179,21 @@ impl Repository { Ok(()) } - fn parse_req(&self, version_req: &NodeVersion) -> &VersionInfo { - match version_req { + fn parse_req(&self, version_req: &NodeVersion) -> Result<&VersionInfo, VersionError> { + let version = match version_req { NodeVersion::Latest => self.versions.latest(), NodeVersion::LatestLts => self.versions.latest_lts(), - NodeVersion::Lts(lts) => self.versions.get_lts(<s).expect("Version not found"), + NodeVersion::Lts(lts) => self + .versions + .get_lts(<s) + .ok_or_else(|| VersionError::UnkownVersion(lts.to_owned()))?, NodeVersion::Req(req) => self .versions .get_fulfilling(&req) - .expect("Version not found"), - } + .ok_or_else(|| VersionError::Unfulfillable(req.to_owned()))?, + }; + + Ok(version) } }