Skip to main content

aube_runtime/
lib.rs

1//! Node.js runtime management for aube: resolve a project's requested
2//! Node version (devEngines.runtime / `.node-version` / `.nvmrc`),
3//! discover satisfying installs (PATH, mise, aube's own runtime dir),
4//! and download missing versions — delegating to mise by default when
5//! it's present so mise users keep one Node store.
6//!
7//! This crate is policy-light plumbing: settings resolution, manifest
8//! parsing, PATH injection, and lockfile recording live in the CLI
9//! crate. Nothing here prints; progress flows through
10//! [`DownloadProgress`] and diagnostics through `tracing`.
11
12mod discover;
13mod error;
14mod extract;
15mod http;
16mod index;
17mod installer;
18mod mise;
19mod paths;
20mod platform;
21mod progress;
22mod resolver;
23mod self_install;
24mod shasums;
25mod sources;
26mod spec;
27
28pub use discover::{
29    InstallOrigin, InstalledNode, list_installed, mise_node_installs_dir, probe_path_node,
30};
31pub use error::Error;
32pub use mise::mise_on_path;
33pub use paths::{install_dir, runtime_dir};
34pub use platform::Platform;
35pub use progress::{DownloadProgress, InstallPhase, NoopProgress};
36pub use resolver::{NodeRuntime, Resolution, ResolvedFrom};
37pub use self_install::{
38    InstalledAube, available_aube_versions, find_installed_aube, install_aube, list_installed_aube,
39    release_target_triple, self_dir,
40};
41pub use shasums::{sha256_from_sri, sri_sha256};
42pub use sources::{effective_request, find_version_file};
43pub use spec::{NodeRequest, NodeSpec, RequestSource};
44
45use std::collections::BTreeMap;
46
47/// Default download base, matching pnpm's runtime resolver (the
48/// `/download/release` tree mirrors `/dist` and is what pnpm records
49/// in lockfiles).
50pub const DEFAULT_MIRROR_BASE: &str = "https://nodejs.org/download/release";
51
52/// musl builds aren't published on the official mirror; pnpm and mise
53/// both source them from unofficial-builds. Used only when no custom
54/// mirror is configured.
55pub const UNOFFICIAL_BASE: &str = "https://unofficial-builds.nodejs.org/download/release";
56
57/// Who installs a missing runtime (the `runtimeInstaller` setting).
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59pub enum InstallerMode {
60    /// Delegate to `mise install` when mise is on PATH, else download.
61    #[default]
62    Auto,
63    /// Always delegate; fail if mise is missing.
64    Mise,
65    /// Never delegate.
66    Aube,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
70pub enum NetworkMode {
71    #[default]
72    Online,
73    /// Serve caches regardless of staleness; never touch the network.
74    Offline,
75}
76
77/// Configuration for a [`NodeRuntime`].
78#[derive(Debug, Clone, Default)]
79pub struct RuntimeConfig {
80    pub installer: InstallerMode,
81    /// `nodeDownloadMirrors.release` — replaces the official base for
82    /// index, checksums, and artifacts when set.
83    pub mirror: Option<String>,
84    pub network: NetworkMode,
85    /// Extra request retries after the first attempt (default 2).
86    pub retries: u32,
87}
88
89impl RuntimeConfig {
90    pub fn new() -> Self {
91        RuntimeConfig {
92            retries: 2,
93            ..Default::default()
94        }
95    }
96
97    /// The base URL for the index and (non-musl) artifacts, without a
98    /// trailing slash.
99    pub(crate) fn mirror_base(&self) -> String {
100        match &self.mirror {
101            Some(m) => m.trim_end_matches('/').to_string(),
102            None => DEFAULT_MIRROR_BASE.to_string(),
103        }
104    }
105
106    /// The base URL artifacts and checksums are fetched from for
107    /// `platform`: the configured mirror always wins (a corporate
108    /// mirror may host musl artifacts); otherwise musl platforms
109    /// route to unofficial-builds.
110    pub(crate) fn artifact_base(&self, platform: &Platform) -> String {
111        if self.mirror.is_none() && platform.libc.as_deref() == Some("musl") {
112            UNOFFICIAL_BASE.to_string()
113        } else {
114            self.mirror_base()
115        }
116    }
117}
118
119/// An exact resolved version plus its per-platform artifacts —
120/// the interchange shape for lockfile pins (the CLI maps this onto
121/// `aube_lockfile::RuntimePin`).
122#[derive(Debug, Clone, PartialEq, Eq)]
123pub struct PinnedNode {
124    pub version: node_semver::Version,
125    pub variants: Vec<PinnedVariant>,
126}
127
128impl PinnedNode {
129    pub fn variant_for(&self, os: &str, cpu: &str, libc: Option<&str>) -> Option<&PinnedVariant> {
130        self.variants
131            .iter()
132            .find(|v| v.os == os && v.cpu == cpu && v.libc.as_deref() == libc)
133    }
134}
135
136/// One platform's artifact in a [`PinnedNode`]. Vocabulary matches
137/// pnpm's lockfile (`os: win32`, `archive: tarball|zip`,
138/// `integrity: sha256-<base64>`).
139#[derive(Debug, Clone, PartialEq, Eq)]
140pub struct PinnedVariant {
141    pub os: String,
142    pub cpu: String,
143    pub libc: Option<String>,
144    pub archive: String,
145    pub url: String,
146    pub integrity_sri: String,
147    pub bin: BTreeMap<String, String>,
148    pub prefix: Option<String>,
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn mirror_bases() {
157        let mut cfg = RuntimeConfig::new();
158        assert_eq!(cfg.mirror_base(), DEFAULT_MIRROR_BASE);
159        cfg.mirror = Some("https://npmmirror.com/mirrors/node/".to_string());
160        assert_eq!(cfg.mirror_base(), "https://npmmirror.com/mirrors/node");
161
162        let musl = Platform {
163            os: "linux".into(),
164            cpu: "x64".into(),
165            libc: Some("musl".into()),
166        };
167        // Custom mirror wins even for musl.
168        assert_eq!(
169            cfg.artifact_base(&musl),
170            "https://npmmirror.com/mirrors/node"
171        );
172        cfg.mirror = None;
173        assert_eq!(cfg.artifact_base(&musl), UNOFFICIAL_BASE);
174        let glibc = Platform {
175            os: "linux".into(),
176            cpu: "x64".into(),
177            libc: None,
178        };
179        assert_eq!(cfg.artifact_base(&glibc), DEFAULT_MIRROR_BASE);
180    }
181}