Improve the version cache to be way faster and smaller

feature/lookup-installed
trivernis 2 years ago
parent 9a7c91620e
commit 309189cb88
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -12,7 +12,9 @@ path = "src/main.rs"
[dependencies] [dependencies]
async-trait = "0.1.62" async-trait = "0.1.62"
bincode = "1.3.3"
clap = { version = "4.1.1", features = ["derive"] } clap = { version = "4.1.1", features = ["derive"] }
color-eyre = "0.6.2"
crossterm = "0.25.0" crossterm = "0.25.0"
dialoguer = "0.10.3" dialoguer = "0.10.3"
dirs = "4.0.0" dirs = "4.0.0"
@ -32,7 +34,7 @@ thiserror = "1.0.38"
tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs", "time", "process"] } tokio = { version = "1.24.2", features = ["rt", "macros", "tracing", "net", "fs", "time", "process"] }
toml = "0.5.11" toml = "0.5.11"
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = "0.3.16" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
xkcd_unreachable = "0.1.1" xkcd_unreachable = "0.1.1"
zip = "0.6.3" zip = "0.6.3"

@ -6,6 +6,10 @@ use clap::{Parser, Subcommand};
#[derive(Clone, Debug, Parser)] #[derive(Clone, Debug, Parser)]
#[clap(infer_subcommands = true)] #[clap(infer_subcommands = true)]
pub struct Args { pub struct Args {
/// Prints verbose logs
#[arg(long)]
pub verbose: bool,
#[command(subcommand)] #[command(subcommand)]
pub command: Command, pub command: Command,
} }

@ -14,7 +14,7 @@ lazy_static! {
.unwrap_or_else(|| PathBuf::from(".cache")) .unwrap_or_else(|| PathBuf::from(".cache"))
.join(PathBuf::from("nenv")); .join(PathBuf::from("nenv"));
pub static ref CFG_FILE_PATH: PathBuf = CFG_DIR.join("config.toml"); pub static ref CFG_FILE_PATH: PathBuf = CFG_DIR.join("config.toml");
pub static ref VERSION_FILE_PATH: PathBuf = DATA_DIR.join("versions.json"); pub static ref VERSION_FILE_PATH: PathBuf = DATA_DIR.join("versions.cache");
pub static ref BIN_DIR: PathBuf = DATA_DIR.join("bin"); pub static ref BIN_DIR: PathBuf = DATA_DIR.join("bin");
pub static ref NODE_VERSIONS_DIR: PathBuf = DATA_DIR.join("versions"); pub static ref NODE_VERSIONS_DIR: PathBuf = DATA_DIR.join("versions");
pub static ref NODE_ARCHIVE_SUFFIX: String = format!("-{OS}-{ARCH}.{ARCHIVE_TYPE}"); pub static ref NODE_ARCHIVE_SUFFIX: String = format!("-{OS}-{ARCH}.{ARCHIVE_TYPE}");

@ -70,11 +70,11 @@ pub struct ParseJsonError {
} }
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]
#[diagnostic(code(nenv::json::serialize))] #[diagnostic(code(nenv::bincode::serialize))]
#[error("failed to serialize value to json string")] #[error("failed to serialize value to bincode")]
pub struct SerializeJsonError { pub struct SerializeBincodeError {
#[from] #[from]
caused_by: serde_json::Error, caused_by: bincode::Error,
} }
#[derive(Debug, Error, Diagnostic)] #[derive(Debug, Error, Diagnostic)]

