From ad1afd3173bd288a164f647a64e924e7b2fc338a Mon Sep 17 00:00:00 2001 From: trivernis Date: Sun, 22 Jan 2023 21:00:40 +0100 Subject: [PATCH] Move commands to nenv instance All commands are moved to the nenv instance. This also changes responsibilities slighly and introduces a shared config object --- Cargo.toml | 1 + src/args.rs | 5 +- src/config/mod.rs | 111 ++++++++++++ src/config/value.rs | 41 +++++ src/main.rs | 156 +++-------------- src/mapper/mapped_dir.rs | 2 +- src/mapper/mod.rs | 73 ++------ src/nenv.rs | 161 ++++++++++++++++++ src/repository/config.rs | 95 ----------- src/repository/mod.rs | 15 +- src/repository/node_path.rs | 1 + src/utils.rs | 9 + .../version_detection/env_detector.rs | 0 src/{mapper => }/version_detection/mod.rs | 0 .../package_json_detector.rs | 0 .../version_file_detector.rs | 0 16 files changed, 367 insertions(+), 303 deletions(-) create mode 100644 src/config/mod.rs create mode 100644 src/config/value.rs create mode 100644 src/nenv.rs delete mode 100644 src/repository/config.rs rename src/{mapper => }/version_detection/env_detector.rs (100%) rename src/{mapper => }/version_detection/mod.rs (100%) rename src/{mapper => }/version_detection/package_json_detector.rs (100%) rename src/{mapper => }/version_detection/version_file_detector.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 841b921..85c9dd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,5 +33,6 @@ tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs" toml = "0.5.11" tracing = "0.1.37" tracing-subscriber = "0.3.16" +xkcd_unreachable = "0.1.1" zip = "0.6.3" diff --git a/src/args.rs b/src/args.rs index 401cdda..716615e 100644 --- a/src/args.rs +++ b/src/args.rs @@ -7,7 +7,7 @@ use clap::{Parser, Subcommand}; #[clap(infer_subcommands = true)] pub struct Args { #[command(subcommand)] - pub commmand: Command, + pub command: Command, } #[derive(Clone, Debug, Subcommand)] @@ -24,7 +24,8 @@ pub enum Command { #[command()] Default(DefaultArgs), - /// Refreshes the node environment mappings and cache + /// Refreshes the node environment mappings and cache. + /// This will erase all binary mappings not relevant to the current node version. #[command()] Refresh, diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..d3bedeb --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,111 @@ +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use miette::Context; +use miette::{IntoDiagnostic, Result}; + +use tokio::fs; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +use crate::error::SerializeTomlError; +use crate::{ + consts::{CFG_DIR, CFG_FILE_PATH}, + error::ParseConfigError, +}; + +use self::value::Config; + +mod value; + +#[derive(Clone)] +pub struct ConfigAccess { + dirty: Arc, + config: Arc>, +} + +pub struct ModifyGuard<'a, T>(ConfigAccess, RwLockWriteGuard<'a, T>); + +impl<'a, T> Deref for ModifyGuard<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.1 + } +} + +impl<'a, T> DerefMut for ModifyGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.1 + } +} + +impl<'a, T> Drop for ModifyGuard<'a, T> { + fn drop(&mut self) { + self.0 + .dirty + .store(true, std::sync::atomic::Ordering::Relaxed); + } +} + +impl ConfigAccess { + /// Loads the config file from the default config path + pub async fn load() -> Result { + if !CFG_FILE_PATH.exists() { + if !CFG_DIR.exists() { + fs::create_dir_all(&*CFG_DIR) + .await + .into_diagnostic() + .context("creating config dir")?; + } + let cfg = Config::default(); + let access = Self::new(cfg); + access.save().await?; + + Ok(access) + } else { + let cfg_string = fs::read_to_string(&*CFG_FILE_PATH) + .await + .into_diagnostic() + .context("reading config file")?; + + let cfg = toml::from_str(&cfg_string) + .map_err(|e| ParseConfigError::new("config.toml", cfg_string, e))?; + + Ok(Self::new(cfg)) + } + } + + pub async fn get(&self) -> RwLockReadGuard { + if self.dirty.swap(false, std::sync::atomic::Ordering::Relaxed) { + self.save().await.expect("Failed so save config"); + } + self.config.read().await + } + + pub async fn get_mut(&self) -> ModifyGuard { + if self.dirty.swap(false, std::sync::atomic::Ordering::Relaxed) { + self.save().await.expect("Failed so save config"); + } + ModifyGuard(self.clone(), self.config.write().await) + } + + fn new(config: Config) -> Self { + Self { + dirty: Arc::new(AtomicBool::new(false)), + config: Arc::new(RwLock::new(config)), + } + } + + pub async fn save(&self) -> Result<()> { + fs::write( + &*CFG_FILE_PATH, + toml::to_string_pretty(&*self.config.read().await).map_err(SerializeTomlError::from)?, + ) + .await + .into_diagnostic() + .context("writing config file")?; + + Ok(()) + } +} diff --git a/src/config/value.rs b/src/config/value.rs new file mode 100644 index 0000000..bfd29b6 --- /dev/null +++ b/src/config/value.rs @@ -0,0 +1,41 @@ +use serde::{Deserialize, Serialize}; + +use crate::{consts::NODE_DIST_URL, repository::NodeVersion}; + +#[derive(Default, Serialize, Deserialize, Clone, Debug)] +pub struct Config { + /// Node execution related config + pub node: NodeConfig, + + /// Configuration for how to download node versions + pub download: DownloadConfig, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NodeConfig { + /// The default version if no version is specified + /// in the `package.json` file or `NODE_VERSION` environment variable + #[serde(with = "NodeVersion")] + pub default_version: NodeVersion, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct DownloadConfig { + pub dist_base_url: String, +} + +impl Default for NodeConfig { + fn default() -> Self { + Self { + default_version: NodeVersion::LatestLts, + } + } +} + +impl Default for DownloadConfig { + fn default() -> Self { + Self { + dist_base_url: String::from(NODE_DIST_URL), + } + } +} diff --git a/src/main.rs b/src/main.rs index d65bb74..42b9fbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,12 +2,8 @@ use std::process; use args::Args; use clap::Parser; -use std::ffi::OsString; -use consts::VERSION_FILE_PATH; -use crossterm::style::Stylize; -use mapper::Mapper; -use repository::{config::Config, NodeVersion, Repository}; +use nenv::Nenv; mod consts; pub mod error; @@ -15,35 +11,41 @@ pub mod mapper; pub mod repository; mod utils; mod web_api; -use dialoguer::Confirm; -use miette::{IntoDiagnostic, Result}; -use tokio::fs; - -use crate::error::VersionError; +use miette::Result; +use xkcd_unreachable::xkcd_unreachable; mod args; +mod config; +mod nenv; +mod version_detection; #[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { miette::set_panic_hook(); let args: Args = Args::parse(); - match args.commmand { - 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, + if let args::Command::Version = &args.command { + print_version(); + return Ok(()); + } + + let mut nenv = get_nenv().await?; + + match args.command { + args::Command::Install(v) => nenv.install(v.version).await, + args::Command::Default(v) => nenv.set_system_default(v.version).await, args::Command::Exec(args) => { - let exit_code = exec(args.command, args.args).await?; + let exit_code = nenv.exec(args.command, args.args).await?; process::exit(exit_code); } - args::Command::Refresh => refresh().await, - args::Command::ListVersions => list_versions().await, + args::Command::Refresh => nenv.refresh().await, + args::Command::ListVersions => nenv.list_versions().await, + _ => xkcd_unreachable!(), }?; + nenv.persist().await?; + Ok(()) } @@ -51,116 +53,6 @@ fn print_version() { println!("{} v{}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); } -/// Installs a given node version -async fn install_version(version: NodeVersion) -> Result<()> { - if VERSION_FILE_PATH.exists() { - fs::remove_file(&*VERSION_FILE_PATH) - .await - .into_diagnostic()?; - } - let repo = get_repository().await?; - - if repo.is_installed(&version)? - && !Confirm::new() - .with_prompt(format!( - "The version {} is already installed. Reinstall?", - version.to_string().bold() - )) - .default(false) - .interact() - .unwrap() - { - return Ok(()); - } - repo.install_version(&version).await?; - println!("Installed {}", version.to_string().bold()); - - Ok(()) -} - -/// 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)? - && Confirm::new() - .with_prompt(format!( - "The version {version} is not installed. Do you want to install it?" - )) - .default(false) - .interact() - .unwrap() - { - mapper.repository().install_version(&version).await?; - } - - mapper.set_default_version(&version).await?; - println!("Now using {}", version.to_string().bold()); - - Ok(()) -} - -/// Exectues a given command -#[inline] -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)? { - mapper.repository().install_version(active_version).await?; - } - let exit_status = mapper.exec(command, args).await?; - - Ok(exit_status.code().unwrap_or(0)) -} - -/// Refreshes the version cache and mapped binaries -async fn refresh() -> Result<()> { - get_mapper().await?.remap().await?; - fs::remove_file(&*VERSION_FILE_PATH) - .await - .into_diagnostic()?; - println!("Remapped binaries and cleared version cache"); - - Ok(()) -} - -/// 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 - .repository() - .lookup_version(mapper.active_version())?; - - println!("{}", "Installed versions:".bold()); - - for version in versions { - let info = mapper - .repository() - .all_versions() - .get(&version) - .ok_or_else(|| VersionError::unknown_version(version.to_string()))?; - let lts = info - .lts - .as_ref() - .map(|l| format!(" ({})", l.to_owned().green())) - .unwrap_or_default(); - - if version == active_version.version { - println!(" {}{} [current]", version.to_string().blue().bold(), lts) - } else { - println!(" {}{}", version.to_string().blue(), lts) - } - } - - Ok(()) -} - -async fn get_repository() -> Result { - Repository::init(Config::load().await?).await -} - -async fn get_mapper() -> Result { - Ok(Mapper::load(get_repository().await?).await) +async fn get_nenv() -> Result { + Nenv::init().await } diff --git a/src/mapper/mapped_dir.rs b/src/mapper/mapped_dir.rs index a283d85..d8acbae 100644 --- a/src/mapper/mapped_dir.rs +++ b/src/mapper/mapped_dir.rs @@ -55,7 +55,7 @@ impl NodeApp { } } -pub async fn map_node_bin(node_path: NodePath) -> Result<()> { +pub async fn map_node_bin(node_path: &NodePath) -> Result<()> { let mapped_app_names = get_applications(&BIN_DIR) .await? .iter() diff --git a/src/mapper/mod.rs b/src/mapper/mod.rs index e30487d..bffa3d0 100644 --- a/src/mapper/mod.rs +++ b/src/mapper/mod.rs @@ -2,70 +2,29 @@ use std::{ffi::OsString, process::ExitStatus}; use tokio::fs; -use crate::{ - consts::BIN_DIR, - error::VersionError, - repository::{NodeVersion, Repository}, -}; +use crate::{consts::BIN_DIR, repository::node_path::NodePath}; -use self::{ - mapped_command::MappedCommand, - mapped_dir::map_node_bin, - version_detection::{ParallelDetector, VersionDetector}, -}; +use self::{mapped_command::MappedCommand, mapped_dir::map_node_bin}; use miette::{IntoDiagnostic, Result}; mod mapped_command; mod mapped_dir; -mod version_detection; /// Responsible for mapping to node executables /// and managing node versions pub struct Mapper { - repo: Repository, - active_version: NodeVersion, + node_path: NodePath, } impl Mapper { - pub async fn load(repository: Repository) -> Self { - let version = Self::get_version() - .await - .unwrap_or_else(|| repository.config.node.default_version.to_owned()); - Self { - repo: repository, - active_version: version, - } + pub fn new(node_path: NodePath) -> Self { + Self { node_path } } - - pub fn repository(&self) -> &Repository { - &self.repo - } - - /// Sets the given version as the default one - pub async fn set_default_version(&mut self, version: &NodeVersion) -> Result<()> { - self.repo - .config - .set_default_version(version.clone()) - .await?; - self.active_version = version.clone(); - self.map_active_version().await?; - - Ok(()) - } - - pub fn active_version(&self) -> &NodeVersion { - &self.active_version - } - /// Executes a mapped command with the given node environment pub async fn exec(&self, command: String, args: Vec) -> Result { - let node_path = self - .repo - .get_version_path(&self.active_version)? - .ok_or_else(|| VersionError::not_installed(&self.active_version))?; - let executable = node_path.bin().join(&command); + let executable = self.node_path.bin().join(&command); let exit_status = MappedCommand::new(command, executable, args).run().await?; - self.map_active_version().await?; + self.remap_additive().await?; Ok(exit_status) } @@ -74,25 +33,13 @@ impl Mapper { pub async fn remap(&self) -> Result<()> { fs::remove_dir_all(&*BIN_DIR).await.into_diagnostic()?; fs::create_dir_all(&*BIN_DIR).await.into_diagnostic()?; - self.map_active_version().await?; + self.remap_additive().await?; Ok(()) } - async fn get_version() -> Option { - ParallelDetector::detect_version() - .await - .ok() - .and_then(|v| v) - } - - /// creates wrapper scripts for the current version - async fn map_active_version(&self) -> Result<()> { - let dir = self - .repo - .get_version_path(&self.active_version)? - .ok_or_else(|| VersionError::not_installed(self.active_version.to_string()))?; - map_node_bin(dir).await?; + pub async fn remap_additive(&self) -> Result<()> { + map_node_bin(&self.node_path).await?; Ok(()) } diff --git a/src/nenv.rs b/src/nenv.rs new file mode 100644 index 0000000..e5b80d0 --- /dev/null +++ b/src/nenv.rs @@ -0,0 +1,161 @@ +use std::ffi::OsString; + +use crate::{ + config::ConfigAccess, + consts::VERSION_FILE_PATH, + error::VersionError, + mapper::Mapper, + repository::{NodeVersion, Repository}, + utils::prompt, + version_detection::{self, VersionDetector}, +}; +use crossterm::style::Stylize; +use miette::{IntoDiagnostic, Result}; +use tokio::fs; + +pub struct Nenv { + config: ConfigAccess, + repo: Repository, + active_version: NodeVersion, +} + +impl Nenv { + pub async fn init() -> Result { + let config = ConfigAccess::load().await?; + let repo = Repository::init(config.clone()).await?; + let default_version = { config.get().await.node.default_version.to_owned() }; + let active_version = Self::get_active_version().await.unwrap_or(default_version); + + Ok(Self { + config, + repo, + active_version, + }) + } + + /// Installs the given node version. + /// Prompts if that version already exists + pub async fn install(&mut self, version: NodeVersion) -> Result<()> { + Self::clear_version_cache().await?; + + if self.repo.is_installed(&version)? + && !prompt( + false, + format!( + "The version {} is already installed. Reinstall?", + version.to_string().bold() + ), + ) + { + println!("Nothing changed."); + Ok(()) + } else { + self.repo.install_version(&version).await?; + self.active_version = version.to_owned(); + self.get_mapper()?.remap_additive().await?; + + println!("Installed {}", version.to_string().bold()); + Ok(()) + } + } + + /// Sets the system-wide default version + pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> { + self.active_version = version.to_owned(); + + if !self.repo.is_installed(&version)? { + if prompt( + false, + format!("The version {version} is not installed. Do you want to install it?"), + ) { + self.repo.install_version(&version).await?; + self.config.get_mut().await.node.default_version = version.to_owned(); + self.get_mapper()?.remap_additive().await?; + println!("Now using {}", version.to_string().bold()); + } + + Ok(()) + } else { + self.get_mapper()?.remap_additive().await?; + self.config.get_mut().await.node.default_version = version.to_owned(); + println!("Now using {}", version.to_string().bold()); + + Ok(()) + } + } + + /// Executes a given node executable for the currently active version + pub async fn exec(&self, command: String, args: Vec) -> Result { + if !self.repo.is_installed(&self.active_version)? { + self.repo.install_version(&self.active_version).await?; + } + let exit_status = self.get_mapper()?.exec(command, args).await?; + + Ok(exit_status.code().unwrap_or(0)) + } + + /// Persits all changes made that aren't written to the disk yet + pub async fn persist(&self) -> Result<()> { + self.config.save().await + } + + /// Clears the version cache and remaps all executables + pub async fn refresh(&self) -> Result<()> { + Self::clear_version_cache().await?; + self.get_mapper()?.remap().await + } + + /// Lists the currently installed versions + pub async fn list_versions(&self) -> Result<()> { + let versions = self.repo.installed_versions().await?; + let active_version = self.repo.lookup_version(&self.active_version)?; + + println!("{}", "Installed versions:".bold()); + + for version in versions { + let info = self + .repo + .all_versions() + .get(&version) + .ok_or_else(|| VersionError::unknown_version(version.to_string()))?; + let lts = info + .lts + .as_ref() + .map(|l| format!(" ({})", l.to_owned().green())) + .unwrap_or_default(); + + if version == active_version.version { + println!(" {}{} [current]", version.to_string().blue().bold(), lts) + } else { + println!(" {}{}", version.to_string().blue(), lts) + } + } + + Ok(()) + } + + async fn get_active_version() -> Option { + version_detection::ParallelDetector::detect_version() + .await + .ok() + .and_then(|v| v) + } + + async fn clear_version_cache() -> Result<()> { + if VERSION_FILE_PATH.exists() { + fs::remove_file(&*VERSION_FILE_PATH) + .await + .into_diagnostic()?; + } + + Ok(()) + } + + fn get_mapper(&self) -> Result { + let node_path = self + .repo + .get_version_path(&self.active_version)? + .ok_or_else(|| VersionError::not_installed(self.active_version.to_owned()))?; + Ok(Mapper::new(node_path)) + } +} diff --git a/src/repository/config.rs b/src/repository/config.rs deleted file mode 100644 index e638984..0000000 --- a/src/repository/config.rs +++ /dev/null @@ -1,95 +0,0 @@ -use miette::Context; -use miette::{IntoDiagnostic, Result}; -use serde::{Deserialize, Serialize}; -use tokio::fs; - -use crate::error::SerializeTomlError; -use crate::{ - consts::{CFG_DIR, CFG_FILE_PATH, NODE_DIST_URL}, - error::ParseConfigError, -}; - -use super::NodeVersion; - -#[derive(Default, Serialize, Deserialize, Clone, Debug)] -pub struct Config { - /// Node execution related config - pub node: NodeConfig, - - /// Configuration for how to download node versions - pub download: DownloadConfig, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct NodeConfig { - /// The default version if no version is specified - /// in the `package.json` file or `NODE_VERSION` environment variable - #[serde(with = "NodeVersion")] - pub default_version: NodeVersion, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct DownloadConfig { - pub dist_base_url: String, -} - -impl Default for NodeConfig { - fn default() -> Self { - Self { - default_version: NodeVersion::LatestLts, - } - } -} - -impl Default for DownloadConfig { - fn default() -> Self { - Self { - dist_base_url: String::from(NODE_DIST_URL), - } - } -} - -impl Config { - /// Loads the config file from the default config path - pub async fn load() -> Result { - if !CFG_FILE_PATH.exists() { - if !CFG_DIR.exists() { - fs::create_dir_all(&*CFG_DIR) - .await - .into_diagnostic() - .context("creating config dir")?; - } - let cfg = Config::default(); - cfg.save().await?; - - Ok(cfg) - } else { - let cfg_string = fs::read_to_string(&*CFG_FILE_PATH) - .await - .into_diagnostic() - .context("reading config file")?; - - let cfg = toml::from_str(&cfg_string) - .map_err(|e| ParseConfigError::new("config.toml", cfg_string, e))?; - - Ok(cfg) - } - } - - pub async fn save(&self) -> Result<()> { - fs::write( - &*CFG_FILE_PATH, - toml::to_string_pretty(&self).map_err(SerializeTomlError::from)?, - ) - .await - .into_diagnostic() - .context("writing config file")?; - - Ok(()) - } - - pub async fn set_default_version(&mut self, default_version: NodeVersion) -> Result<()> { - self.node.default_version = default_version; - self.save().await - } -} diff --git a/src/repository/mod.rs b/src/repository/mod.rs index 99e0f07..ba520b2 100644 --- a/src/repository/mod.rs +++ b/src/repository/mod.rs @@ -12,6 +12,7 @@ use tokio::{ }; use crate::{ + config::ConfigAccess, consts::{ ARCH, BIN_DIR, CACHE_DIR, CFG_DIR, DATA_DIR, NODE_ARCHIVE_SUFFIX, NODE_VERSIONS_DIR, OS, }, @@ -21,9 +22,8 @@ use crate::{ use miette::{IntoDiagnostic, Result}; -use self::{config::Config, node_path::NodePath, versions::Versions}; +use self::{node_path::NodePath, versions::Versions}; -pub mod config; pub(crate) mod extract; pub(crate) mod node_path; pub mod versions; @@ -86,21 +86,16 @@ impl fmt::Display for NodeVersion { pub struct Repository { versions: Versions, web_api: WebApi, - pub config: Config, } impl Repository { /// Initializes a new repository with the given confi - pub async fn init(config: Config) -> Result { + pub async fn init(config: ConfigAccess) -> Result { Self::create_folders().await?; - let web_api = WebApi::new(&config.download.dist_base_url); + let web_api = WebApi::new(&config.get().await.download.dist_base_url); let versions = load_versions(&web_api).await?; - Ok(Self { - config, - web_api, - versions, - }) + Ok(Self { web_api, versions }) } async fn create_folders() -> Result<()> { diff --git a/src/repository/node_path.rs b/src/repository/node_path.rs index 92411a4..95b2254 100644 --- a/src/repository/node_path.rs +++ b/src/repository/node_path.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +#[derive(Clone, Debug)] pub struct NodePath { base: PathBuf, } diff --git a/src/utils.rs b/src/utils.rs index 3a37480..0081b27 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,6 +3,7 @@ use std::{ time::Duration, }; +use dialoguer::Confirm; use indicatif::{ProgressBar, ProgressStyle}; pub fn progress_bar(total: u64) -> ProgressBar { @@ -30,6 +31,14 @@ pub fn progress_spinner() -> ProgressBar { pb } +pub fn prompt(default: bool, prompt: S) -> bool { + Confirm::new() + .with_prompt(prompt.to_string()) + .default(default) + .interact() + .unwrap() +} + pub fn find_in_parents>(origin: PathBuf, name: P) -> Option { for part in dir_parts(origin) { let file = part.join(&name); diff --git a/src/mapper/version_detection/env_detector.rs b/src/version_detection/env_detector.rs similarity index 100% rename from src/mapper/version_detection/env_detector.rs rename to src/version_detection/env_detector.rs diff --git a/src/mapper/version_detection/mod.rs b/src/version_detection/mod.rs similarity index 100% rename from src/mapper/version_detection/mod.rs rename to src/version_detection/mod.rs diff --git a/src/mapper/version_detection/package_json_detector.rs b/src/version_detection/package_json_detector.rs similarity index 100% rename from src/mapper/version_detection/package_json_detector.rs rename to src/version_detection/package_json_detector.rs diff --git a/src/mapper/version_detection/version_file_detector.rs b/src/version_detection/version_file_detector.rs similarity index 100% rename from src/mapper/version_detection/version_file_detector.rs rename to src/version_detection/version_file_detector.rs