Skip to content

Commit 80dfb48

Browse files
authored
fix(vite_global_cli): invalidate resolve cache after version configuration changes (voidzero-dev#1089)
resolve voidzero-dev#840
1 parent a206ba9 commit 80dfb48

File tree

4 files changed

+153
-0
lines changed

4 files changed

+153
-0
lines changed

crates/vite_global_cli/src/commands/env/default.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ async fn set_default(version: &str) -> Result<ExitStatus, Error> {
8787
config.default_node_version = Some(store_version);
8888
save_config(&config).await?;
8989

90+
// Invalidate resolve cache so the new default takes effect immediately
91+
crate::shim::invalidate_cache();
92+
9093
println!("\u{2713} Default Node.js version set to {display_version}");
9194

9295
Ok(ExitStatus::default())

crates/vite_global_cli/src/commands/env/pin.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ async fn do_pin(
130130
// Write the version to .node-version
131131
tokio::fs::write(&node_version_path, format!("{resolved_version}\n")).await?;
132132

133+
// Invalidate resolve cache so the pinned version takes effect immediately
134+
crate::shim::invalidate_cache();
135+
133136
// Print success message
134137
if was_alias {
135138
output::success(&format!(
@@ -206,13 +209,18 @@ pub async fn do_unpin(cwd: &AbsolutePathBuf) -> Result<ExitStatus, Error> {
206209
}
207210

208211
tokio::fs::remove_file(&node_version_path).await?;
212+
213+
// Invalidate resolve cache so the unpinned version falls back correctly
214+
crate::shim::invalidate_cache();
215+
209216
output::success(&format!("Removed {} from {}", NODE_VERSION_FILE, cwd.as_path().display()));
210217

211218
Ok(ExitStatus::default())
212219
}
213220

214221
#[cfg(test)]
215222
mod tests {
223+
use serial_test::serial;
216224
use tempfile::TempDir;
217225
use vite_path::AbsolutePathBuf;
218226

@@ -275,6 +283,90 @@ mod tests {
275283
assert!(!tokio::fs::try_exists(&node_version_path).await.unwrap());
276284
}
277285

286+
#[tokio::test]
287+
// Run serially: mutates VITE_PLUS_HOME env var which affects invalidate_cache()
288+
#[serial]
289+
async fn test_do_unpin_invalidates_cache() {
290+
let temp_dir = TempDir::new().unwrap();
291+
let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
292+
293+
// Point VITE_PLUS_HOME to temp dir
294+
unsafe {
295+
std::env::set_var(vite_shared::env_vars::VITE_PLUS_HOME, temp_path.as_path());
296+
}
297+
298+
// Create cache file manually
299+
let cache_dir = temp_path.join("cache");
300+
std::fs::create_dir_all(&cache_dir).unwrap();
301+
let cache_file = cache_dir.join("resolve_cache.json");
302+
std::fs::write(&cache_file, r#"{"version":2,"entries":{}}"#).unwrap();
303+
assert!(
304+
std::fs::metadata(cache_file.as_path()).is_ok(),
305+
"Cache file should exist before unpin"
306+
);
307+
308+
// Create .node-version and unpin
309+
let node_version_path = temp_path.join(".node-version");
310+
tokio::fs::write(&node_version_path, "20.18.0\n").await.unwrap();
311+
let result = do_unpin(&temp_path).await;
312+
assert!(result.is_ok());
313+
314+
// Cache file should be removed by invalidate_cache()
315+
assert!(
316+
std::fs::metadata(cache_file.as_path()).is_err(),
317+
"Cache file should be removed after unpin"
318+
);
319+
320+
// Cleanup
321+
unsafe {
322+
std::env::remove_var(vite_shared::env_vars::VITE_PLUS_HOME);
323+
}
324+
}
325+
326+
// Run serially: mutates VITE_PLUS_HOME env var which affects invalidate_cache()
327+
#[tokio::test]
328+
#[serial]
329+
async fn test_do_pin_invalidates_cache() {
330+
let temp_dir = TempDir::new().unwrap();
331+
let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
332+
333+
// Point VITE_PLUS_HOME to temp dir
334+
unsafe {
335+
std::env::set_var(vite_shared::env_vars::VITE_PLUS_HOME, temp_path.as_path());
336+
}
337+
338+
// Create cache file manually
339+
let cache_dir = temp_path.join("cache");
340+
std::fs::create_dir_all(&cache_dir).unwrap();
341+
let cache_file = cache_dir.join("resolve_cache.json");
342+
std::fs::write(&cache_file, r#"{"version":2,"entries":{}}"#).unwrap();
343+
assert!(
344+
std::fs::metadata(cache_file.as_path()).is_ok(),
345+
"Cache file should exist before pin"
346+
);
347+
348+
// Pin an exact version (no_install=true to skip download, force=true to skip prompt)
349+
let result = do_pin(&temp_path, "20.18.0", true, true).await;
350+
assert!(result.is_ok());
351+
352+
// .node-version should be created
353+
let node_version_path = temp_path.join(".node-version");
354+
assert!(tokio::fs::try_exists(&node_version_path).await.unwrap());
355+
let content = tokio::fs::read_to_string(&node_version_path).await.unwrap();
356+
assert_eq!(content.trim(), "20.18.0");
357+
358+
// Cache file should be removed by invalidate_cache()
359+
assert!(
360+
std::fs::metadata(cache_file.as_path()).is_err(),
361+
"Cache file should be removed after pin"
362+
);
363+
364+
// Cleanup
365+
unsafe {
366+
std::env::remove_var(vite_shared::env_vars::VITE_PLUS_HOME);
367+
}
368+
}
369+
278370
#[tokio::test]
279371
async fn test_do_unpin_no_file() {
280372
let temp_dir = TempDir::new().unwrap();

crates/vite_global_cli/src/shim/cache.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,14 @@ pub fn get_cache_path() -> Option<AbsolutePathBuf> {
188188
Some(home.join("cache").join("resolve_cache.json"))
189189
}
190190

191+
/// Invalidate the entire resolve cache by deleting the cache file.
192+
/// Called after version configuration changes (e.g., `vp env default`, `vp env pin`, `vp env unpin`).
193+
pub fn invalidate_cache() {
194+
if let Some(cache_path) = get_cache_path() {
195+
std::fs::remove_file(cache_path.as_path()).ok();
196+
}
197+
}
198+
191199
/// Get the mtime of a file as Unix timestamp.
192200
pub fn get_file_mtime(path: &AbsolutePath) -> Option<u64> {
193201
let metadata = std::fs::metadata(path).ok()?;
@@ -335,4 +343,53 @@ mod tests {
335343
assert!(cached_entry.is_some(), "Range version cache should be valid within TTL");
336344
assert_eq!(cached_entry.unwrap().version, "20.20.0");
337345
}
346+
347+
// Run serially: mutates VITE_PLUS_HOME env var which affects get_cache_path()
348+
#[test]
349+
#[serial_test::serial]
350+
fn test_invalidate_cache_removes_file() {
351+
let temp_dir = TempDir::new().unwrap();
352+
let temp_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap();
353+
354+
// Set VITE_PLUS_HOME to temp dir so invalidate_cache() targets our test file
355+
let cache_dir = temp_path.join("cache");
356+
std::fs::create_dir_all(&cache_dir).unwrap();
357+
let cache_file = cache_dir.join("resolve_cache.json");
358+
359+
// Create a cache with an entry and save it
360+
let mut cache = ResolveCache::default();
361+
cache.insert(
362+
&temp_path,
363+
ResolveCacheEntry {
364+
version: "20.18.0".to_string(),
365+
source: ".node-version".to_string(),
366+
project_root: None,
367+
resolved_at: now_timestamp(),
368+
version_file_mtime: 0,
369+
source_path: None,
370+
is_range: false,
371+
},
372+
);
373+
cache.save(&cache_file);
374+
assert!(std::fs::metadata(cache_file.as_path()).is_ok(), "Cache file should exist");
375+
376+
// Point VITE_PLUS_HOME to our temp dir and call invalidate_cache
377+
unsafe {
378+
std::env::set_var(vite_shared::env_vars::VITE_PLUS_HOME, temp_path.as_path());
379+
}
380+
invalidate_cache();
381+
unsafe {
382+
std::env::remove_var(vite_shared::env_vars::VITE_PLUS_HOME);
383+
}
384+
385+
// Cache file should be removed
386+
assert!(
387+
std::fs::metadata(cache_file.as_path()).is_err(),
388+
"Cache file should be removed after invalidation"
389+
);
390+
391+
// Loading from removed file should return empty default cache
392+
let loaded_cache = ResolveCache::load(&cache_file);
393+
assert!(loaded_cache.get(&temp_path).is_none(), "Cache should be empty after invalidation");
394+
}
338395
}

crates/vite_global_cli/src/shim/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod cache;
1212
pub(crate) mod dispatch;
1313
pub(crate) mod exec;
1414

15+
pub(crate) use cache::invalidate_cache;
1516
pub use dispatch::dispatch;
1617
use vite_shared::env_vars;
1718

0 commit comments

Comments
 (0)