Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 52 additions & 6 deletions crates/volta-core/src/tool/node/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,31 @@ use archive::{self, Archive};
use cfg_if::cfg_if;
use fs_utils::ensure_containing_dir_exists;
use log::debug;
use node_semver::Version;
use node_semver::{Identifier, Version};
use serde::Deserialize;

cfg_if! {
if #[cfg(feature = "mock-network")] {
// TODO: We need to reconsider our mocking strategy in light of mockito deprecating the
// SERVER_URL constant: Since our acceptance tests run the binary in a separate process,
// we can't use `mockito::server_url()`, which relies on shared memory.
fn public_node_server_root() -> String {
fn public_node_server_root(version: &Version) -> String {
#[allow(deprecated)]
mockito::SERVER_URL.to_string()
let base = mockito::SERVER_URL.to_string();

if is_nightly_version(version) {
format!("{}/download/nightly", base)
} else {
base
}
}
} else {
fn public_node_server_root() -> String {
"https://nodejs.org/dist".to_string()
fn public_node_server_root(version: &Version) -> String {
if is_nightly_version(version) {
"https://nodejs.org/download/nightly".to_string()
} else {
"https://nodejs.org/dist".to_string()
}
}
}
}
Expand All @@ -47,6 +57,13 @@ fn npm_manifest_path(version: &Version) -> PathBuf {
manifest
}

fn is_nightly_version(version: &Version) -> bool {
matches!(
version.pre_release.first(),
Some(Identifier::AlphaNumeric(pre)) if pre.starts_with("nightly")
)
}

pub fn fetch(version: &Version, hooks: Option<&ToolHooks<Node>>) -> Fallible<NodeVersion> {
let home = volta_home()?;
let node_dir = home.node_inventory_dir();
Expand Down Expand Up @@ -162,7 +179,7 @@ fn determine_remote_url(version: &Version, hooks: Option<&ToolHooks<Node>>) -> F
}
_ => Ok(format!(
"{}/v{}/{}",
public_node_server_root(),
public_node_server_root(version),
version,
distro_file_name
)),
Expand Down Expand Up @@ -217,3 +234,32 @@ fn save_default_npm_version(node: &Version, npm: &Version) -> Fallible<()> {
}
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn nightly_versions_use_nightly_root() {
let version = Version::parse("22.0.0-nightly20240101abcd").unwrap();

let url = determine_remote_url(&version, None).expect("should build nightly URL");

assert!(
url.contains("/download/nightly/v22.0.0-nightly20240101abcd/"),
"expected nightly download URL, got {url}"
);
}

#[test]
fn stable_versions_use_release_root() {
let version = Version::parse("22.0.0").unwrap();

let url = determine_remote_url(&version, None).expect("should build release URL");

assert!(
url.contains("/dist/v22.0.0/") || url.contains("/v22.0.0/"),
"expected release download URL, got {url}"
);
}
}
46 changes: 45 additions & 1 deletion crates/volta-core/src/tool/node/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@ cfg_if! {
fn public_node_version_index() -> String {
format!("{}/node-dist/index.json", SERVER_URL)
}
fn public_node_nightly_index() -> String {
format!("{}/node-nightly/index.json", SERVER_URL)
}
} else {
/// Returns the URL of the index of available Node versions on the public Node server.
fn public_node_version_index() -> String {
"https://nodejs.org/dist/index.json".to_string()
}
/// Returns the URL of the index of available nightly Node versions on the public Node server.
fn public_node_nightly_index() -> String {
"https://nodejs.org/download/nightly/index.json".to_string()
}
}
}

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

