Skip to main content

aube_runtime/
paths.rs

1//! On-disk layout for aube-managed Node installs and caches. Follows
2//! the same XDG / `%LOCALAPPDATA%` / home-fallback convention as
3//! `aube-store/src/dirs.rs` (kept separate so this crate doesn't pull
4//! in the CAS machinery for three path joins).
5//!
6//! ```text
7//! $XDG_DATA_HOME/<data_namespace>/nodejs/
8//! ├── 24.1.0/              # native layout: unix bin/node, win node.exe
9//! ├── .downloads/          # in-flight archive downloads
10//! ├── .tmp/                # extraction staging (rename source)
11//! └── .locks/              # per-version fslock files
12//! ```
13
14use std::path::PathBuf;
15
16/// Root of aube-managed Node installs. `AUBE_RUNTIME_DIR` overrides
17/// for tests and unusual setups. The data namespace comes from the
18/// active embedder (standalone aube → `"aube"`).
19pub fn runtime_dir() -> Option<PathBuf> {
20    if let Some(dir) = aube_util::env::embedder_env("RUNTIME_DIR")
21        && !dir.is_empty()
22    {
23        return Some(PathBuf::from(dir));
24    }
25    let ns = aube_util::embedder().data_namespace;
26    #[cfg(windows)]
27    if let Ok(local) = std::env::var("LOCALAPPDATA") {
28        return Some(PathBuf::from(local).join(ns).join("nodejs"));
29    }
30    let data_home = match aube_util::env::xdg_data_home() {
31        Some(xdg) => xdg,
32        None => aube_util::env::home_dir()?.join(".local/share"),
33    };
34    Some(data_home.join(ns).join("nodejs"))
35}
36
37/// The install dir for an exact version: `<runtime_dir>/24.1.0/`.
38pub fn install_dir(version: &node_semver::Version) -> Option<PathBuf> {
39    runtime_dir().map(|d| d.join(version.to_string()))
40}
41
42pub(crate) fn downloads_dir() -> Option<PathBuf> {
43    runtime_dir().map(|d| d.join(".downloads"))
44}
45
46pub(crate) fn staging_dir() -> Option<PathBuf> {
47    runtime_dir().map(|d| d.join(".tmp"))
48}
49
50pub(crate) fn locks_dir() -> Option<PathBuf> {
51    runtime_dir().map(|d| d.join(".locks"))
52}
53
54/// Cache directory shared with the rest of aube, mirroring `aube-store`'s
55/// `cache_dir`. Uses the active embedder's `cache_namespace` (standalone aube →
56/// `"aube"`) under `$XDG_CACHE_HOME`, `%LOCALAPPDATA%`, or `~/.cache`.
57pub(crate) fn cache_dir() -> Option<PathBuf> {
58    let ns = aube_util::embedder().cache_namespace;
59    if let Some(xdg) = aube_util::env::xdg_cache_home() {
60        return Some(xdg.join(ns));
61    }
62    #[cfg(windows)]
63    if let Ok(local) = std::env::var("LOCALAPPDATA") {
64        return Some(PathBuf::from(local).join(ns));
65    }
66    aube_util::env::home_dir().map(|h| h.join(".cache").join(ns))
67}
68
69/// Disk cache for the dist index, segmented by mirror origin so two
70/// mirrors never serve each other's entries:
71/// `$XDG_CACHE_HOME/aube/node-index/origin-<sha256-16>/index.json`.
72pub(crate) fn index_cache_path(mirror_base: &str) -> Option<PathBuf> {
73    cache_dir().map(|d| {
74        d.join("node-index")
75            .join(origin_segment(mirror_base))
76            .join("index.json")
77    })
78}
79
80/// Disk cache for a release's SHASUMS256.txt (immutable once
81/// published — cached forever):
82/// `$XDG_CACHE_HOME/aube/node-shasums/origin-<sha256-16>/v24.1.0.txt`.
83pub(crate) fn shasums_cache_path(
84    mirror_base: &str,
85    version: &node_semver::Version,
86) -> Option<PathBuf> {
87    cache_dir().map(|d| {
88        d.join("node-shasums")
89            .join(origin_segment(mirror_base))
90            .join(format!("v{version}.txt"))
91    })
92}
93
94fn origin_segment(mirror_base: &str) -> String {
95    use sha2::Digest;
96    let digest = sha2::Sha256::digest(mirror_base.as_bytes());
97    format!("origin-{}", hex::encode(&digest[..8]))
98}