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