fn resolve_nightly(hooks: Option<&ToolHooks<Node>>) -> Fallible<Version> {
let url = match hooks {
Some(&ToolHooks {
latest: Some(ref hook),
..
}) => {
debug!("Using node.latest hook to determine node nightly index URL");
hook.resolve("nightly/index.json")?
}
Some(&ToolHooks {
index: Some(ref hook),
..
}) => {
debug!("Using node.index hook to determine node nightly index URL");
hook.resolve("nightly/index.json")?
}
_ => public_node_nightly_index(),
};

let version_opt = match_node_version(&url, |_| true)?;

match version_opt {
Some(version) => {
debug!(
"Found latest nightly node version ({}) from {}",
version, url
);
Ok(version)
}
None => Err(ErrorKind::NodeVersionNotFound {
matching: "nightly".into(),
}
.into()),
}
}

fn resolve_lts(hooks: Option<&ToolHooks<Node>>) -> Fallible<Version> {
let url = match hooks {
Some(&ToolHooks {
Expand Down
40 changes: 33 additions & 7 deletions crates/volta-core/src/tool/serial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,16 @@ impl Spec {

/// Determine if a given string is "version-like".
///
/// This means it is either 'latest', 'lts', a Version, or a Version Range.
/// This means it is either 'latest', 'lts', 'nightly', a Version, or a Version Range.
fn is_version_like(value: &str) -> bool {
matches!(
value.parse(),
match value.parse::<VersionSpec>() {
Ok(VersionSpec::Exact(_))
| Ok(VersionSpec::Semver(_))
| Ok(VersionSpec::Tag(VersionTag::Latest))
| Ok(VersionSpec::Tag(VersionTag::Lts))
)
| Ok(VersionSpec::Semver(_))
| Ok(VersionSpec::Tag(VersionTag::Latest))
| Ok(VersionSpec::Tag(VersionTag::Lts)) => true,
Ok(VersionSpec::Tag(VersionTag::Custom(tag))) if tag == "nightly" => true,
_ => false,
}
}

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

/// Convenience macro for generating the <tool>@<version> string.
macro_rules! versioned_tool {
Expand Down Expand Up @@ -224,6 +226,11 @@ mod tests {
Spec::try_from_str(&versioned_tool!(tool, LTS)).expect("succeeds"),
Spec::Node(VersionSpec::Tag(VersionTag::Lts))
);

assert_eq!(
Spec::try_from_str(&versioned_tool!(tool, NIGHTLY)).expect("succeeds"),
Spec::Node(VersionSpec::Tag(VersionTag::Custom(NIGHTLY.into())))
);
}

#[test]
Expand Down Expand Up @@ -414,6 +421,25 @@ mod tests {
);
}

#[test]
fn special_cases_tool_space_nightly() {
let name = "node";
let version = "nightly";
let args: Vec<String> = vec![name.into(), version.into()];

let err = Spec::from_strings(&args, PIN).unwrap_err();

assert_eq!(
err.kind(),
&ErrorKind::InvalidInvocation {
action: PIN.into(),
name: name.into(),
version: version.into()
},
"`volta <action> node nightly` results in the correct error"
);
}

#[test]
fn leaves_other_scenarios_alone() {
let empty: Vec<&str> = Vec::new();
Expand Down
2 changes: 1 addition & 1 deletion src/command/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::command::Command;

#[derive(clap::Args)]
pub(crate) struct Fetch {
/// Tools to fetch, like `node`, `yarn@latest` or `your-package@^14.4.3`.
/// Tools to fetch, like `node@nightly`, `yarn@latest` or `your-package@^14.4.3`.
#[arg(value_name = "tool[@version]", required = true)]
tools: Vec<String>,
}
Expand Down
2 changes: 1 addition & 1 deletion src/command/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::command::Command;

#[derive(clap::Args)]
pub(crate) struct Install {
/// Tools to install, like `node`, `yarn@latest` or `your-package@^14.4.3`.
/// Tools to install, like `node@nightly`, `yarn@latest` or `your-package@^14.4.3`.
#[arg(value_name = "tool[@version]", required = true)]
tools: Vec<String>,
}
Expand Down
2 changes: 1 addition & 1 deletion src/command/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::command::Command;

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