You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

108 lines
3.7 KiB

use super::command::Command as Cmd;
use crate::choose_version_for_user_input::{
choose_version_for_user_input, Error as UserInputError,
};
use crate::config::FnmConfig;
use crate::outln;
use crate::user_version::UserVersion;
use crate::user_version_reader::UserVersionReader;
use colored::Colorize;
use std::process::{Command, Stdio};
use thiserror::Error;
#[derive(Debug, clap::Parser)]
#[clap(trailing_var_arg = true)]
pub struct Exec {
/// Either an explicit version, or a filename with the version written in it
#[clap(long = "using")]
version: Option<UserVersionReader>,
/// Deprecated. This is the default now.
#[clap(long = "using-file", hide = true)]
using_file: bool,
/// The command to run
arguments: Vec<String>,
}
impl Cmd for Exec {
type Error = Error;
fn apply(self, config: &FnmConfig) -> Result<(), Self::Error> {
if self.using_file {
outln!(
config,
Error,
"{} {} is deprecated. This is now the default.",
"warning:".yellow().bold(),
"--using-file".italic()
);
}
let (binary, arguments) = self
.arguments
.split_first()
.ok_or(Error::NoBinaryProvided)?;
let version = self
.version
.unwrap_or_else(|| {
let current_dir = std::env::current_dir().unwrap();
UserVersionReader::Path(current_dir)
})
.into_user_version(config)
.ok_or(Error::CantInferVersion)?;
let applicable_version = choose_version_for_user_input(&version, config)
.map_err(|source| Error::ApplicableVersionError { source })?
.ok_or(Error::VersionNotFound { version })?;
#[cfg(windows)]
let bin_path = applicable_version.path().to_path_buf();
#[cfg(unix)]
let bin_path = applicable_version.path().join("bin");
let path_env = {
let paths_env = std::env::var_os("PATH").ok_or(Error::CantReadPathVariable)?;
let mut paths: Vec<_> = std::env::split_paths(&paths_env).collect();
paths.insert(0, bin_path);
std::env::join_paths(paths)
.map_err(|source| Error::CantAddPathToEnvironment { source })?
};
let exit_status = Command::new(&binary)
.args(arguments)
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.env("PATH", path_env)
.spawn()
.expect("Can't spawn program")
.wait()
.expect("Failed to grab exit code");
let code = exit_status.code().ok_or(Error::CantReadProcessExitCode)?;
std::process::exit(code);
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("Can't read path environment variable")]
CantReadPathVariable,
#[error("Can't add path to environment variable: {}", source)]
CantAddPathToEnvironment { source: std::env::JoinPathsError },
#[error("Can't find version in dotfiles. Please provide a version manually to the command.")]
CantInferVersion,
#[error("Requested version {} is not currently installed", version)]
VersionNotFound { version: UserVersion },
#[error(transparent)]
ApplicableVersionError {
#[from]
source: UserInputError,
},
#[error("Can't read exit code from process.\nMaybe the process was killed using a signal?")]
CantReadProcessExitCode,
#[error("command not provided. Please provide a command to run as an argument, like {} or {}.\n{} {}", "node".italic(), "bash".italic(), "example:".yellow().bold(), "fnm exec --using=12 node --version".italic().yellow())]
NoBinaryProvided,
}