1use crate::NodeLanguage;
2use proto_core::{
3 async_trait, is_offline, is_semantic_version, load_versions_manifest, parse_version,
4 remove_v_prefix, Describable, ProtoError, Resolvable, Tool, VersionManifest,
5 VersionManifestEntry,
6};
7use serde::Deserialize;
8use std::collections::BTreeMap;
9use tracing::debug;
10
11#[derive(Deserialize)]
12#[serde(untagged)]
13enum NodeLTS {
14 Name(String),
15 State(bool),
16}
17
18#[derive(Deserialize)]
19struct NodeDistVersion {
20 lts: NodeLTS,
21 version: String, }
23
24#[async_trait]
25impl Resolvable<'_> for NodeLanguage {
26 fn get_resolved_version(&self) -> &str {
27 match self.version.as_ref() {
28 Some(version) => version,
29 None => "latest",
30 }
31 }
32
33 async fn load_version_manifest(&self) -> Result<VersionManifest, ProtoError> {
34 let mut aliases = BTreeMap::new();
35 let mut versions = BTreeMap::new();
36 let response: Vec<NodeDistVersion> =
37 load_versions_manifest("https://nodejs.org/dist/index.json").await?;
38
39 for (index, item) in response.iter().enumerate() {
40 if index == 0 {
42 aliases.insert("latest".into(), item.version.clone());
43 }
44
45 let mut entry = VersionManifestEntry {
46 alias: None,
47 version: remove_v_prefix(&item.version),
48 };
49
50 if let NodeLTS::Name(alias) = &item.lts {
51 let alias = alias.to_lowercase();
52
53 if !aliases.contains_key("stable") {
55 aliases.insert("stable".into(), item.version.clone());
56 }
57
58 if !aliases.contains_key(&alias) {
60 aliases.insert(alias.clone(), item.version.clone());
61 }
62
63 entry.alias = Some(alias);
64 }
65
66 versions.insert(entry.version.clone(), entry);
67 }
68
69 let mut manifest = VersionManifest { aliases, versions };
70
71 manifest.inherit_aliases(&self.get_manifest()?.aliases);
72
73 Ok(manifest)
74 }
75
76 async fn resolve_version(&mut self, initial_version: &str) -> Result<String, ProtoError> {
77 if let Some(version) = &self.version {
78 return Ok(version.to_owned());
79 }
80
81 let initial_version = remove_v_prefix(initial_version).to_lowercase();
82
83 if is_semantic_version(&initial_version) && is_offline() {
86 self.set_version(&initial_version);
87
88 return Ok(initial_version);
89 }
90
91 debug!(
92 tool = self.get_id(),
93 initial_version = initial_version,
94 "Resolving a semantic version for \"{}\"",
95 initial_version
96 );
97
98 let manifest = self.load_version_manifest().await?;
99 let candidate;
100
101 if initial_version == "node" || initial_version == "latest" {
103 candidate = manifest.get_version_from_alias("latest")?;
104
105 } else if initial_version == "stable"
107 || initial_version == "lts-*"
108 || initial_version == "lts/*"
109 {
110 candidate = manifest.get_version_from_alias("stable")?;
111
112 } else if initial_version.starts_with("lts-") || initial_version.starts_with("lts/") {
114 candidate = manifest.get_version_from_alias(&initial_version[4..])?;
115
116 } else {
118 candidate = manifest.find_version(&initial_version)?;
119 }
120
121 let version = parse_version(candidate)?.to_string();
122
123 debug!(
124 tool = self.get_id(),
125 version = candidate,
126 "Resolved to {}",
127 version
128 );
129
130 self.set_version(&version);
131
132 Ok(version)
133 }
134
135 fn set_version(&mut self, version: &str) {
136 self.version = Some(version.to_owned());
137 }
138}