@ -12,6 +12,8 @@ pub mod repository;
mod utils; mod utils;
mod web_api; mod web_api;
use miette::Result; use miette::Result;
use tracing::metadata::LevelFilter;
use tracing_subscriber::fmt::format::FmtSpan;
use xkcd_unreachable::xkcd_unreachable; use xkcd_unreachable::xkcd_unreachable;
mod args; mod args;
@ -24,6 +26,10 @@ async fn main() -> Result<()> {
miette::set_panic_hook(); miette::set_panic_hook();
let args: Args = Args::parse(); let args: Args = Args::parse();
if args.verbose {
init_tracing();
}
if let args::Command::Version = &args.command { if let args::Command::Version = &args.command {
print_version(); print_version();
return Ok(()); return Ok(());
@ -56,3 +62,12 @@ fn print_version() {
async fn get_nenv() -> Result<Nenv> { async fn get_nenv() -> Result<Nenv> {
Nenv::init().await Nenv::init().await
} }
fn init_tracing() {
tracing_subscriber::fmt::SubscriberBuilder::default()
.with_max_level(LevelFilter::DEBUG)
.with_writer(std::io::stderr)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.compact()
.init();
}

@ -21,6 +21,7 @@ impl Mapper {
Self { node_path } Self { node_path }
} }
/// Executes a mapped command with the given node environment /// Executes a mapped command with the given node environment
#[tracing::instrument(level = "debug", skip(self))]
pub async fn exec(&self, command: String, args: Vec<OsString>) -> Result<ExitStatus> { pub async fn exec(&self, command: String, args: Vec<OsString>) -> Result<ExitStatus> {
let executable = self.node_path.bin().join(&command); let executable = self.node_path.bin().join(&command);
let exit_status = MappedCommand::new(command, executable, args).run().await?; let exit_status = MappedCommand::new(command, executable, args).run().await?;
@ -30,6 +31,7 @@ impl Mapper {
} }
/// Recreates all environment mappings /// Recreates all environment mappings
#[tracing::instrument(level = "debug", skip(self))]
pub async fn remap(&self) -> Result<()> { pub async fn remap(&self) -> Result<()> {
fs::remove_dir_all(&*BIN_DIR).await.into_diagnostic()?; fs::remove_dir_all(&*BIN_DIR).await.into_diagnostic()?;
fs::create_dir_all(&*BIN_DIR).await.into_diagnostic()?; fs::create_dir_all(&*BIN_DIR).await.into_diagnostic()?;
@ -38,6 +40,7 @@ impl Mapper {
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self))]
pub async fn remap_additive(&self) -> Result<()> { pub async fn remap_additive(&self) -> Result<()> {
map_node_bin(&self.node_path).await?; map_node_bin(&self.node_path).await?;

@ -20,6 +20,7 @@ pub struct Nenv {
} }
impl Nenv { impl Nenv {
#[tracing::instrument(level = "debug")]
pub async fn init() -> Result<Self> { pub async fn init() -> Result<Self> {
let config = ConfigAccess::load().await?; let config = ConfigAccess::load().await?;
let repo = Repository::init(config.clone()).await?; let repo = Repository::init(config.clone()).await?;
@ -35,6 +36,7 @@ impl Nenv {
/// Installs the given node version. /// Installs the given node version.
/// Prompts if that version already exists /// Prompts if that version already exists
#[tracing::instrument(skip(self))]
pub async fn install(&mut self, version: NodeVersion) -> Result<()> { pub async fn install(&mut self, version: NodeVersion) -> Result<()> {
Self::clear_version_cache().await?; Self::clear_version_cache().await?;
@ -60,6 +62,7 @@ impl Nenv {
} }
/// Sets the system-wide default version /// Sets the system-wide default version
#[tracing::instrument(skip(self))]
pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> { pub async fn set_system_default(&mut self, version: NodeVersion) -> Result<()> {
self.active_version = version.to_owned(); self.active_version = version.to_owned();
@ -85,6 +88,7 @@ impl Nenv {
} }
/// Executes a given node executable for the currently active version /// Executes a given node executable for the currently active version
#[tracing::instrument(skip(self))]
pub async fn exec(&self, command: String, args: Vec<OsString>) -> Result<i32> { pub async fn exec(&self, command: String, args: Vec<OsString>) -> Result<i32> {
if !self.repo.is_installed(&self.active_version)? { if !self.repo.is_installed(&self.active_version)? {
self.repo.install_version(&self.active_version).await?; self.repo.install_version(&self.active_version).await?;
@ -94,18 +98,15 @@ impl Nenv {
Ok(exit_status.code().unwrap_or(0)) 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 /// Clears the version cache and remaps all executables
#[tracing::instrument(skip(self))]
pub async fn refresh(&self) -> Result<()> { pub async fn refresh(&self) -> Result<()> {
Self::clear_version_cache().await?; Self::clear_version_cache().await?;
self.get_mapper()?.remap().await self.get_mapper()?.remap().await
} }
/// Lists the currently installed versions /// Lists the currently installed versions
#[tracing::instrument(skip(self))]
pub async fn list_versions(&self) -> Result<()> { pub async fn list_versions(&self) -> Result<()> {
let versions = self.repo.installed_versions().await?; let versions = self.repo.installed_versions().await?;
let active_version = self.repo.lookup_version(&self.active_version)?; let active_version = self.repo.lookup_version(&self.active_version)?;
@ -134,6 +135,13 @@ impl Nenv {
Ok(()) Ok(())
} }
/// Persits all changes made that aren't written to the disk yet
#[tracing::instrument(level = "debug", skip(self))]
pub async fn persist(&self) -> Result<()> {
self.config.save().await
}
#[tracing::instrument(level = "debug")]
async fn get_active_version() -> Option<NodeVersion> { async fn get_active_version() -> Option<NodeVersion> {
version_detection::ParallelDetector::detect_version() version_detection::ParallelDetector::detect_version()
.await .await
@ -141,6 +149,7 @@ impl Nenv {
.and_then(|v| v) .and_then(|v| v)
} }
#[tracing::instrument(level = "debug")]
async fn clear_version_cache() -> Result<()> { async fn clear_version_cache() -> Result<()> {
if VERSION_FILE_PATH.exists() { if VERSION_FILE_PATH.exists() {
fs::remove_file(&*VERSION_FILE_PATH) fs::remove_file(&*VERSION_FILE_PATH)
@ -151,6 +160,7 @@ impl Nenv {
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self))]
fn get_mapper(&self) -> Result<Mapper> { fn get_mapper(&self) -> Result<Mapper> {
let node_path = self let node_path = self
.repo .repo

@ -17,12 +17,15 @@ use crate::{
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::VersionError, error::VersionError,
web_api::{VersionInfo, WebApi}, web_api::WebApi,
}; };
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use self::{node_path::NodePath, versions::Versions}; use self::{
node_path::NodePath,
versions::{SimpleVersionInfo, Versions},
};
pub(crate) mod extract; pub(crate) mod extract;
pub(crate) mod node_path; pub(crate) mod node_path;
@ -90,6 +93,7 @@ pub struct Repository {
impl Repository { impl Repository {
/// Initializes a new repository with the given confi /// Initializes a new repository with the given confi
#[tracing::instrument(level = "debug", skip_all)]
pub async fn init(config: ConfigAccess) -> Result<Self> { pub async fn init(config: ConfigAccess) -> Result<Self> {
Self::create_folders().await?; Self::create_folders().await?;
let web_api = WebApi::new(&config.get().await.download.dist_base_url); let web_api = WebApi::new(&config.get().await.download.dist_base_url);
@ -98,6 +102,7 @@ impl Repository {
Ok(Self { web_api, versions }) Ok(Self { web_api, versions })
} }
#[tracing::instrument(level = "debug")]
async fn create_folders() -> Result<()> { async fn create_folders() -> Result<()> {
let dirs = vec![ let dirs = vec![
&*CFG_DIR, &*CFG_DIR,
@ -116,6 +121,7 @@ impl Repository {
} }
/// Returns the path for the given node version /// Returns the path for the given node version
#[tracing::instrument(level = "debug", skip(self))]
pub fn get_version_path(&self, version: &NodeVersion) -> Result<Option<NodePath>> { pub fn get_version_path(&self, version: &NodeVersion) -> Result<Option<NodePath>> {
let info = self.lookup_version(version)?; let info = self.lookup_version(version)?;
let path = build_version_path(&info.version); let path = build_version_path(&info.version);
@ -128,6 +134,7 @@ impl Repository {
} }
/// Returns a list of installed versions /// Returns a list of installed versions
#[tracing::instrument(level = "debug", skip(self))]
pub async fn installed_versions(&self) -> Result<Vec<Version>> { pub async fn installed_versions(&self) -> Result<Vec<Version>> {
let mut versions = Vec::new(); let mut versions = Vec::new();
let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await.into_diagnostic()?; let mut iter = fs::read_dir(&*NODE_VERSIONS_DIR).await.into_diagnostic()?;
@ -142,6 +149,7 @@ impl Repository {
} }
/// Returns if the given version is installed /// Returns if the given version is installed
#[tracing::instrument(level = "debug", skip(self))]
pub fn is_installed(&self, version: &NodeVersion) -> Result<bool> { pub fn is_installed(&self, version: &NodeVersion) -> Result<bool> {
let info = self.lookup_version(version)?; let info = self.lookup_version(version)?;
@ -149,7 +157,11 @@ impl Repository {
} }
/// Performs a lookup for the given node version /// Performs a lookup for the given node version
pub fn lookup_version(&self, version_req: &NodeVersion) -> Result<&VersionInfo, VersionError> { #[tracing::instrument(level = "debug", skip(self))]
pub fn lookup_version(
&self,
version_req: &NodeVersion,
) -> Result<&SimpleVersionInfo, VersionError> {
let version = 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(),
@ -167,19 +179,22 @@ impl Repository {
} }
/// Returns the reference to all known versions /// Returns the reference to all known versions
#[tracing::instrument(level = "debug", skip(self))]
pub fn all_versions(&self) -> &Versions { pub fn all_versions(&self) -> &Versions {
&self.versions &self.versions
} }
/// Installs a specified node version /// Installs a specified node version
#[tracing::instrument(level = "debug", skip(self))]
pub async fn install_version(&self, version_req: &NodeVersion) -> Result<()> { pub async fn install_version(&self, version_req: &NodeVersion) -> Result<()> {
let info = self.lookup_version(version_req)?; let info = self.lookup_version(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.version, &archive_path)?;
Ok(()) Ok(())
} }
#[tracing::instrument(level = "debug", skip(self))]
async fn download_version(&self, version: &Version) -> Result<PathBuf> { async fn download_version(&self, version: &Version) -> Result<PathBuf> {
let download_path = CACHE_DIR.join(format!("node-v{}{}", version, *NODE_ARCHIVE_SUFFIX)); let download_path = CACHE_DIR.join(format!("node-v{}{}", version, *NODE_ARCHIVE_SUFFIX));
@ -195,8 +210,9 @@ impl Repository {
Ok(download_path) Ok(download_path)
} }
fn extract_archive(&self, info: &VersionInfo, archive_path: &Path) -> Result<()> { #[tracing::instrument(level = "debug", skip(self))]
let dst_path = NODE_VERSIONS_DIR.join(info.version.to_string()); fn extract_archive(&self, version: &Version, archive_path: &Path) -> Result<()> {
let dst_path = NODE_VERSIONS_DIR.join(version.to_string());
extract::extract_file(archive_path, &dst_path)?; extract::extract_file(archive_path, &dst_path)?;
Ok(()) Ok(())
@ -204,6 +220,7 @@ impl Repository {
} }
#[inline] #[inline]
#[tracing::instrument(level = "debug", skip_all)]
async fn load_versions(web_api: &WebApi) -> Result<Versions> { async fn load_versions(web_api: &WebApi) -> Result<Versions> {
let versions = if let Some(v) = Versions::load().await { let versions = if let Some(v) = Versions::load().await {
v v

@ -1,17 +1,67 @@
use std::collections::HashMap; use std::{collections::HashMap, fmt::Display};
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use tokio::fs; use tokio::fs;
use crate::{consts::VERSION_FILE_PATH, error::SerializeJsonError, web_api::VersionInfo}; use crate::{consts::VERSION_FILE_PATH, error::SerializeBincodeError, web_api::VersionInfo};
use miette::{IntoDiagnostic, Result}; use miette::{Context, IntoDiagnostic, Result};
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Versions { pub struct Versions {
lts_versions: HashMap<String, VersionReq>, lts_versions: HashMap<String, u16>,
versions: HashMap<Version, VersionInfo>, versions: HashMap<SimpleVersion, SimpleVersionInfo>,
sorted_versions: Vec<Version>, sorted_versions: Vec<SimpleVersion>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize, Hash)]
pub struct SimpleVersion {
pub major: u16,
pub minor: u16,
pub patch: u32,
}
impl From<semver::Version> for SimpleVersion {
fn from(value: semver::Version) -> Self {
Self {
major: value.major as u16,
minor: value.minor as u16,
patch: value.patch as u32,
}
}
}
impl From<SimpleVersion> for semver::Version {
fn from(value: SimpleVersion) -> Self {
Self::new(value.major as u64, value.minor as u64, value.patch as u64)
}
}
impl Display for SimpleVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Self {
major,
minor,
patch,
} = self;
write!(f, "{major}.{minor}.{patch}")
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct SimpleVersionInfo {
pub version: Version,
pub lts: Option<String>,
}
impl From<VersionInfo> for SimpleVersionInfo {
fn from(value: VersionInfo) -> Self {
Self {
version: value.version,
lts: value.lts.lts(),
}
}
} }
impl Versions { impl Versions {
@ -20,32 +70,34 @@ impl Versions {
if !VERSION_FILE_PATH.exists() { if !VERSION_FILE_PATH.exists() {
return None; return None;
} }
let versions_string = fs::read_to_string(&*VERSION_FILE_PATH).await.ok()?; let byte_contents = fs::read(&*VERSION_FILE_PATH).await.ok()?;
let versions = serde_json::from_str(&versions_string).ok()?;
match bincode::deserialize(&byte_contents) {
Some(versions) Ok(versions) => Some(versions),
Err(e) => {
tracing::error!("Failed to deserialize cache {e}");
fs::remove_file(&*VERSION_FILE_PATH).await.ok()?;
None
}
}
} }
/// creates a new instance to access version information /// creates a new instance to access version information
#[tracing::instrument(level = "debug", skip_all)]
pub fn new(all_versions: Vec<VersionInfo>) -> Self { pub fn new(all_versions: Vec<VersionInfo>) -> Self {
let lts_versions = all_versions let lts_versions = all_versions
.iter() .iter()
.filter_map(|v| { .filter_map(|v| Some((v.lts.lts_ref()?.to_lowercase(), v.version.major as u16)))
Some((
v.lts.as_ref()?.to_lowercase(),
VersionReq::parse(&format!("{}", v.version.major)).ok()?,
))
})
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
let mut sorted_versions = all_versions let mut sorted_versions = all_versions
.iter() .iter()
.map(|v| v.version.to_owned()) .map(|v| v.version.to_owned().into())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
sorted_versions.sort(); sorted_versions.sort();
let versions = all_versions let versions = all_versions
.into_iter() .into_iter()
.map(|v| (v.version.to_owned(), v)) .map(|v| (v.version.to_owned().into(), v.into()))
.collect::<HashMap<_, _>>(); .collect::<HashMap<_, _>>();
Self { Self {
@ -55,52 +107,74 @@ impl Versions {
} }
} }
#[tracing::instrument(level = "debug", skip_all)]
pub(crate) async fn save(&self) -> Result<()> { pub(crate) async fn save(&self) -> Result<()> {
let json_string = serde_json::to_string(&self).map_err(SerializeJsonError::from)?; let byte_content = bincode::serialize(self).map_err(SerializeBincodeError::from)?;
fs::write(&*VERSION_FILE_PATH, json_string) fs::write(&*VERSION_FILE_PATH, byte_content)
.await .await
.into_diagnostic()?; .into_diagnostic()
.context("Caching available node version.")?;
Ok(()) Ok(())
} }
/// Returns the latest known node version /// Returns the latest known node version
pub fn latest(&self) -> &VersionInfo { #[tracing::instrument(level = "debug", skip_all)]
pub fn latest(&self) -> &SimpleVersionInfo {
self.versions self.versions
.get(self.sorted_versions.last().expect("No known node versions")) .get(self.sorted_versions.last().expect("No known node versions"))
.unwrap() .unwrap()
} }
/// Returns the latest node lts version /// Returns the latest node lts version
pub fn latest_lts(&self) -> &VersionInfo { #[tracing::instrument(level = "debug", skip_all)]
pub fn latest_lts(&self) -> &SimpleVersionInfo {
let mut versions = self let mut versions = self
.lts_versions .lts_versions
.values() .values()
.filter_map(|req| self.get_fulfilling(req)) .filter_map(|req| self.get_latest_for_major(*req))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
versions.sort_by_key(|v| &v.version); versions.sort_by_key(|v| &v.version);
versions.last().expect("No known lts node versions") versions.last().expect("No known lts node versions")
} }
/// Returns a lts version by name /// Returns a lts version by name
pub fn get_lts<S: AsRef<str>>(&self, lts_name: S) -> Option<&VersionInfo> { #[tracing::instrument(level = "debug", skip(self))]
pub fn get_lts<S: AsRef<str> + Debug>(&self, lts_name: S) -> Option<&SimpleVersionInfo> {
let lts_version = self.lts_versions.get(lts_name.as_ref())?; let lts_version = self.lts_versions.get(lts_name.as_ref())?;
self.get_fulfilling(lts_version) self.get_latest_for_major(*lts_version)
} }
/// Returns any version that fulfills the given requirement /// Returns any version that fulfills the given requirement
pub fn get_fulfilling(&self, req: &VersionReq) -> Option<&VersionInfo> { #[tracing::instrument(level = "debug", skip(self))]
pub fn get_fulfilling(&self, req: &VersionReq) -> Option<&SimpleVersionInfo> {
let fulfilling_versions = self let fulfilling_versions = self
.sorted_versions .sorted_versions
.iter() .iter()
.map(|v| (*v).into())
.filter(|v| req.matches(v)) .filter(|v| req.matches(v))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
self.versions.get(fulfilling_versions.last()?) let version = fulfilling_versions.last()?.clone().into();
self.versions.get(&version).into()
} }
/// Returns the info for the given version /// Returns the info for the given version
pub fn get(&self, version: &Version) -> Option<&VersionInfo> { #[tracing::instrument(level = "debug", skip(self))]
self.versions.get(version) pub fn get(&self, version: &Version) -> Option<&SimpleVersionInfo> {
self.versions.get(&version.clone().into())
}
/// Returns any version that fulfills the given requirement
#[tracing::instrument(level = "debug", skip(self))]
fn get_latest_for_major(&self, major: u16) -> Option<&SimpleVersionInfo> {
let fulfilling_versions = self
.sorted_versions
.iter()
.filter(|v| v.major == major)
.collect::<Vec<_>>();
let version = fulfilling_versions.last()?;
self.versions.get(&version).into()
} }
} }

@ -42,7 +42,7 @@ impl WebApi {
} }
/// Returns the list of available node versions /// Returns the list of available node versions
#[tracing::instrument(level = "trace")] #[tracing::instrument(level = "debug")]
pub async fn get_versions(&self) -> Result<Vec<VersionInfo>> { pub async fn get_versions(&self) -> Result<Vec<VersionInfo>> {
let versions = self let versions = self
.client .client
@ -61,7 +61,7 @@ impl WebApi {
/// Downloads a specific node version /// Downloads a specific node version
/// and writes it to the given writer /// and writes it to the given writer
#[tracing::instrument(level = "trace", skip(writer))] #[tracing::instrument(level = "debug", skip(writer))]
pub async fn download_version<W: AsyncWrite + Unpin, S: Display + Debug>( pub async fn download_version<W: AsyncWrite + Unpin, S: Display + Debug>(
&self, &self,
version: S, version: S,

@ -1,49 +1,52 @@
use std::borrow::Cow; use serde::{Deserialize, Deserializer};
use serde::{Deserialize, Deserializer, Serialize};
/// Represents a single nodejs version info entry /// Represents a single nodejs version info entry
/// as retrieved from nodejs.org /// as retrieved from nodejs.org
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize)]
pub struct VersionInfo { pub struct VersionInfo {
#[serde(deserialize_with = "deserialize_prefixed_version")] #[serde(deserialize_with = "deserialize_prefixed_version")]
pub version: semver::Version, pub version: semver::Version,
pub date: String, pub date: String,
pub modules: Option<String>, pub modules: Option<String>,
#[serde(deserialize_with = "deserialize_false_as_none")] pub lts: LtsInfo,
pub lts: Option<String>,
pub security: bool, pub security: bool,
#[serde(flatten)]
pub module_versions: ModuleVersions,
pub files: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ModuleVersions {
pub v8: String, pub v8: String,
pub npm: Option<String>, pub npm: Option<String>,
pub uv: Option<String>, pub uv: Option<String>,
pub zlib: Option<String>, pub zlib: Option<String>,
pub openssl: Option<String>, pub openssl: Option<String>,
pub files: Vec<String>,
} }
fn deserialize_false_as_none<'de, D: Deserializer<'de>>( #[derive(Clone, Debug, Deserialize)]
deserializer: D, #[serde(untagged)]
) -> Result<Option<String>, D::Error> { pub enum LtsInfo {
Ok(String::deserialize(deserializer).ok()) Version(String),
NotLts(bool),
}
impl LtsInfo {
pub fn lts(self) -> Option<String> {
match self {
LtsInfo::Version(v) => Some(v),
LtsInfo::NotLts(_) => None,
}
}
pub fn lts_ref(&self) -> Option<&String> {
match &self {
LtsInfo::Version(v) => Some(v),
LtsInfo::NotLts(_) => None,
}
}
} }
fn deserialize_prefixed_version<'de, D: Deserializer<'de>>( fn deserialize_prefixed_version<'de, D: Deserializer<'de>>(
deserializer: D, deserializer: D,
) -> Result<semver::Version, D::Error> { ) -> Result<semver::Version, D::Error> {
let version = String::deserialize(deserializer)?; let version = String::deserialize(deserializer)?;
let version = if let Some(v) = version.strip_prefix('v') { let version = semver::Version::parse(version.trim_start_matches('v'))
Cow::Borrowed(v) .map_err(serde::de::Error::custom)?;
} else {
Cow::Owned(version)
};
let version = semver::Version::parse(version.as_ref()).map_err(serde::de::Error::custom)?;
Ok(version) Ok(version)
} }

Loading…
Cancel
Save