Skip to content
Closed
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
42 changes: 41 additions & 1 deletion rewatch/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,32 @@ use crate::build::packages;
use crate::config::Config;
use crate::helpers;
use crate::project_context::ProjectContext;
use ahash::AHashMap;
use anyhow::anyhow;
use std::ffi::OsString;
use std::fs;
use std::fs::File;
use std::io::Read;
use std::io::{self, BufRead};
use std::path::{Component, Path, PathBuf};
use std::sync::{LazyLock, RwLock};
use std::time::{SystemTime, UNIX_EPOCH};

pub type StdErr = String;

pub mod deserialize;

// Thread-safe memoization cache for try_package_path results per build.
// Keyed by "{package_config.name}+{package_name}". Value is Some(path) or None (not found).
static TRY_PACKAGE_PATH_CACHE: LazyLock<RwLock<AHashMap<String, Option<PathBuf>>>> =
LazyLock::new(|| RwLock::new(AHashMap::new()));

pub fn reset_try_package_path_cache() {
if let Ok(mut map) = TRY_PACKAGE_PATH_CACHE.write() {
map.clear();
}
}

pub mod emojis {
use console::Emoji;
pub static COMMAND: Emoji<'_, '_> = Emoji("🏃 ", "");
Expand Down Expand Up @@ -114,6 +127,19 @@ pub fn try_package_path(
project_context: &ProjectContext,
package_name: &str,
) -> anyhow::Result<PathBuf> {
// First, attempt to serve from cache
let cache_key = format!("{}+{}", package_config.name, package_name);
if let Ok(cache) = TRY_PACKAGE_PATH_CACHE.read() {
if let Some(cached) = cache.get(&cache_key) {
return match cached {
Some(path) => Ok(path.clone()),
None => Err(anyhow!(
"The package \"{package_name}\" is not found (are node_modules up-to-date?)..."
)),
};
}
}

// package folder + node_modules + package_name
// This can happen in the following scenario:
// The ProjectContext has a MonoRepoContext::MonorepoRoot.
Expand Down Expand Up @@ -147,7 +173,7 @@ pub fn try_package_path(

// root folder + node_modules + package_name
let path_from_root = package_path(project_context.get_root_path(), package_name);
if path_from_current_package.exists() {
let result = if path_from_current_package.exists() {
Ok(path_from_current_package)
} else if path_from_current_config.exists() {
Ok(path_from_current_config)
Expand All @@ -157,7 +183,21 @@ pub fn try_package_path(
Err(anyhow!(
"The package \"{package_name}\" is not found (are node_modules up-to-date?)..."
))
};

// Store in cache
if let Ok(mut cache) = TRY_PACKAGE_PATH_CACHE.write() {
match &result {
Ok(path) => {
cache.insert(cache_key, Some(path.clone()));
}
Err(_) => {
cache.insert(cache_key, None);
Comment on lines +191 to +195

Choose a reason for hiding this comment

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

[P1] Avoid caching missing packages without invalidation

The new global cache records both successful paths and None for failures. Once a lookup stores None, subsequent calls immediately return the cached error without touching the filesystem. Because the cache is never cleared (reset_try_package_path_cache is unused), running rewatch while node_modules are temporarily incomplete—e.g. before or during an install—causes the tool to keep failing to resolve that package until the process is restarted. Previously, the helper retried on each call and would succeed after installation. Consider either invalidating the cache when dependencies change or skipping negative caching.

Useful? React with 👍 / 👎.

}
}
}

result
}

pub fn get_abs_path(path: &Path) -> PathBuf {
Expand Down
Loading