Skip to content

Commit ea5c2c6

Browse files
feat: added node nightly version support
1 parent 5eedd5f commit ea5c2c6

File tree

7 files changed

+145
-19
lines changed

7 files changed

+145
-19
lines changed

Cargo.lock

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/volta-core/src/tool/node/fetch.rs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,31 @@ use archive::{self, Archive};
1515
use cfg_if::cfg_if;
1616
use fs_utils::ensure_containing_dir_exists;
1717
use log::debug;
18-
use node_semver::Version;
18+
use node_semver::{Identifier, Version};
1919
use serde::Deserialize;
2020

2121
cfg_if! {
2222
if #[cfg(feature = "mock-network")] {
2323
// TODO: We need to reconsider our mocking strategy in light of mockito deprecating the
2424
// SERVER_URL constant: Since our acceptance tests run the binary in a separate process,
2525
// we can't use `mockito::server_url()`, which relies on shared memory.
26-
fn public_node_server_root() -> String {
26+
fn public_node_server_root(version: &Version) -> String {
2727
#[allow(deprecated)]
28-
mockito::SERVER_URL.to_string()
28+
let base = mockito::SERVER_URL.to_string();
29+
30+
if is_nightly_version(version) {
31+
format!("{}/download/nightly", base)
32+
} else {
33+
base
34+
}
2935
}
3036
} else {
31-
fn public_node_server_root() -> String {
32-
"https://nodejs.org/dist".to_string()
37+
fn public_node_server_root(version: &Version) -> String {
38+
if is_nightly_version(version) {
39+
"https://nodejs.org/download/nightly".to_string()
40+
} else {
41+
"https://nodejs.org/dist".to_string()
42+
}
3343
}
3444
}
3545
}
@@ -47,6 +57,13 @@ fn npm_manifest_path(version: &Version) -> PathBuf {
4757
manifest
4858
}
4959

60+
fn is_nightly_version(version: &Version) -> bool {
61+
matches!(
62+
version.pre_release.first(),
63+
Some(Identifier::AlphaNumeric(pre)) if pre.starts_with("nightly")
64+
)
65+
}
66+
5067
pub fn fetch(version: &Version, hooks: Option<&ToolHooks<Node>>) -> Fallible<NodeVersion> {
5168
let home = volta_home()?;
5269
let node_dir = home.node_inventory_dir();
@@ -162,7 +179,7 @@ fn determine_remote_url(version: &Version, hooks: Option<&ToolHooks<Node>>) -> F
162179
}
163180
_ => Ok(format!(
164181
"{}/v{}/{}",
165-
public_node_server_root(),
182+
public_node_server_root(version),
166183
version,
167184
distro_file_name
168185
)),
@@ -217,3 +234,32 @@ fn save_default_npm_version(node: &Version, npm: &Version) -> Fallible<()> {
217234
}
218235
})
219236
}
237+
238+
#[cfg(test)]
239+
mod tests {
240+
use super::*;
241+
242+
#[test]
243+
fn nightly_versions_use_nightly_root() {
244+
let version = Version::parse("22.0.0-nightly20240101abcd").unwrap();
245+
246+
let url = determine_remote_url(&version, None).expect("should build nightly URL");
247+
248+
assert!(
249+
url.contains("/download/nightly/v22.0.0-nightly20240101abcd/"),
250+
"expected nightly download URL, got {url}"
251+
);
252+
}
253+
254+
#[test]
255+
fn stable_versions_use_release_root() {
256+
let version = Version::parse("22.0.0").unwrap();
257+
258+
let url = determine_remote_url(&version, None).expect("should build release URL");
259+
260+
assert!(
261+
url.contains("/dist/v22.0.0/") || url.contains("/v22.0.0/"),
262+
"expected release download URL, got {url}"
263+
);
264+
}
265+
}

crates/volta-core/src/tool/node/resolve.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,18 @@ cfg_if! {
3333
fn public_node_version_index() -> String {
3434
format!("{}/node-dist/index.json", SERVER_URL)
3535
}
36+
fn public_node_nightly_index() -> String {
37+
format!("{}/node-nightly/index.json", SERVER_URL)
38+
}
3639
} else {
3740
/// Returns the URL of the index of available Node versions on the public Node server.
3841
fn public_node_version_index() -> String {
3942
"https://nodejs.org/dist/index.json".to_string()
4043
}
44+
/// Returns the URL of the index of available nightly Node versions on the public Node server.
45+
fn public_node_nightly_index() -> String {
46+
"https://nodejs.org/download/nightly/index.json".to_string()
47+
}
4148
}
4249
}
4350

@@ -48,7 +55,8 @@ pub fn resolve(matching: VersionSpec, session: &mut Session) -> Fallible<Version
4855
VersionSpec::Exact(version) => Ok(version),
4956
VersionSpec::None | VersionSpec::Tag(VersionTag::Lts) => resolve_lts(hooks),
5057
VersionSpec::Tag(VersionTag::Latest) => resolve_latest(hooks),
51-
// Node doesn't have "tagged" versions (apart from 'latest' and 'lts'), so custom tags will always be an error
58+
// Node doesn't have "tagged" versions (apart from 'latest', 'lts', and 'nightly'), so other custom tags are an error
59+
VersionSpec::Tag(VersionTag::Custom(tag)) if tag == "nightly" => resolve_nightly(hooks),
5260
VersionSpec::Tag(VersionTag::Custom(tag)) => {
5361
Err(ErrorKind::NodeVersionNotFound { matching: tag }.into())
5462
}
@@ -83,6 +91,42 @@ fn resolve_latest(hooks: Option<&ToolHooks<Node>>) -> Fallible<Version> {
8391
}
8492
}
8593

