From c0589d1278e6f84a0cf19bda985056128c18196b Mon Sep 17 00:00:00 2001 From: zhom <2717306+zhom@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:25:59 +0400 Subject: [PATCH] refactor: better node binary traversal --- src/bundler.rs | 26 ++++++++++---------- src/node_downloader.rs | 54 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/src/bundler.rs b/src/bundler.rs index bda8abf..76be805 100644 --- a/src/bundler.rs +++ b/src/bundler.rs @@ -103,20 +103,20 @@ pub async fn bundle_project( let node_executable = node_downloader .ensure_node_binary_with_progress(Some(&pb_prepare)) .await?; - let node_root_buf = if Platform::current().is_windows() { - // On Windows, node.exe lives directly under the platform directory - node_executable + let node_root_buf = { + // The extraction target_dir is what we passed to NodeDownloader::download_and_extract_node + // which is cache_dir/node// on all platforms. We want to bundle that + // entire directory under "node/" so the runtime can find binaries consistently. + // Derive the root by walking up from the executable until we find the directory named + // the platform triplet (win32-*, darwin-*, linux-*). + let mut cur = node_executable .parent() - .expect("node executable must have a parent") - .to_path_buf() - } else { - // On Unix, node is under /bin/node - node_executable - .parent() - .expect("node executable must have a parent") - .parent() - .unwrap_or_else(|| panic!("Unexpected node layout for {}", node_executable.display())) - .to_path_buf() + .expect("node executable must have a parent"); + // If on Unix and we are at ...//bin, step up to + if cur.file_name().is_some_and(|n| n == "bin") { + cur = cur.parent().unwrap_or(cur); + } + cur.to_path_buf() }; let node_root: &Path = &node_root_buf; pb_prepare.finish_and_clear(); diff --git a/src/node_downloader.rs b/src/node_downloader.rs index 87288f8..a4b0109 100644 --- a/src/node_downloader.rs +++ b/src/node_downloader.rs @@ -94,7 +94,7 @@ impl NodeDownloader { .join(&self.node_version) .join(self.platform.to_string()); - let node_executable = node_dir.join(self.platform.node_executable_path()); + let mut node_executable = node_dir.join(self.platform.node_executable_path()); if node_executable.exists() { // Update in-memory cache @@ -118,11 +118,55 @@ impl NodeDownloader { // Download and extract Node.js self.download_and_extract_node(&node_dir, progress).await?; + // Validate presence; if not in expected location, search recursively as a fallback if !node_executable.exists() { - anyhow::bail!( - "Node executable not found after extraction: {}", - node_executable.display() - ); + // Platform-specific name to search for + let expected_name = self.platform.node_executable_path(); + let expected_name_str = expected_name + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or("node.exe"); + let mut found_path: Option = None; + if std::fs::read_dir(&node_dir).is_ok() { + for e in walkdir::WalkDir::new(&node_dir) + .follow_links(true) + .into_iter() + .flatten() + { + let p = e.path(); + if p.is_file() { + if let Some(name) = p.file_name().and_then(|n| n.to_str()) { + // On Windows search for node.exe case-insensitively, else match exact + if (self.platform.is_windows() + && name.eq_ignore_ascii_case(expected_name_str)) + || (!self.platform.is_windows() && name == expected_name_str) + { + found_path = Some(p.to_path_buf()); + break; + } + } + } + } + } + + if let Some(found) = found_path { + node_executable = found; + } else { + // Provide additional diagnostics + let dir_list: Vec = std::fs::read_dir(&node_dir) + .map(|it| { + it.filter_map(|e| e.ok()) + .map(|entry| entry.file_name().to_string_lossy().to_string()) + .collect() + }) + .unwrap_or_default(); + anyhow::bail!( + "Node executable not found after extraction: {}\nDirectory: {}\nEntries: {:?}", + node_executable.display(), + node_dir.display(), + dir_list + ); + } } // Make executable on Unix systems