Add workflow and improve error handling

feature/lookup-installed
trivernis 2 years ago
parent ca8a4a3245
commit 14ba587c2c
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -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*

@ -1,6 +1,7 @@
use std::io; use std::io;
use miette::Diagnostic; use miette::Diagnostic;
use semver::VersionReq;
use thiserror::Error; use thiserror::Error;
use crate::{ use crate::{
@ -15,14 +16,15 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]
pub enum Error { pub enum Error {
#[error("Failed to call nodejs.com api: {0}")] #[error("Failed to call nodejs.com api.")]
Web( Web(
#[from] #[from]
#[source] #[source]
#[diagnostic_source] #[diagnostic_source]
ApiError, ApiError,
), ),
#[error("Failed to extract archive: {0}")]
#[error("The node archive could not be extracted")]
Extract( Extract(
#[from] #[from]
#[source] #[source]
@ -30,23 +32,47 @@ pub enum Error {
ExtractError, ExtractError,
), ),
#[error("Failed to load config file: {0}")] #[error("The config file could not be loaded")]
Config( Config(
#[from] #[from]
#[source] #[source]
#[diagnostic_source] #[diagnostic_source]
ConfigError, ConfigError,
), ),
#[error("Mapper failed: {0}")]
#[error("Mapping failed")]
Mapper( Mapper(
#[from] #[from]
#[source] #[source]
#[diagnostic_source] #[diagnostic_source]
MapperError, 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), Json(#[from] serde_json::Error),
#[error("IO Error: {0}")] #[error("Error during IO operation")]
Io(#[from] io::Error), 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),
}

@ -16,10 +16,12 @@ use error::Result;
use tokio::fs; use tokio::fs;
pub async fn install_version(version: NodeVersion) -> Result<()> { 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?; let repo = get_repository().await?;
if repo.is_installed(&version) { if repo.is_installed(&version)? {
if !Confirm::new() if !Confirm::new()
.with_prompt("The version {version} is already installed. Reinstall?") .with_prompt("The version {version} is already installed. Reinstall?")
.default(false) .default(false)
@ -38,7 +40,7 @@ pub async fn install_version(version: NodeVersion) -> Result<()> {
pub async fn set_default_version(version: NodeVersion) -> Result<()> { pub 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)?
&& Confirm::new() && Confirm::new()
.with_prompt(format!( .with_prompt(format!(
"The version {version} is not installed. Do you want to install it?" "The version {version} is not installed. Do you want to install it?"
@ -61,7 +63,7 @@ pub 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();
if !mapper.repository().is_installed(active_version) { if !mapper.repository().is_installed(active_version)? {
mapper.repository().install_version(&active_version).await?; mapper.repository().install_version(&active_version).await?;
} }
let exit_status = mapper.exec(command, args).await?; let exit_status = mapper.exec(command, args).await?;

@ -6,7 +6,7 @@ use clap::Parser;
mod args; mod args;
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> nenv::error::Result<()> { async fn main() -> color_eyre::eyre::Result<()> {
color_eyre::install().unwrap(); color_eyre::install().unwrap();
let args: Args = Args::parse(); let args: Args = Args::parse();
@ -20,7 +20,9 @@ async fn main() -> nenv::error::Result<()> {
process::exit(exit_code); process::exit(exit_code);
} }
args::Command::Refresh => nenv::refresh().await, args::Command::Refresh => nenv::refresh().await,
} }?;
Ok(())
} }
fn print_version() { fn print_version() {

@ -1,7 +1,5 @@
use std::{ use std::{
ffi::OsString, ffi::OsString,
io::{stderr, stdin, stdout},
os::fd::{AsRawFd, FromRawFd},
path::PathBuf, path::PathBuf,
process::{ExitStatus, Stdio}, process::{ExitStatus, Stdio},
}; };
@ -35,18 +33,11 @@ impl MappedCommand {
if !self.path.exists() { if !self.path.exists() {
return Err(CommandError::NotFound(self.path)); 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) let exit_status = Command::new(self.path)
.args(self.args) .args(self.args)
.stdin(stdin) .stdin(Stdio::inherit())
.stdout(stdout) .stdout(Stdio::inherit())
.stderr(stderr) .stderr(Stdio::inherit())
.spawn()? .spawn()?
.wait() .wait()
.await?; .await?;

@ -4,7 +4,7 @@ use tokio::fs;
use crate::{ use crate::{
consts::BIN_DIR, consts::BIN_DIR,
error::LibResult, error::{LibResult, VersionError},
repository::{NodeVersion, Repository}, repository::{NodeVersion, Repository},
}; };
@ -60,7 +60,7 @@ impl Mapper {
pub async fn exec(&self, command: String, args: Vec<OsString>) -> LibResult<ExitStatus> { pub async fn exec(&self, command: String, args: Vec<OsString>) -> LibResult<ExitStatus> {
let node_path = self let node_path = self
.repo .repo
.get_version_path(&self.active_version) .get_version_path(&self.active_version)?
.expect("version not installed"); .expect("version not installed");
let executable = node_path.bin().join(command); let executable = node_path.bin().join(command);
let exit_status = MappedCommand::new(executable, args) let exit_status = MappedCommand::new(executable, args)
@ -101,8 +101,8 @@ impl Mapper {
async fn map_active_version(&self) -> LibResult<()> { async fn map_active_version(&self) -> LibResult<()> {
let dir = self let dir = self
.repo .repo
.get_version_path(&self.active_version) .get_version_path(&self.active_version)?
.expect("missing version"); .ok_or_else(|| VersionError::NotInstalled(self.active_version.to_string()))?;
map_node_bin(dir).await?; map_node_bin(dir).await?;
Ok(()) Ok(())

@ -15,7 +15,7 @@ use crate::{
consts::{ consts::{
ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR, OS, 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}, web_api::{VersionInfo, WebApi},
}; };
@ -117,15 +117,15 @@ impl Repository {
} }
/// Returns the path for the given node version /// Returns the path for the given node version
pub fn get_version_path(&self, version: &NodeVersion) -> Option<NodePath> { pub fn get_version_path(&self, version: &NodeVersion) -> LibResult<Option<NodePath>> {
let info = self.parse_req(&version); let info = self.parse_req(&version)?;
let path = build_version_path(&info.version); let path = build_version_path(&info.version);
if path.exists() { Ok(if path.exists() {
Some(NodePath::new(path)) Some(NodePath::new(path))
} else { } else {
None None
} })
} }
/// Returns a list of installed versions /// Returns a list of installed versions
@ -143,15 +143,15 @@ impl Repository {
} }
/// Returns if the given version is installed /// Returns if the given version is installed
pub fn is_installed(&self, version: &NodeVersion) -> bool { pub fn is_installed(&self, version: &NodeVersion) -> LibResult<bool> {
let info = self.parse_req(version); let info = self.parse_req(version)?;
build_version_path(&info.version).exists() Ok(build_version_path(&info.version).exists())
} }
/// Installs a specified node version /// Installs a specified node version
pub async fn install_version(&self, version_req: &NodeVersion) -> LibResult<()> { 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?; let archive_path = self.download_version(&info.version).await?;
self.extract_archive(info, &archive_path)?; self.extract_archive(info, &archive_path)?;
@ -179,16 +179,21 @@ impl Repository {
Ok(()) Ok(())
} }
fn parse_req(&self, version_req: &NodeVersion) -> &VersionInfo { fn parse_req(&self, version_req: &NodeVersion) -> Result<&VersionInfo, VersionError> {
match version_req { let version = match version_req {
NodeVersion::Latest => self.versions.latest(), NodeVersion::Latest => self.versions.latest(),
NodeVersion::LatestLts => self.versions.latest_lts(), NodeVersion::LatestLts => self.versions.latest_lts(),
NodeVersion::Lts(lts) => self.versions.get_lts(&lts).expect("Version not found"), NodeVersion::Lts(lts) => self
.versions
.get_lts(&lts)
.ok_or_else(|| VersionError::UnkownVersion(lts.to_owned()))?,
NodeVersion::Req(req) => self NodeVersion::Req(req) => self
.versions .versions
.get_fulfilling(&req) .get_fulfilling(&req)
.expect("Version not found"), .ok_or_else(|| VersionError::Unfulfillable(req.to_owned()))?,
} };
Ok(version)
} }
} }

Loading…
Cancel
Save