94+
fn resolve_nightly(hooks: Option<&ToolHooks<Node>>) -> Fallible<Version> {
95+
let url = match hooks {
96+
Some(&ToolHooks {
97+
latest: Some(ref hook),
98+
..
99+
}) => {
100+
debug!("Using node.latest hook to determine node nightly index URL");
101+
hook.resolve("nightly/index.json")?
102+
}
103+
Some(&ToolHooks {
104+
index: Some(ref hook),
105+
..
106+
}) => {
107+
debug!("Using node.index hook to determine node nightly index URL");
108+
hook.resolve("nightly/index.json")?
109+
}
110+
_ => public_node_nightly_index(),
111+
};
112+
113+
let version_opt = match_node_version(&url, |_| true)?;
114+
115+
match version_opt {
116+
Some(version) => {
117+
debug!(
118+
"Found latest nightly node version ({}) from {}",
119+
version, url
120+
);
121+
Ok(version)
122+
}
123+
None => Err(ErrorKind::NodeVersionNotFound {
124+
matching: "nightly".into(),
125+
}
126+
.into()),
127+
}
128+
}
129+
86130
fn resolve_lts(hooks: Option<&ToolHooks<Node>>) -> Fallible<Version> {
87131
let url = match hooks {
88132
Some(&ToolHooks {

crates/volta-core/src/tool/serial.rs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,16 @@ impl Spec {
155155

156156
/// Determine if a given string is "version-like".
157157
///
158-
/// This means it is either 'latest', 'lts', a Version, or a Version Range.
158+
/// This means it is either 'latest', 'lts', 'nightly', a Version, or a Version Range.
159159
fn is_version_like(value: &str) -> bool {
160-
matches!(
161-
value.parse(),
160+
match value.parse::<VersionSpec>() {
162161
Ok(VersionSpec::Exact(_))
163-
| Ok(VersionSpec::Semver(_))
164-
| Ok(VersionSpec::Tag(VersionTag::Latest))
165-
| Ok(VersionSpec::Tag(VersionTag::Lts))
166-
)
162+
| Ok(VersionSpec::Semver(_))
163+
| Ok(VersionSpec::Tag(VersionTag::Latest))
164+
| Ok(VersionSpec::Tag(VersionTag::Lts)) => true,
165+
Ok(VersionSpec::Tag(VersionTag::Custom(tag))) if tag == "nightly" => true,
166+
_ => false,
167+
}
167168
}
168169

169170
#[cfg(test)]
@@ -180,6 +181,7 @@ mod tests {
180181
const MINOR: &str = "3.0";
181182
const PATCH: &str = "3.0.0";
182183
const BETA: &str = "beta";
184+
const NIGHTLY: &str = "nightly";
183185

184186
/// Convenience macro for generating the <tool>@<version> string.
185187
macro_rules! versioned_tool {
@@ -224,6 +226,11 @@ mod tests {
224226
Spec::try_from_str(&versioned_tool!(tool, LTS)).expect("succeeds"),
225227
Spec::Node(VersionSpec::Tag(VersionTag::Lts))
226228
);
229+
230+
assert_eq!(
231+
Spec::try_from_str(&versioned_tool!(tool, NIGHTLY)).expect("succeeds"),
232+
Spec::Node(VersionSpec::Tag(VersionTag::Custom(NIGHTLY.into())))
233+
);
227234
}
228235

229236
#[test]
@@ -414,6 +421,25 @@ mod tests {
414421
);
415422
}
416423

424+
#[test]
425+
fn special_cases_tool_space_nightly() {
426+
let name = "node";
427+
let version = "nightly";
428+
let args: Vec<String> = vec![name.into(), version.into()];
429+
430+
let err = Spec::from_strings(&args, PIN).unwrap_err();
431+
432+
assert_eq!(
433+
err.kind(),
434+
&ErrorKind::InvalidInvocation {
435+
action: PIN.into(),
436+
name: name.into(),
437+
version: version.into()
438+
},
439+
"`volta <action> node nightly` results in the correct error"
440+
);
441+
}
442+
417443
#[test]
418444
fn leaves_other_scenarios_alone() {
419445
let empty: Vec<&str> = Vec::new();

src/command/fetch.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::command::Command;
66

77
#[derive(clap::Args)]
88
pub(crate) struct Fetch {
9-
/// Tools to fetch, like `node`, `yarn@latest` or `your-package@^14.4.3`.
9+
/// Tools to fetch, like `node@nightly`, `yarn@latest` or `your-package@^14.4.3`.
1010
#[arg(value_name = "tool[@version]", required = true)]
1111
tools: Vec<String>,
1212
}

src/command/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::command::Command;
66

77
#[derive(clap::Args)]
88
pub(crate) struct Install {
9-
/// Tools to install, like `node`, `yarn@latest` or `your-package@^14.4.3`.
9+
/// Tools to install, like `node@nightly`, `yarn@latest` or `your-package@^14.4.3`.
1010
#[arg(value_name = "tool[@version]", required = true)]
1111
tools: Vec<String>,
1212
}

src/command/pin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::command::Command;
66

77
#[derive(clap::Args)]
88
pub(crate) struct Pin {
9-
/// Tools to pin, like `node@lts` or `yarn@^1.14`.
9+
/// Tools to pin, like `node@lts`, `node@nightly`, or `yarn@^1.14`.
1010
#[arg(value_name = "tool[@version]", required = true)]
1111
tools: Vec<String>,
1212
}

0 commit comments

Comments
 (0)