snm_core/version/
user_version.rs1use semver::VersionReq;
2use serde::Deserialize;
3use std::{env::current_dir, fmt::Display, fs::read_to_string, str::FromStr};
4
5use crate::{
6 fetcher::{Lts, Release},
7 types::{UserAlias, UserLts},
8 SnmRes,
9};
10
11use super::{DistVersion, ParseVersion};
12
13const PACKAGE_JSON: &str = "package.json";
14const VERSION_FILES: [&str; 3] = [".nvmrc", ".node-version", PACKAGE_JSON];
15
16#[derive(Debug, PartialEq, Eq)]
19pub enum UserVersion {
20 Major(u64),
22 MajorMinor(u64, u64),
24 Semver(DistVersion),
26 Range(VersionReq),
28 Alias(UserAlias),
30 Lts(UserLts),
32}
33
34impl ParseVersion for UserVersion {
35 type Item = Self;
36 fn parse(v: &str) -> SnmRes<Self::Item> {
37 let trimmed = v.trim_start_matches('v');
38
39 let version = if let Ok(x) = DistVersion::parse(trimmed) {
40 Self::Semver(x)
41 } else {
42 let is_numeric = trimmed.chars().next().unwrap_or_default().is_numeric();
43
44 if is_numeric {
45 let mut splitted = trimmed.splitn(2, '.');
46
47 match (splitted.next(), splitted.next()) {
48 (Some(a), Some(b)) => Self::MajorMinor(a.parse::<u64>()?, b.parse::<u64>()?),
49 (Some(a), None) => Self::Major(a.parse::<u64>()?),
50 _ => anyhow::bail!("Unable to parse the user provided version"),
51 }
52 } else if let Ok(x) = VersionReq::parse(v) {
53 Self::Range(x)
54 } else if UserLts::is_lts(v) {
55 Self::Lts(UserLts::new(v))
56 } else {
57 Self::Alias(UserAlias::from_str(v)?)
59 }
60 };
61
62 Ok(version)
63 }
64}
65
66impl FromStr for UserVersion {
68 type Err = anyhow::Error;
69 fn from_str(s: &str) -> Result<Self, Self::Err> {
70 Self::parse(s)
71 }
72}
73
74impl Display for UserVersion {
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match self {
77 Self::Major(major) => write!(f, "v{}.x.x", major),
78 Self::MajorMinor(major, minor) => write!(f, "v{}.{}.x", major, minor),
79 Self::Semver(x) => x.fmt(f),
80 Self::Range(x) => x.fmt(f),
81 Self::Alias(x) => x.fmt(f),
82 Self::Lts(x) => x.fmt(f),
83 }
84 }
85}
86
87#[derive(Debug, Deserialize)]
88pub struct Engines {
89 node: Option<String>,
90}
91
92#[derive(Debug, Deserialize)]
93pub struct PkgJson {
94 engines: Option<Engines>,
95}
96
97impl UserVersion {
98 pub fn match_release(&self, release: &Release) -> bool {
99 match (self, &release.version, &release.lts) {
100 (Self::Major(a), DistVersion(b), _) => a == &b.major,
101 (Self::MajorMinor(a, b), DistVersion(c), _) => a == &c.major && b == &c.minor,
102 (Self::Semver(a), b, _) => a.eq(b),
103 (Self::Range(a), DistVersion(b), _) => a.matches(b),
104 (Self::Lts(a), _, Lts::Yes(b)) => a.as_ref() == b,
105 _ => false,
106 }
107 }
108
109 pub fn from_file() -> SnmRes<Self> {
110 let pwd = current_dir()?;
111
112 for f_name in VERSION_FILES {
113 let f_path = pwd.join(&f_name);
114
115 if !f_path.exists() {
116 continue;
117 }
118
119 let version_file = read_to_string(&f_path)?;
120
121 if f_name.eq(PACKAGE_JSON) {
122 let pkg_json: PkgJson = serde_json::from_str(&version_file)?;
123
124 if let Some(Engines { node: Some(v) }) = pkg_json.engines {
125 let parsed = Self::parse(&v)?;
126
127 return Ok(parsed);
128 }
129 } else {
130 let line = version_file.lines().next();
131
132 if let Some(l) = line {
133 let parsed = Self::parse(l)?;
134
135 return Ok(parsed);
136 }
137 }
138 }
139
140 anyhow::bail!("Unable to read version from dotfiles. Please provide a version manually.")
141 }
142}