Skip to content

Commit 2de6fdb

Browse files
elrrrrrrrclaude
andcommitted
fix(pm): ensure cache dir is on same drive as project on Windows
Hardlinks cannot cross drive boundaries on Windows. When cache is on C: but project is on D:, utoo falls back to copy (very slow). Detect cross-drive and use a cache dir on the project's drive instead (e.g. D:\.cache\nm). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 69b4ce3 commit 2de6fdb

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

crates/pm/src/util/platform_const.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
//! Platform-specific constants for cross-platform compatibility.
1+
//! Platform-specific constants and utilities for cross-platform compatibility.
2+
3+
use std::path::{Path, PathBuf};
24

35
/// PATH environment variable separator.
46
/// Windows uses `;`, Unix uses `:`.
@@ -8,3 +10,39 @@ pub const PATH_SEPARATOR: &str = if cfg!(windows) { ";" } else { ":" };
810
/// Windows: `<prefix>/node_modules/<pkg>`
911
/// Unix: `<prefix>/lib/node_modules/<pkg>`
1012
pub const GLOBAL_NODE_MODULES: &str = if cfg!(windows) { "node_modules" } else { "lib/node_modules" };
13+
14+
/// Ensure cache directory is on the same drive as the project directory.
15+
///
16+
/// On Windows, hardlinks cannot cross drive boundaries (e.g. cache on C:,
17+
/// project on D:). When a cross-drive situation is detected, returns a
18+
/// cache path on the project's drive instead.
19+
///
20+
/// On Unix, returns the original cache path unchanged.
21+
pub fn ensure_same_drive(cache_dir: &Path, project_dir: &Path) -> PathBuf {
22+
#[cfg(windows)]
23+
{
24+
let cache_prefix = cache_dir
25+
.components()
26+
.next()
27+
.map(|c| c.as_os_str().to_ascii_uppercase());
28+
let project_prefix = project_dir
29+
.components()
30+
.next()
31+
.map(|c| c.as_os_str().to_ascii_uppercase());
32+
33+
if cache_prefix != project_prefix {
34+
if let Some(project_root) = project_dir.components().next() {
35+
let alt_cache = PathBuf::from(project_root.as_os_str()).join(".cache").join("nm");
36+
tracing::debug!(
37+
"Cache dir {} is on a different drive than project {}, using {} instead",
38+
cache_dir.display(),
39+
project_dir.display(),
40+
alt_cache.display()
41+
);
42+
return alt_cache;
43+
}
44+
}
45+
}
46+
let _ = project_dir; // suppress unused warning on non-windows
47+
cache_dir.to_path_buf()
48+
}

crates/pm/src/util/user_config.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,14 @@ pub async fn set_cache_dir(cache_dir: Option<String>) {
140140
}
141141

142142
pub fn get_cache_dir() -> PathBuf {
143-
PathBuf::from(CACHE_DIR.get_sync())
143+
let cache = PathBuf::from(CACHE_DIR.get_sync());
144+
// On Windows, ensure cache is on the same drive as the project
145+
// to allow hardlinks (which can't cross drive boundaries)
146+
if let Ok(cwd) = std::env::current_dir() {
147+
super::platform_const::ensure_same_drive(&cache, &cwd)
148+
} else {
149+
cache
150+
}
144151
}
145152

146153
// Package.json cache — keyed by directory path, covers root + workspace members.

0 commit comments

Comments
 (0)