mirror of
https://github.com/zhom/banderole.git
synced 2026-06-10 16:23:52 +02:00
feat: support workspace .nvmrc
This commit is contained in:
+6
-1
@@ -1,4 +1,5 @@
|
||||
mod bundler;
|
||||
mod executable;
|
||||
mod node_downloader;
|
||||
mod node_version_manager;
|
||||
mod platform;
|
||||
@@ -33,6 +34,9 @@ enum Commands {
|
||||
/// Disable compression for faster bundling (useful for testing)
|
||||
#[arg(long)]
|
||||
no_compression: bool,
|
||||
/// Ignore cached version resolution results
|
||||
#[arg(long)]
|
||||
ignore_cached_versions: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -46,8 +50,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
output,
|
||||
name,
|
||||
no_compression,
|
||||
ignore_cached_versions,
|
||||
} => {
|
||||
bundler::bundle_project(path, output, name, no_compression).await?;
|
||||
bundler::bundle_project(path, output, name, no_compression, ignore_cached_versions).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ impl NodeDownloader {
|
||||
|
||||
// Resolve the version specification to a concrete version
|
||||
let resolved_version = version_resolver
|
||||
.resolve_version(version_spec)
|
||||
.resolve_version(version_spec, false)
|
||||
.await
|
||||
.context(format!(
|
||||
"Failed to resolve Node.js version '{}'",
|
||||
|
||||
@@ -27,7 +27,7 @@ impl VersionCache {
|
||||
Self {
|
||||
versions: Vec::new(),
|
||||
last_updated: None,
|
||||
cache_duration: Duration::from_secs(3600), // 1 hour
|
||||
cache_duration: Duration::from_secs(86400), // 1 day
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,15 +87,6 @@ impl ParsedVersion {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
match (self.minor, self.patch) {
|
||||
(Some(minor), Some(patch)) => format!("{}.{}.{}", self.major, minor, patch),
|
||||
(Some(minor), None) => format!("{}.{}", self.major, minor),
|
||||
(None, None) => format!("{}", self.major),
|
||||
(None, Some(_)) => unreachable!("Cannot have patch without minor"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ParsedVersion {
|
||||
@@ -144,8 +135,8 @@ impl NodeVersionManager {
|
||||
}
|
||||
|
||||
/// Resolve a version specification like "23", "23.5", "v22.1.0" to a complete version
|
||||
pub async fn resolve_version(&self, version_spec: &str) -> Result<String> {
|
||||
let versions = self.fetch_versions().await?;
|
||||
pub async fn resolve_version(&self, version_spec: &str, ignore_cached_versions: bool) -> Result<String> {
|
||||
let versions = self.fetch_versions(ignore_cached_versions).await?;
|
||||
let parsed_spec = self.parse_version_spec(version_spec)?;
|
||||
|
||||
let matching_versions = self.find_matching_versions(&versions, &parsed_spec);
|
||||
@@ -154,46 +145,22 @@ impl NodeVersionManager {
|
||||
anyhow::bail!("No Node.js version found matching '{}'", version_spec);
|
||||
}
|
||||
|
||||
// Return the latest matching version
|
||||
let latest = matching_versions.last().unwrap();
|
||||
Ok(latest.version.trim_start_matches('v').to_string())
|
||||
}
|
||||
|
||||
/// Get the latest version for a major version (e.g., latest v23.x.x)
|
||||
pub async fn get_latest_for_major(&self, major: u32) -> Result<String> {
|
||||
self.resolve_version(&major.to_string()).await
|
||||
}
|
||||
|
||||
/// List all available versions matching a pattern
|
||||
pub async fn list_versions(&self, pattern: Option<&str>) -> Result<Vec<String>> {
|
||||
let versions = self.fetch_versions().await?;
|
||||
|
||||
let filtered_versions = if let Some(pattern) = pattern {
|
||||
let parsed_spec = self.parse_version_spec(pattern)?;
|
||||
self.find_matching_versions(&versions, &parsed_spec)
|
||||
} else {
|
||||
versions.iter().collect()
|
||||
};
|
||||
|
||||
Ok(filtered_versions
|
||||
.iter()
|
||||
.map(|v| v.version.trim_start_matches('v').to_string())
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn fetch_versions(&self) -> Result<Vec<NodeVersion>> {
|
||||
async fn fetch_versions(&self, ignore_cached_versions: bool) -> Result<Vec<NodeVersion>> {
|
||||
// Check cache first
|
||||
{
|
||||
let cache = VERSION_CACHE
|
||||
.lock()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to acquire cache lock: {}", e))?;
|
||||
|
||||
if !cache.is_expired() && !cache.versions.is_empty() {
|
||||
if !ignore_cached_versions && !cache.is_expired() && !cache.versions.is_empty() {
|
||||
return Ok(cache.versions.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from Node.js API
|
||||
let url = "https://nodejs.org/dist/index.json";
|
||||
let response = self
|
||||
.client
|
||||
@@ -219,7 +186,6 @@ impl NodeVersionManager {
|
||||
version_a.cmp(&version_b)
|
||||
});
|
||||
|
||||
// Update cache
|
||||
{
|
||||
let mut cache = VERSION_CACHE
|
||||
.lock()
|
||||
@@ -286,7 +252,6 @@ impl NodeVersionManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by version number
|
||||
matching.sort_by(|a, b| {
|
||||
let version_a = self.parse_node_version(&a.version).unwrap_or_default();
|
||||
let version_b = self.parse_node_version(&b.version).unwrap_or_default();
|
||||
@@ -365,7 +330,7 @@ mod tests {
|
||||
let resolver = NodeVersionManager::new();
|
||||
|
||||
// This test requires internet connection
|
||||
if let Ok(version) = resolver.resolve_version("18").await {
|
||||
if let Ok(version) = resolver.resolve_version("18", false).await {
|
||||
assert!(version.starts_with("18."));
|
||||
println!("Resolved '18' to: {}", version);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
mod common;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::{
|
||||
BundlerTestHelper, TestAssertions, TestCacheManager, TestProject, TestProjectManager,
|
||||
};
|
||||
use serial_test::serial;
|
||||
use std::fs;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_nvmrc_file_handling() -> Result<()> {
|
||||
println!("Testing workspace .nvmrc file handling...");
|
||||
|
||||
// Create a workspace project
|
||||
let project = TestProject::new("nvmrc-workspace-app")
|
||||
.workspace()
|
||||
.with_dependency("lodash", "^4.17.21");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Create .nvmrc file in workspace root with version 20
|
||||
let workspace_root = manager.workspace_root().unwrap();
|
||||
fs::write(workspace_root.join(".nvmrc"), "20")?;
|
||||
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("nvmrc-workspace-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
TestAssertions::assert_executable_works(
|
||||
&executable_path,
|
||||
&[
|
||||
"Hello from workspace project!",
|
||||
"Dependencies:",
|
||||
"Workspace project test completed!",
|
||||
],
|
||||
&[],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
println!("✅ workspace .nvmrc file handling test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_node_version_file_handling() -> Result<()> {
|
||||
println!("Testing workspace .node-version file handling...");
|
||||
|
||||
// Create a workspace project
|
||||
let project = TestProject::new("node-version-workspace-app")
|
||||
.workspace()
|
||||
.with_dependency("uuid", "^9.0.1");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Create .node-version file in workspace root with version 18.17.0
|
||||
let workspace_root = manager.workspace_root().unwrap();
|
||||
fs::write(workspace_root.join(".node-version"), "18.17.0")?;
|
||||
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("node-version-workspace-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
TestAssertions::assert_executable_works(
|
||||
&executable_path,
|
||||
&[
|
||||
"Hello from workspace project!",
|
||||
"Dependencies:",
|
||||
"Workspace project test completed!",
|
||||
],
|
||||
&[],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
println!("✅ workspace .node-version file handling test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_project_level_version_overrides_workspace() -> Result<()> {
|
||||
println!("Testing project-level version file overrides workspace version...");
|
||||
|
||||
// Create a workspace project
|
||||
let project = TestProject::new("version-override-app")
|
||||
.workspace()
|
||||
.with_dependency("date-fns", "^2.30.0");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Create .nvmrc file in workspace root with version 20
|
||||
let workspace_root = manager.workspace_root().unwrap();
|
||||
fs::write(workspace_root.join(".nvmrc"), "20")?;
|
||||
|
||||
// Create .nvmrc file in project directory with version 18 (should override workspace)
|
||||
fs::write(manager.project_path().join(".nvmrc"), "18")?;
|
||||
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("version-override-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
TestAssertions::assert_executable_works(
|
||||
&executable_path,
|
||||
&[
|
||||
"Hello from workspace project!",
|
||||
"Dependencies:",
|
||||
"Workspace project test completed!",
|
||||
],
|
||||
&[],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
println!("✅ project-level version override test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_version_format_compatibility() -> Result<()> {
|
||||
println!("Testing various Node version format compatibility...");
|
||||
|
||||
// Test different version formats that nvmrc supports
|
||||
let test_cases = vec![
|
||||
("23", "major-only"),
|
||||
("23.5", "major-minor"),
|
||||
("v20.10.0", "full-with-v-prefix"),
|
||||
("20.10.0", "full-without-prefix"),
|
||||
];
|
||||
|
||||
for (version_spec, test_name) in test_cases {
|
||||
println!("Testing version format: {} ({})", version_spec, test_name);
|
||||
|
||||
let project = TestProject::new(&format!("version-format-{}", test_name))
|
||||
.workspace()
|
||||
.with_dependency("fs-extra", "^11.1.1");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Create .nvmrc file with the test version
|
||||
let workspace_root = manager.workspace_root().unwrap();
|
||||
fs::write(workspace_root.join(".nvmrc"), version_spec)?;
|
||||
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some(&format!("version-format-{}-test", test_name)),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
TestAssertions::assert_executable_works(
|
||||
&executable_path,
|
||||
&[
|
||||
"Hello from workspace project!",
|
||||
"Dependencies:",
|
||||
"Workspace project test completed!",
|
||||
],
|
||||
&[],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
println!("✅ version format {} test passed!", version_spec);
|
||||
}
|
||||
|
||||
println!("✅ all version format compatibility tests passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_nested_workspace_package_version_resolution() -> Result<()> {
|
||||
println!("Testing nested workspace package version resolution...");
|
||||
|
||||
// Create a deeply nested workspace structure
|
||||
let project = TestProject::new("nested-version-app")
|
||||
.workspace()
|
||||
.with_dependency("commander", "^11.0.0");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Create version files at different levels
|
||||
let workspace_root = manager.workspace_root().unwrap();
|
||||
|
||||
// Workspace root has Node 20
|
||||
fs::write(workspace_root.join(".nvmrc"), "20")?;
|
||||
|
||||
// Create an intermediate directory (simulating packages/ directory)
|
||||
let packages_dir = workspace_root.join("packages");
|
||||
fs::create_dir_all(&packages_dir)?;
|
||||
|
||||
// Packages directory has Node 18 (should be ignored since project is deeper)
|
||||
fs::write(packages_dir.join(".node-version"), "18")?;
|
||||
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("nested-version-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
TestAssertions::assert_executable_works(
|
||||
&executable_path,
|
||||
&[
|
||||
"Hello from workspace project!",
|
||||
"Dependencies:",
|
||||
"Workspace project test completed!",
|
||||
],
|
||||
&[],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
println!("✅ nested workspace package version resolution test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_zzz_cleanup_workspace_version_cache() -> Result<()> {
|
||||
println!("Cleaning up application cache after workspace version tests...");
|
||||
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
println!("✅ Workspace version cache cleanup completed!");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user