Skip to content
Merged
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
89 changes: 80 additions & 9 deletions src/plugins/core/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::build_time::built_info;
use crate::cache::CacheManagerBuilder;
use crate::cli::args::BackendArg;
use crate::cmd::CmdLineRunner;
use crate::config::settings::DEFAULT_NODE_MIRROR_URL;
use crate::config::{Config, Settings};
use crate::file::{TarFormat, TarOptions};
use crate::http::{HTTP, HTTP_FETCH};
Expand Down Expand Up @@ -157,15 +158,27 @@ impl NodePlugin {
) -> Result<()> {
debug!("{:?}: we will fetch the source and compile", self);
let tarball_name = &opts.source_tarball_name;
self.fetch_tarball(
ctx,
tv,
ctx.pr.as_ref(),
&opts.source_tarball_url,
&opts.source_tarball_path,
&opts.version,
)
.await?;
if let Err(err) = self
.fetch_tarball(
ctx,
tv,
ctx.pr.as_ref(),
&opts.source_tarball_url,
&opts.source_tarball_path,
&opts.version,
)
.await
{
if let Some(reqwest_err) = err.root_cause().downcast_ref::<reqwest::Error>()
&& reqwest_err.status() == Some(reqwest::StatusCode::NOT_FOUND)
&& let Ok(Some(msg)) = self
.suggest_available_flavors(&opts.version, &Settings::get())
.await
{
return Err(eyre::eyre!("{err}\n{msg}"));
}
return Err(err);
}
ctx.pr.next_operation();
ctx.pr.set_message(format!("extract {tarball_name}"));
file::remove_all(&opts.build_dir)?;
Expand Down Expand Up @@ -399,6 +412,64 @@ impl NodePlugin {
.join(&format!("v{v}/SHASUMS256.txt"))?;
Ok(url)
}

async fn suggest_available_flavors(
&self,
v: &str,
settings: &Settings,
) -> Result<Option<String>> {
let base = settings.node.mirror_url();
// If using default mirror, we don't need to suggest anything as it's likely a real 404
if base.to_string() == DEFAULT_NODE_MIRROR_URL {
return Ok(None);
}
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The URL comparison uses string comparison which may not be reliable for URLs. The URL base.to_string() could have variations like trailing slashes or different representations that would still be semantically equivalent. Consider using URL comparison directly or normalizing both URLs before comparison. For example, if one has a trailing slash and the other doesn't, they would be considered different even though they represent the same resource.

Copilot uses AI. Check for mistakes.

let versions: Vec<NodeVersion> = HTTP_FETCH
.json(base.join("index.json")?)
.await
.unwrap_or_default();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This call to .unwrap_or_default() on a Result will not compile because Result does not implement this method. The likely intent is to default to an empty Vec<NodeVersion> if the HTTP request fails. You can achieve this by first converting the Result to an Option with .ok().

Suggested change
.unwrap_or_default();
.ok().unwrap_or_default();


if let Some(version) = versions.iter().find(|nv| {
Comment on lines +429 to +432
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTTP request error from fetching index.json is silently ignored with unwrap_or_default(). If the mirror is misconfigured or the network request fails for any reason (timeout, connection error, invalid JSON, etc.), this will silently return an empty vector and the function will return Ok(None), providing no helpful feedback to the user about why the suggestion system didn't work. Consider logging a warning or debug message when the fetch fails so users can troubleshoot mirror configuration issues.

Suggested change
.await
.unwrap_or_default();
if let Some(version) = versions.iter().find(|nv| {
let versions: Vec<NodeVersion> = match HTTP_FETCH
.json(base.join("index.json")?)
.await
{
Ok(versions) => versions,
Err(err) => {
eprintln!(
"Warning: failed to fetch Node mirror index.json from {}: {err}",
base
);
Vec::new()
}
};

Copilot uses AI. Check for mistakes.
nv.version == format!("v{v}") || nv.version == v || nv.version == format!("v{v}.")
}) {
let os = os();
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version comparison logic includes format!("v{v}.") which appears to be checking for a version string ending with a period. This seems incorrect as version strings typically don't end with a period. This condition would match versions like "v20.0.0." which is unlikely to be a valid version format. Consider removing this comparison or clarifying the intended behavior if there's a specific use case for versions ending with periods.

Suggested change
let os = os();
nv.version == format!("v{v}") || nv.version == v

Copilot uses AI. Check for mistakes.
let arch = arch(settings);
let candidates: Vec<&String> = version
.files
.iter()
.filter(|f| f.starts_with(&format!("{os}-{arch}-")))
.collect();

if !candidates.is_empty() {
let mut msg = format!("Could not find node@{v} with the current settings.\n");
msg.push_str(&format!(
"However, the following flavors are available on the mirror for {os}-{arch}:\n"
));
for candidate in candidates {
// Extract flavor from "linux-x64-musl" -> "musl"
// format is {os}-{arch}-{flavor}
let prefix = format!("{os}-{arch}-");
if let Some(flavor) = candidate.strip_prefix(&prefix) {
msg.push_str(&format!(" - {flavor}\n"));
} else {
msg.push_str(&format!(" - {candidate} (unknown format)\n"));
}
}
msg.push_str("\nYou can try setting the flavor using:\n");
msg.push_str(" mise settings set node.flavor <flavor>\n");
return Ok(Some(msg));
} else {
// Fallback: list all files for that version if no arch match
let mut msg = format!("Could not find node@{v} for {os}-{arch}.\n");
msg.push_str("Available files for this version on the mirror:\n");
for file in &version.files {
msg.push_str(&format!(" - {file}\n"));
}
return Ok(Some(msg));
}
}
Ok(None)
}
}

#[async_trait]
Expand Down
Loading