use std::sync::OnceLock;
use crate::meta;
static GLOBAL_CLI_OVERRIDES: OnceLock<Vec<(String, String)>> = OnceLock::new();
pub fn set_global_cli_overrides(overrides: Vec<(String, String)>) {
let _ = GLOBAL_CLI_OVERRIDES.set(overrides);
}
fn global_cli_overrides() -> &'static [(String, String)] {
GLOBAL_CLI_OVERRIDES.get().map(Vec::as_slice).unwrap_or(&[])
}
pub struct ResolveCtx<'a> {
pub project_aube_config: &'a [(String, String)],
pub project_npmrc: &'a [(String, String)],
pub user_aube_config: &'a [(String, String)],
pub user_npmrc: &'a [(String, String)],
pub workspace_yaml: &'a std::collections::BTreeMap<String, yaml_serde::Value>,
pub env: &'a [(String, String)],
pub cli: &'a [(String, String)],
}
impl<'a> ResolveCtx<'a> {
pub fn files_only(
npmrc: &'a [(String, String)],
workspace_yaml: &'a std::collections::BTreeMap<String, yaml_serde::Value>,
) -> Self {
Self {
project_aube_config: &[],
project_npmrc: npmrc,
user_aube_config: &[],
user_npmrc: &[],
workspace_yaml,
env: &[],
cli: &[],
}
}
}
static PROCESS_ENV: std::sync::LazyLock<Vec<(String, String)>> =
std::sync::LazyLock::new(|| std::env::vars().collect());
pub fn capture_env() -> Vec<(String, String)> {
PROCESS_ENV.clone()
}
pub fn process_env() -> &'static [(String, String)] {
PROCESS_ENV.as_slice()
}
pub mod resolved {
use super::ResolveCtx;
include!(concat!(env!("OUT_DIR"), "/settings_resolved.rs"));
}
pub(crate) fn bool_from_npmrc(setting: &str, entries: &[(String, String)]) -> Option<bool> {
let meta = meta::find(setting)?;
if meta.type_ != "bool" {
return None;
}
for (key, raw) in entries.iter().rev() {
if meta.npmrc_keys.contains(&key.as_str())
&& let Some(v) = parse_bool(raw)
{
return Some(v);
}
}
None
}
pub fn string_from_npmrc(setting: &str, entries: &[(String, String)]) -> Option<String> {
let meta = meta::find(setting)?;
if !is_stringish(meta.type_) {
return None;
}
for (key, raw) in entries.iter().rev() {
if meta.npmrc_keys.contains(&key.as_str()) {
return Some(raw.clone());
}
}
None
}
pub(crate) fn bool_from_workspace_yaml(
setting: &str,
raw: &std::collections::BTreeMap<String, yaml_serde::Value>,
) -> Option<bool> {
let meta = meta::find(setting)?;
if meta.type_ != "bool" {
return None;
}
for key in meta.workspace_yaml_keys {
let Some(val) = workspace_yaml_value(raw, key) else {
continue;
};
match val {
yaml_serde::Value::Bool(b) => return Some(*b),
yaml_serde::Value::String(s) => {
if let Some(b) = parse_bool(s) {
return Some(b);
}
}
_ => {}
}
}
None
}
pub fn string_from_workspace_yaml(
setting: &str,
raw: &std::collections::BTreeMap<String, yaml_serde::Value>,
) -> Option<String> {
let meta = meta::find(setting)?;
if !is_stringish(meta.type_) {
return None;
}
for key in meta.workspace_yaml_keys {
let Some(val) = workspace_yaml_value(raw, key) else {
continue;
};
match val {
yaml_serde::Value::String(s) => return Some(s.clone()),
yaml_serde::Value::Number(n) => return Some(n.to_string()),
yaml_serde::Value::Bool(b) => return Some(b.to_string()),
_ => {}
}
}
None
}
fn is_stringish(ty: &str) -> bool {
matches!(ty, "string" | "path" | "url") || ty.starts_with('"')
}
pub(crate) fn u64_from_npmrc(setting: &str, entries: &[(String, String)]) -> Option<u64> {
let meta = meta::find(setting)?;
if meta.type_ != "int" {
return None;
}
for (key, raw) in entries.iter().rev() {
if meta.npmrc_keys.contains(&key.as_str())
&& let Ok(v) = raw.trim().parse::<u64>()
{
return Some(v);
}
}
None
}
pub(crate) fn u64_from_workspace_yaml(
setting: &str,
raw: &std::collections::BTreeMap<String, yaml_serde::Value>,
) -> Option<u64> {
let meta = meta::find(setting)?;
if meta.type_ != "int" {
return None;
}
for key in meta.workspace_yaml_keys {
let Some(val) = workspace_yaml_value(raw, key) else {
continue;
};
match val {
yaml_serde::Value::Number(n) => {
if let Some(u) = n.as_u64() {
return Some(u);
}
}
yaml_serde::Value::String(s) => {
if let Ok(u) = s.trim().parse::<u64>() {
return Some(u);
}
}
_ => {}
}
}
None
}
pub(crate) fn string_list_from_npmrc(
setting: &str,
entries: &[(String, String)],
) -> Option<Vec<String>> {
let meta = meta::find(setting)?;
if meta.type_ != "list<string>" {
return None;
}
for (key, raw) in entries.iter().rev() {
if meta.npmrc_keys.contains(&key.as_str()) {
return Some(parse_string_list(raw));
}
}
None
}
pub(crate) fn string_list_from_workspace_yaml(
setting: &str,
raw: &std::collections::BTreeMap<String, yaml_serde::Value>,
) -> Option<Vec<String>> {
let meta = meta::find(setting)?;
if meta.type_ != "list<string>" {
return None;
}
for key in meta.workspace_yaml_keys {
let Some(val) = workspace_yaml_value(raw, key) else {
continue;
};
match val {
yaml_serde::Value::Sequence(seq) => {
let items: Vec<String> = seq
.iter()
.filter_map(|v| v.as_str().map(|s| s.to_string()))
.collect();
return Some(items);
}
yaml_serde::Value::String(s) => return Some(parse_string_list(s)),
_ => {}
}
}
None
}
pub fn workspace_yaml_value<'a>(
raw: &'a std::collections::BTreeMap<String, yaml_serde::Value>,
key: &str,
) -> Option<&'a yaml_serde::Value> {
let mut parts = key.split('.');
let first = parts.next()?;
let mut value = raw.get(first)?;
for part in parts {
let yaml_serde::Value::Mapping(map) = value else {
return None;
};
value = map.get(yaml_serde::Value::String(part.to_string()))?;
}
Some(value)
}
fn raw_from_env<'a>(meta: &meta::SettingMeta, env: &'a [(String, String)]) -> Option<&'a str> {
for alias in meta.env_vars.iter().rev() {
for (key, raw) in env.iter().rev() {
if key == alias {
return Some(raw);
}
}
}
None
}
pub(crate) fn bool_from_env(setting: &str, env: &[(String, String)]) -> Option<bool> {
let meta = meta::find(setting)?;
if meta.type_ != "bool" {
return None;
}
raw_from_env(meta, env).and_then(parse_bool)
}
pub fn string_from_env(setting: &str, env: &[(String, String)]) -> Option<String> {
let meta = meta::find(setting)?;
if !is_stringish(meta.type_) {
return None;
}
raw_from_env(meta, env).map(ToOwned::to_owned)
}
pub(crate) fn u64_from_env(setting: &str, env: &[(String, String)]) -> Option<u64> {
let meta = meta::find(setting)?;
if meta.type_ != "int" {
return None;
}
raw_from_env(meta, env).and_then(|raw| raw.trim().parse::<u64>().ok())
}
pub(crate) fn string_list_from_env(setting: &str, env: &[(String, String)]) -> Option<Vec<String>> {
let meta = meta::find(setting)?;
if meta.type_ != "list<string>" {
return None;
}
raw_from_env(meta, env).map(parse_string_list)
}
fn cli_key_matches(key: &str, meta: &meta::SettingMeta) -> bool {
if meta.cli_flags.contains(&key) {
return true;
}
if key == meta.name {
return true;
}
let key_kebab = to_kebab_case(key);
if key_kebab == to_kebab_case(meta.name) {
return true;
}
false
}
fn to_kebab_case(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 4);
let mut prev_lower = false;
for c in s.chars() {
if c == '_' || c == '-' {
if !out.ends_with('-') && !out.is_empty() {
out.push('-');
}
prev_lower = false;
} else if c == '.' {
out.push('.');
prev_lower = false;
} else if c.is_ascii_uppercase() {
if prev_lower {
out.push('-');
}
out.push(c.to_ascii_lowercase());
prev_lower = false;
} else {
out.push(c);
prev_lower = c.is_ascii_lowercase() || c.is_ascii_digit();
}
}
out
}
fn cli_raw_for<'a>(
meta: &meta::SettingMeta,
cli: &'a [(String, String)],
accept: impl Fn(&str) -> bool,
) -> Option<&'a str> {
for (key, raw) in cli.iter().rev() {
if cli_key_matches(key, meta) && accept(raw.as_str()) {
return Some(raw.as_str());
}
}
for (key, raw) in global_cli_overrides().iter().rev() {
if cli_key_matches(key, meta) && accept(raw.as_str()) {
return Some(raw.as_str());
}
}
None
}
pub(crate) fn bool_from_cli(setting: &str, cli: &[(String, String)]) -> Option<bool> {
let meta = meta::find(setting)?;
if meta.type_ != "bool" {
return None;
}
cli_raw_for(meta, cli, |raw| parse_bool(raw).is_some()).and_then(parse_bool)
}
pub fn string_from_cli(setting: &str, cli: &[(String, String)]) -> Option<String> {
let meta = meta::find(setting)?;
if !is_stringish(meta.type_) {
return None;
}
cli_raw_for(meta, cli, |_| true).map(ToOwned::to_owned)
}
pub(crate) fn u64_from_cli(setting: &str, cli: &[(String, String)]) -> Option<u64> {
let meta = meta::find(setting)?;
if meta.type_ != "int" {
return None;
}
cli_raw_for(meta, cli, |raw| raw.trim().parse::<u64>().is_ok())
.and_then(|raw| raw.trim().parse::<u64>().ok())
}
pub(crate) fn string_list_from_cli(setting: &str, cli: &[(String, String)]) -> Option<Vec<String>> {
let meta = meta::find(setting)?;
if meta.type_ != "list<string>" {
return None;
}
cli_raw_for(meta, cli, |_| true).map(parse_string_list)
}
fn parse_string_list(raw: &str) -> Vec<String> {
let trimmed = raw.trim();
if let Some(inner) = trimmed.strip_prefix('[').and_then(|s| s.strip_suffix(']')) {
return inner
.split(',')
.map(|s| {
s.trim()
.trim_matches(|c: char| c == '"' || c == '\'')
.to_string()
})
.filter(|s| !s.is_empty())
.collect();
}
trimmed
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
pub fn parse_bool(s: &str) -> Option<bool> {
match s.trim().to_ascii_lowercase().as_str() {
"true" | "1" => Some(true),
"false" | "0" => Some(false),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::BTreeMap;
fn entries(pairs: &[(&str, &str)]) -> Vec<(String, String)> {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect()
}
#[test]
fn workspace_yaml_value_resolves_dotted_paths() {
let raw: BTreeMap<String, yaml_serde::Value> =
yaml_serde::from_str("outer:\n inner:\n key: value\n").unwrap();
assert_eq!(
workspace_yaml_value(&raw, "outer.inner.key").and_then(|v| v.as_str()),
Some("value")
);
assert!(workspace_yaml_value(&raw, "outer.missing.key").is_none());
}
#[test]
fn resolves_auto_install_peers_kebab_case() {
let e = entries(&[("auto-install-peers", "false")]);
assert_eq!(bool_from_npmrc("autoInstallPeers", &e), Some(false));
}
#[test]
fn resolves_auto_install_peers_camel_case() {
let e = entries(&[("autoInstallPeers", "true")]);
assert_eq!(bool_from_npmrc("autoInstallPeers", &e), Some(true));
}
#[test]
fn resolves_package_manager_strict_kebab_case() {
let e = entries(&[("package-manager-strict", "false")]);
assert_eq!(
string_from_npmrc("packageManagerStrict", &e),
Some("false".to_string())
);
}
#[test]
fn resolves_package_manager_strict_camel_case() {
let e = entries(&[("packageManagerStrict", "warn")]);
assert_eq!(
string_from_npmrc("packageManagerStrict", &e),
Some("warn".to_string())
);
}
#[test]
fn resolves_package_manager_strict_version_kebab_case() {
let e = entries(&[("package-manager-strict-version", "true")]);
assert_eq!(
bool_from_npmrc("packageManagerStrictVersion", &e),
Some(true)
);
}
#[test]
fn resolves_git_shallow_hosts_kebab_case() {
let e = entries(&[("git-shallow-hosts", "[example.invalid, other.test]")]);
assert_eq!(
string_list_from_npmrc("gitShallowHosts", &e),
Some(vec![
"example.invalid".to_string(),
"other.test".to_string(),
])
);
}
#[test]
fn resolves_git_shallow_hosts_camel_case() {
let e = entries(&[("gitShallowHosts", "example.invalid")]);
assert_eq!(
string_list_from_npmrc("gitShallowHosts", &e),
Some(vec!["example.invalid".to_string()])
);
}
#[test]
fn returns_none_when_no_key_matches() {
let e = entries(&[("registry", "https://x.test/")]);
assert_eq!(bool_from_npmrc("autoInstallPeers", &e), None);
}
#[test]
fn returns_none_for_unknown_setting() {
let e = entries(&[("auto-install-peers", "false")]);
assert_eq!(
bool_from_npmrc("totally-fake-setting", &e),
None,
"unknown setting must return None without crashing"
);
}
#[test]
fn parses_numeric_shell_booleans() {
assert_eq!(
bool_from_npmrc("autoInstallPeers", &entries(&[("auto-install-peers", "1")])),
Some(true)
);
assert_eq!(
bool_from_npmrc("autoInstallPeers", &entries(&[("auto-install-peers", "0")])),
Some(false)
);
}
#[test]
fn later_entries_win_over_earlier_ones() {
let e = entries(&[
("auto-install-peers", "false"),
("auto-install-peers", "true"),
]);
assert_eq!(bool_from_npmrc("autoInstallPeers", &e), Some(true));
}
#[test]
fn ignores_unparseable_value_and_falls_back() {
let e = entries(&[
("auto-install-peers", "false"),
("auto-install-peers", "maybe"),
]);
assert_eq!(bool_from_npmrc("autoInstallPeers", &e), Some(false));
}
fn raw_yaml(src: &str) -> std::collections::BTreeMap<String, yaml_serde::Value> {
yaml_serde::from_str(src).expect("test fixture is valid yaml")
}
#[test]
fn workspace_yaml_resolves_bool_field() {
let m = raw_yaml("autoInstallPeers: false\n");
assert_eq!(
bool_from_workspace_yaml("autoInstallPeers", &m),
Some(false)
);
}
#[test]
fn workspace_yaml_returns_none_when_absent() {
let m = raw_yaml("packages:\n - 'pkgs/*'\n");
assert_eq!(bool_from_workspace_yaml("autoInstallPeers", &m), None);
}
#[test]
fn workspace_yaml_accepts_stringified_bool() {
let m = raw_yaml("autoInstallPeers: \"false\"\n");
assert_eq!(
bool_from_workspace_yaml("autoInstallPeers", &m),
Some(false)
);
}
#[test]
fn workspace_yaml_ignores_non_bool_setting() {
let m = raw_yaml("storeDir: /tmp/x\n");
assert_eq!(bool_from_workspace_yaml("storeDir", &m), None);
}
#[test]
fn workspace_yaml_resolves_string_field() {
let m = raw_yaml("storeDir: /tmp/my-store\n");
assert_eq!(
string_from_workspace_yaml("storeDir", &m),
Some("/tmp/my-store".to_string())
);
}
#[test]
fn workspace_yaml_string_ignores_bool_setting() {
let m = raw_yaml("autoInstallPeers: false\n");
assert_eq!(string_from_workspace_yaml("autoInstallPeers", &m), None);
}
#[test]
fn workspace_yaml_resolves_nested_string_list_field() {
let m = raw_yaml("updateConfig:\n ignoreDependencies:\n - is-odd\n - is-even\n");
assert_eq!(
string_list_from_workspace_yaml("updateConfig.ignoreDependencies", &m),
Some(vec!["is-odd".to_string(), "is-even".to_string()])
);
}
#[test]
fn generated_accessor_walks_npmrc_then_workspace_yaml() {
let npmrc = entries(&[("auto-install-peers", "false")]);
let ws = raw_yaml("autoInstallPeers: true\n");
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert!(!resolved::auto_install_peers(&ctx));
}
#[test]
fn generated_accessor_falls_through_to_workspace_yaml() {
let npmrc: Vec<(String, String)> = Vec::new();
let ws = raw_yaml("autoInstallPeers: false\n");
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert!(!resolved::auto_install_peers(&ctx));
}
#[test]
fn generated_accessor_returns_declared_default_when_no_source_matches() {
let npmrc: Vec<(String, String)> = Vec::new();
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert!(resolved::auto_install_peers(&ctx));
}
#[test]
fn env_resolves_auto_install_peers_via_declared_aliases() {
let env_lower = vec![(
"npm_config_auto_install_peers".to_string(),
"false".to_string(),
)];
assert_eq!(bool_from_env("autoInstallPeers", &env_lower), Some(false));
let env_upper = vec![(
"NPM_CONFIG_AUTO_INSTALL_PEERS".to_string(),
"true".to_string(),
)];
assert_eq!(bool_from_env("autoInstallPeers", &env_upper), Some(true));
}
#[test]
fn cli_bag_resolves_resolution_mode_string() {
let cli = vec![("resolution-mode".to_string(), "time-based".to_string())];
assert_eq!(
string_from_cli("resolutionMode", &cli),
Some("time-based".to_string())
);
}
#[test]
fn cli_bag_matches_canonical_name_for_settings_without_declared_cli_alias() {
let kebab = vec![("strict-dep-builds".to_string(), "true".to_string())];
assert_eq!(bool_from_cli("strictDepBuilds", &kebab), Some(true));
let camel = vec![("strictDepBuilds".to_string(), "true".to_string())];
assert_eq!(bool_from_cli("strictDepBuilds", &camel), Some(true));
let screaming = vec![("STRICT_DEP_BUILDS".to_string(), "false".to_string())];
assert_eq!(bool_from_cli("strictDepBuilds", &screaming), Some(false));
}
#[test]
fn cli_bag_keeps_existing_alias_match_for_declared_settings() {
let cli = vec![("verify-store-integrity".to_string(), "true".to_string())];
assert_eq!(bool_from_cli("verifyStoreIntegrity", &cli), Some(true));
}
#[test]
fn cli_bag_falls_through_unparseable_values_to_earlier_valid_entry() {
let cli = vec![
("strictDepBuilds".to_string(), "true".to_string()),
("strictDepBuilds".to_string(), "notabool".to_string()),
];
assert_eq!(bool_from_cli("strictDepBuilds", &cli), Some(true));
let cli = vec![
("network-concurrency".to_string(), "8".to_string()),
("network-concurrency".to_string(), "garbage".to_string()),
];
assert_eq!(u64_from_cli("networkConcurrency", &cli), Some(8));
}
#[test]
fn cli_beats_env_beats_npmrc_beats_workspace_yaml() {
let npmrc = entries(&[("auto-install-peers", "false")]);
let ws = raw_yaml("autoInstallPeers: false\n");
let env = vec![(
"npm_config_auto_install_peers".to_string(),
"false".to_string(),
)];
let cli = vec![("auto-install-peers".to_string(), "true".to_string())];
let ctx = ResolveCtx {
project_aube_config: &[],
project_npmrc: &npmrc,
user_aube_config: &[],
user_npmrc: &[],
workspace_yaml: &ws,
env: &env,
cli: &cli,
};
assert!(resolved::auto_install_peers(&ctx));
}
#[test]
fn env_wins_over_file_sources_when_cli_empty() {
let npmrc = entries(&[("auto-install-peers", "false")]);
let aube_config = entries(&[("autoInstallPeers", "false")]);
let ws = raw_yaml("autoInstallPeers: false\n");
let env = vec![(
"npm_config_auto_install_peers".to_string(),
"true".to_string(),
)];
let ctx = ResolveCtx {
project_aube_config: &aube_config,
project_npmrc: &npmrc,
user_aube_config: &aube_config,
user_npmrc: &npmrc,
workspace_yaml: &ws,
env: &env,
cli: &[],
};
assert!(resolved::auto_install_peers(&ctx));
}
#[test]
fn minimum_release_age_honors_per_setting_precedence_override() {
let aube_config = entries(&[("minimumReleaseAge", "2880")]);
let ws = raw_yaml("minimumReleaseAge: 1440\n");
let ctx = ResolveCtx {
project_aube_config: &[],
project_npmrc: &[],
user_aube_config: &aube_config,
user_npmrc: &[],
workspace_yaml: &ws,
env: &[],
cli: &[],
};
assert_eq!(resolved::minimum_release_age(&ctx), 1440);
let ws = BTreeMap::new();
let ctx = ResolveCtx {
project_aube_config: &[],
project_npmrc: &[],
user_aube_config: &aube_config,
user_npmrc: &[],
workspace_yaml: &ws,
env: &[],
cli: &[],
};
assert_eq!(resolved::minimum_release_age(&ctx), 2880);
}
#[test]
fn user_aube_config_wins_over_user_npmrc_by_default() {
let user_npmrc = entries(&[("auto-install-peers", "false")]);
let user_aube_config = entries(&[("autoInstallPeers", "true")]);
let ws = BTreeMap::new();
let ctx = ResolveCtx {
project_aube_config: &[],
project_npmrc: &[],
user_aube_config: &user_aube_config,
user_npmrc: &user_npmrc,
workspace_yaml: &ws,
env: &[],
cli: &[],
};
assert!(
resolved::auto_install_peers(&ctx),
"user aube_config=true should win over user npmrc=false"
);
}
#[test]
fn project_npmrc_wins_over_user_aube_config_by_default() {
let project_npmrc = entries(&[("auto-install-peers", "false")]);
let user_aube_config = entries(&[("autoInstallPeers", "true")]);
let ws = BTreeMap::new();
let ctx = ResolveCtx {
project_aube_config: &[],
project_npmrc: &project_npmrc,
user_aube_config: &user_aube_config,
user_npmrc: &[],
workspace_yaml: &ws,
env: &[],
cli: &[],
};
assert!(
!resolved::auto_install_peers(&ctx),
"project npmrc=false should win over user aube_config=true"
);
}
#[test]
fn project_aube_config_wins_over_project_npmrc_by_default() {
let project_npmrc = entries(&[("auto-install-peers", "false")]);
let project_aube_config = entries(&[("autoInstallPeers", "true")]);
let ws = BTreeMap::new();
let ctx = ResolveCtx {
project_aube_config: &project_aube_config,
project_npmrc: &project_npmrc,
user_aube_config: &[],
user_npmrc: &[],
workspace_yaml: &ws,
env: &[],
cli: &[],
};
assert!(
resolved::auto_install_peers(&ctx),
"project aube_config=true should win over project npmrc=false"
);
}
#[test]
fn workspace_yaml_wins_over_user_sources_by_default() {
let user_npmrc = entries(&[("auto-install-peers", "true")]);
let user_aube_config = entries(&[("autoInstallPeers", "true")]);
let ws = raw_yaml("autoInstallPeers: false\n");
let ctx = ResolveCtx {
project_aube_config: &[],
project_npmrc: &[],
user_aube_config: &user_aube_config,
user_npmrc: &user_npmrc,
workspace_yaml: &ws,
env: &[],
cli: &[],
};
assert!(
!resolved::auto_install_peers(&ctx),
"workspace yaml should win over user-scope sources"
);
}
#[test]
fn env_alias_order_defines_priority() {
let env = entries(&[
("CI", "true"),
("NPM_CONFIG_CI", "false"),
("npm_config_no_proxy", ".internal"),
]);
assert_eq!(bool_from_env("ci", &env), Some(true));
assert_eq!(
string_from_env("noProxy", &env),
Some(".internal".to_string())
);
}
#[test]
fn generated_enum_accessor_returns_typed_variant() {
let npmrc = entries(&[("resolutionMode", "time-based")]);
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(
resolved::resolution_mode(&ctx),
resolved::ResolutionMode::TimeBased
);
}
#[test]
fn generated_enum_accessor_uses_default_for_unknown_variant() {
let npmrc = entries(&[("nodeLinker", "totally-fake")]);
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(resolved::node_linker(&ctx), resolved::NodeLinker::Isolated);
}
#[test]
fn generated_enum_accessor_preserves_strict_precedence_on_unknown_value() {
let npmrc = entries(&[("nodeLinker", "totally-fake")]);
let ws = raw_yaml("nodeLinker: hoisted\n");
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(
resolved::node_linker(&ctx),
resolved::NodeLinker::Isolated,
".npmrc had a raw value, even if unparseable — it must win \
over pnpm-workspace.yaml and fall back to the generated \
default"
);
}
#[test]
fn generated_enum_accessor_is_case_insensitive() {
let npmrc = entries(&[("nodeLinker", "Hoisted")]);
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(resolved::node_linker(&ctx), resolved::NodeLinker::Hoisted);
}
#[test]
fn generated_enum_accessor_reads_kebab_case_npmrc_alias() {
let npmrc = entries(&[("node-linker", "hoisted")]);
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(resolved::node_linker(&ctx), resolved::NodeLinker::Hoisted);
}
#[test]
fn npmrc_accepts_kebab_alias_for_camel_only_setting() {
let npmrc = entries(&[("virtual-store-dir-max-length", "40")]);
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(resolved::virtual_store_dir_max_length(&ctx), Some(40));
}
#[test]
fn npmrc_accepts_camel_alias_for_kebab_only_setting() {
let npmrc = entries(&[("preferFrozenLockfile", "false")]);
let ws: std::collections::BTreeMap<String, yaml_serde::Value> =
std::collections::BTreeMap::new();
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(resolved::prefer_frozen_lockfile(&ctx), Some(false));
}
#[test]
fn generated_string_accessor_reads_workspace_yaml() {
let npmrc: Vec<(String, String)> = Vec::new();
let ws = raw_yaml("storeDir: /tmp/from-ws\n");
let ctx = ResolveCtx::files_only(&npmrc, &ws);
assert_eq!(resolved::store_dir(&ctx), Some("/tmp/from-ws".to_string()));
}
}