pub mod add;
pub mod approve_builds;
pub mod audit;
pub mod bin;
pub mod cache;
pub mod cat_file;
pub mod cat_index;
pub mod catalogs;
pub mod ci;
pub mod clean;
pub mod completion;
pub mod config;
pub mod create;
pub mod dedupe;
pub mod deploy;
pub mod deprecate;
pub mod dist_tag;
pub mod dlx;
pub mod exec;
pub mod fetch;
pub mod find_hash;
pub mod global;
pub mod ignored_builds;
pub mod import;
pub mod init;
pub mod inject;
pub mod install;
pub mod install_test;
pub mod licenses;
pub mod link;
pub mod list;
pub mod login;
pub mod logout;
pub mod npm_fallback;
pub mod npmrc;
pub mod outdated;
pub mod pack;
pub mod patch;
pub mod patch_commit;
pub mod patch_remove;
pub mod peers;
pub mod prune;
pub mod publish;
pub mod publish_provenance;
pub mod rebuild;
pub mod recursive;
pub mod remove;
pub mod restart;
pub mod root;
pub mod run;
pub mod sbom;
pub mod store;
pub mod undeprecate;
pub mod unlink;
pub mod unpublish;
pub mod update;
pub mod version;
pub mod view;
pub mod why;
use aube_registry::client::RegistryClient;
use aube_registry::config::NpmConfig;
use miette::{Context, IntoDiagnostic, miette};
use std::any::Any;
use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{OnceLock, RwLock};
static GLOBAL_FROZEN: OnceLock<install::GlobalFrozenFlags> = OnceLock::new();
static REGISTRY_OVERRIDE: RwLock<Option<String>> = RwLock::new(None);
#[derive(Copy, Clone, Debug, Default)]
pub(crate) struct GlobalOutputFlags {
pub silent: bool,
}
static GLOBAL_OUTPUT: OnceLock<GlobalOutputFlags> = OnceLock::new();
pub(crate) fn set_registry_override(url: Option<String>) {
*REGISTRY_OVERRIDE.write().expect("registry lock poisoned") =
url.map(|u| aube_registry::config::normalize_registry_url_pub(&u));
}
pub(crate) struct RegistryOverrideGuard {
previous: Option<String>,
changed: bool,
}
impl Drop for RegistryOverrideGuard {
fn drop(&mut self) {
if self.changed {
*REGISTRY_OVERRIDE.write().expect("registry lock poisoned") = self.previous.take();
}
}
}
pub(crate) fn scoped_registry_override(url: Option<String>) -> RegistryOverrideGuard {
let mut guard = REGISTRY_OVERRIDE.write().expect("registry lock poisoned");
let previous = guard.clone();
let changed = url.is_some();
if let Some(u) = url {
*guard = Some(aube_registry::config::normalize_registry_url_pub(&u));
}
RegistryOverrideGuard { previous, changed }
}
pub(crate) fn registry_override() -> Option<String> {
REGISTRY_OVERRIDE
.read()
.expect("registry lock poisoned")
.clone()
}
pub(crate) fn load_npm_config(dir: &std::path::Path) -> NpmConfig {
let mut config = NpmConfig::load(dir);
if let Some(url) = registry_override() {
config.registry = url;
}
config
}
pub(crate) fn set_global_frozen_flags(flags: install::GlobalFrozenFlags) {
let _ = GLOBAL_FROZEN.set(flags);
}
pub(crate) fn set_global_output_flags(flags: GlobalOutputFlags) {
let _ = GLOBAL_OUTPUT.set(flags);
}
pub(crate) fn global_frozen_flags() -> install::GlobalFrozenFlags {
GLOBAL_FROZEN.get().copied().unwrap_or_default()
}
pub(crate) fn global_output_flags() -> GlobalOutputFlags {
GLOBAL_OUTPUT.get().copied().unwrap_or_default()
}
pub(crate) fn configure_script_settings(ctx: &aube_settings::ResolveCtx<'_>) {
let node_options = aube_settings::resolved::node_options(ctx).and_then(non_empty_string);
let script_shell = aube_settings::resolved::script_shell(ctx)
.and_then(|s| non_empty_string(s).map(Into::into));
let unsafe_perm = aube_settings::resolved::unsafe_perm(ctx);
let shell_emulator = aube_settings::resolved::shell_emulator(ctx);
aube_scripts::set_script_settings(aube_scripts::ScriptSettings {
node_options,
script_shell,
unsafe_perm,
shell_emulator,
});
}
fn non_empty_string(value: String) -> Option<String> {
let trimmed = value.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
}
pub(crate) fn retarget_cwd(path: &Path) -> miette::Result<()> {
let path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir().into_diagnostic()?.join(path)
};
std::env::set_current_dir(&path)
.into_diagnostic()
.wrap_err_with(|| format!("failed to chdir into {}", path.display()))?;
crate::dirs::set_cwd(&path)?;
Ok(())
}
pub(crate) fn chained_frozen_mode(default: install::FrozenMode) -> install::FrozenMode {
let g = global_frozen_flags();
if g.frozen || g.no_frozen || g.prefer_frozen {
install::FrozenMode::from_flags(g.frozen, g.no_frozen, g.prefer_frozen, None)
} else {
default
}
}
pub(crate) fn ensure_registry_auth(
client: &RegistryClient,
registry_url: &str,
) -> miette::Result<()> {
if client.has_resolved_auth_for(registry_url) {
Ok(())
} else {
Err(miette!(
"no auth token for {registry_url}. Run `aube login --registry {registry_url}` first."
))
}
}
static LOCK_HELD: AtomicBool = AtomicBool::new(false);
fn aube_no_lock_enabled(cwd: &std::path::Path) -> bool {
with_settings_ctx(cwd, aube_settings::resolved::aube_no_lock)
}
pub(crate) struct ProjectLock {
_inner: Option<Box<dyn Any + Send>>,
owns_flag: bool,
}
impl Drop for ProjectLock {
fn drop(&mut self) {
if self.owns_flag {
LOCK_HELD.store(false, Ordering::Release);
}
}
}
pub(crate) fn take_project_lock(cwd: &std::path::Path) -> miette::Result<ProjectLock> {
if aube_no_lock_enabled(cwd) {
return Ok(ProjectLock {
_inner: None,
owns_flag: false,
});
}
if LOCK_HELD.load(Ordering::Acquire) {
return Ok(ProjectLock {
_inner: None,
owns_flag: false,
});
}
let nm_path = project_modules_dir(cwd);
let lock = xx::fslock::FSLock::new(&nm_path)
.with_callback(|_| {
eprintln!("Waiting for another aube process to finish in this project...");
})
.lock()
.map_err(|e| miette!("failed to acquire project lock: {e}"))?;
LOCK_HELD.store(true, Ordering::Release);
Ok(ProjectLock {
_inner: Some(Box::new(lock)),
owns_flag: true,
})
}
pub(crate) fn open_store(cwd: &std::path::Path) -> miette::Result<aube_store::Store> {
if let Some(custom) = resolved_store_dir(cwd) {
aube_store::Store::with_root(custom.join("v1").join("files"))
.into_diagnostic()
.wrap_err("failed to open store")
} else {
aube_store::Store::default_location()
.into_diagnostic()
.wrap_err("failed to open store")
}
}
fn resolved_store_dir(cwd: &std::path::Path) -> Option<std::path::PathBuf> {
with_settings_ctx(cwd, |ctx| {
let raw = aube_settings::resolved::store_dir(ctx)?;
expand_setting_path(&raw, cwd)
})
}
pub(crate) fn expand_setting_path(raw: &str, cwd: &std::path::Path) -> Option<std::path::PathBuf> {
let expanded = if let Some(rest) = raw.strip_prefix("~/") {
std::path::PathBuf::from(std::env::var_os("HOME")?).join(rest)
} else if raw == "~" {
std::path::PathBuf::from(std::env::var_os("HOME")?)
} else {
std::path::PathBuf::from(raw)
};
Some(if expanded.is_absolute() {
expanded
} else {
cwd.join(expanded)
})
}
pub(crate) fn with_settings_ctx<T>(
cwd: &std::path::Path,
f: impl FnOnce(&aube_settings::ResolveCtx<'_>) -> T,
) -> T {
let npmrc = aube_registry::config::load_npmrc_entries(cwd);
let raw_workspace = aube_manifest::workspace::load_raw(cwd).unwrap_or_default();
let env = aube_settings::values::capture_env();
let ctx = aube_settings::ResolveCtx {
npmrc: &npmrc,
workspace_yaml: &raw_workspace,
env: &env,
cli: &[],
};
f(&ctx)
}
pub(crate) fn make_client(cwd: &std::path::Path) -> aube_registry::client::RegistryClient {
let config = load_npm_config(cwd);
tracing::debug!("registry: {}", config.registry);
for (scope, url) in &config.scoped_registries {
tracing::debug!("scoped registry: {scope} -> {url}");
}
let policy = resolve_fetch_policy(cwd);
aube_registry::client::RegistryClient::from_config_with_policy(config, policy)
}
pub(crate) fn resolve_fetch_policy(cwd: &std::path::Path) -> aube_registry::config::FetchPolicy {
let npmrc = aube_registry::config::load_npmrc_entries(cwd);
let workspace_yaml = aube_manifest::workspace::load_both(cwd)
.map(|(_, raw)| raw)
.unwrap_or_default();
let env = aube_settings::values::capture_env();
let ctx = aube_settings::ResolveCtx {
npmrc: &npmrc,
workspace_yaml: &workspace_yaml,
env: &env,
cli: &[],
};
aube_registry::config::FetchPolicy::from_ctx(&ctx)
}
pub(crate) fn resolved_cache_dir(cwd: &std::path::Path) -> std::path::PathBuf {
let platform_default =
|| aube_store::dirs::cache_dir().unwrap_or_else(|| std::env::temp_dir().join("aube"));
let npmrc = aube_registry::config::load_npmrc_entries(cwd);
let has_explicit = npmrc
.iter()
.any(|(k, _)| k == "cacheDir" || k == "cache-dir");
if !has_explicit {
return platform_default();
}
with_settings_ctx(cwd, |ctx| {
let raw = aube_settings::resolved::cache_dir(ctx);
expand_setting_path(&raw, cwd).unwrap_or_else(platform_default)
})
}
pub(crate) fn resolve_virtual_store_dir_max_length(ctx: &aube_settings::ResolveCtx<'_>) -> usize {
aube_settings::resolved::virtual_store_dir_max_length(ctx)
.map(|v| v as usize)
.unwrap_or(aube_lockfile::dep_path_filename::DEFAULT_VIRTUAL_STORE_DIR_MAX_LENGTH)
}
pub(crate) fn resolve_virtual_store_dir_max_length_for_cwd(cwd: &std::path::Path) -> usize {
with_settings_ctx(cwd, resolve_virtual_store_dir_max_length)
}
pub(crate) fn resolve_modules_dir_name_for_cwd(cwd: &std::path::Path) -> String {
with_settings_ctx(cwd, aube_settings::resolved::modules_dir)
}
pub(crate) fn project_modules_dir(cwd: &std::path::Path) -> std::path::PathBuf {
cwd.join(resolve_modules_dir_name_for_cwd(cwd))
}
pub(crate) fn resolve_virtual_store_dir(
ctx: &aube_settings::ResolveCtx<'_>,
project_dir: &std::path::Path,
) -> std::path::PathBuf {
let default_from_modules_dir = || {
let modules_dir = aube_settings::resolved::modules_dir(ctx);
project_dir.join(modules_dir).join(".aube")
};
let has_explicit_npmrc = ctx
.npmrc
.iter()
.any(|(k, _)| k == "virtualStoreDir" || k == "virtual-store-dir");
let has_explicit_yaml = ctx.workspace_yaml.contains_key("virtualStoreDir");
let has_explicit_env = ctx
.env
.iter()
.any(|(k, _)| k == "npm_config_virtual_store_dir" || k == "NPM_CONFIG_VIRTUAL_STORE_DIR");
if !(has_explicit_npmrc || has_explicit_yaml || has_explicit_env) {
return default_from_modules_dir();
}
let raw = aube_settings::resolved::virtual_store_dir(ctx);
expand_setting_path(&raw, project_dir).unwrap_or_else(default_from_modules_dir)
}
pub(crate) fn resolve_virtual_store_dir_for_cwd(cwd: &std::path::Path) -> std::path::PathBuf {
with_settings_ctx(cwd, |ctx| resolve_virtual_store_dir(ctx, cwd))
}
pub(crate) fn format_virtual_store_display_prefix(
aube_dir: &std::path::Path,
ref_dir: &std::path::Path,
) -> String {
if let Some(rel) = pathdiff::diff_paths(aube_dir, ref_dir)
&& !rel.as_os_str().is_empty()
&& !rel
.components()
.any(|c| matches!(c, std::path::Component::ParentDir))
{
return format!("./{}/", rel.display());
}
format!("{}/", aube_dir.display())
}
pub(crate) fn packument_cache_dir() -> std::path::PathBuf {
let cwd = crate::dirs::cwd().unwrap_or_else(|_| std::env::current_dir().unwrap_or_default());
resolved_cache_dir(&cwd).join("packuments-v1")
}
pub(crate) fn packument_full_cache_dir() -> std::path::PathBuf {
let cwd = crate::dirs::cwd().unwrap_or_else(|_| std::env::current_dir().unwrap_or_default());
resolved_cache_dir(&cwd).join("packuments-full-v1")
}
pub(crate) type CatalogMap =
std::collections::BTreeMap<String, std::collections::BTreeMap<String, String>>;
fn merge_catalog_source(
out: &mut CatalogMap,
default_cat: &std::collections::BTreeMap<String, String>,
named_cats: &CatalogMap,
) {
if !default_cat.is_empty() {
let entry = out.entry("default".to_string()).or_default();
for (k, v) in default_cat {
entry.insert(k.clone(), v.clone());
}
}
for (name, entries) in named_cats {
let bucket = out.entry(name.clone()).or_default();
for (k, v) in entries {
bucket.insert(k.clone(), v.clone());
}
}
}
fn merge_manifest_catalogs(out: &mut CatalogMap, manifest: &aube_manifest::PackageJson) {
if let Some(ws) = &manifest.workspaces {
merge_catalog_source(out, ws.catalog(), ws.catalogs());
}
merge_catalog_source(out, &manifest.pnpm_catalog(), &manifest.pnpm_catalogs());
}
fn find_workspaces_field_root(start: &std::path::Path) -> Option<std::path::PathBuf> {
start.ancestors().find_map(|dir| {
let pkg = dir.join("package.json");
if !pkg.is_file() {
return None;
}
let manifest = aube_manifest::PackageJson::from_path(&pkg).ok()?;
manifest.workspaces.as_ref()?;
Some(dir.to_path_buf())
})
}
pub(crate) fn discover_catalogs(project_root: &std::path::Path) -> miette::Result<CatalogMap> {
use miette::{Context, IntoDiagnostic};
let mut out = CatalogMap::new();
let project_manifest_path = project_root.join("package.json");
let project_manifest = aube_manifest::PackageJson::from_path(&project_manifest_path).ok();
if let Some(m) = &project_manifest {
merge_manifest_catalogs(&mut out, m);
}
let workspace_yaml_dir = crate::dirs::find_workspace_root(project_root);
let workspace_root_dir = workspace_yaml_dir
.clone()
.or_else(|| find_workspaces_field_root(project_root));
if let Some(dir) = &workspace_root_dir
&& dir != project_root
&& let Ok(m) = aube_manifest::PackageJson::from_path(&dir.join("package.json"))
{
merge_manifest_catalogs(&mut out, &m);
}
let yaml_dir = workspace_yaml_dir.as_deref().unwrap_or(project_root);
let (ws_config, _raw) = aube_manifest::workspace::load_both(yaml_dir)
.into_diagnostic()
.wrap_err("failed to load workspace config")?;
merge_catalog_source(&mut out, &ws_config.catalog, &ws_config.catalogs);
out.retain(|_, v| !v.is_empty());
Ok(out)
}
pub(crate) fn load_workspace_catalogs(cwd: &std::path::Path) -> miette::Result<CatalogMap> {
discover_catalogs(cwd)
}
pub(crate) fn find_workspace_root(start: &std::path::Path) -> miette::Result<std::path::PathBuf> {
crate::dirs::find_workspace_root(start).ok_or_else(|| {
miette!(
"no aube-workspace.yaml or pnpm-workspace.yaml found above {}",
start.display()
)
})
}
pub(crate) fn select_workspace_packages(
cwd: &std::path::Path,
filter: &aube_workspace::selector::EffectiveFilter,
command: &str,
) -> miette::Result<Vec<aube_workspace::selector::SelectedPackage>> {
let workspace_pkgs = aube_workspace::find_workspace_packages(cwd)
.map_err(|e| miette!("failed to discover workspace packages: {e}"))?;
if workspace_pkgs.is_empty() {
return Err(miette!(
"aube {command}: --filter requires a pnpm-workspace.yaml at {}",
cwd.display()
));
}
let matched = aube_workspace::selector::select_workspace_packages(cwd, &workspace_pkgs, filter)
.map_err(|e| miette!("invalid --filter selector: {e}"))?;
if matched.is_empty() {
return Err(miette!(
"aube {command}: filter {filter:?} did not match any workspace package"
));
}
Ok(matched)
}
pub(crate) fn resolve_version(packument: &serde_json::Value, spec: Option<&str>) -> Option<String> {
let dist_tags = packument.get("dist-tags").and_then(|v| v.as_object());
let versions = packument.get("versions").and_then(|v| v.as_object())?;
let spec = match spec {
None | Some("") => {
return dist_tags?
.get("latest")
.and_then(|v| v.as_str())
.map(String::from);
}
Some(s) => s,
};
if let Some(tag) = dist_tags.and_then(|t| t.get(spec)).and_then(|v| v.as_str()) {
return Some(tag.to_string());
}
if versions.contains_key(spec) {
return Some(spec.to_string());
}
let range: node_semver::Range = spec.parse().ok()?;
versions
.keys()
.filter_map(|v| {
v.parse::<node_semver::Version>()
.ok()
.filter(|parsed| parsed.satisfies(&range))
.map(|parsed| (v.clone(), parsed))
})
.max_by(|a, b| a.1.cmp(&b.1))
.map(|(raw, _)| raw)
}
pub(crate) fn split_name_spec(input: &str) -> (&str, Option<&str>) {
if let Some(rest) = input.strip_prefix('@') {
if let Some(slash) = rest.find('/') {
let after_slash = &rest[slash + 1..];
if let Some(at) = after_slash.find('@') {
let name_end = 1 + slash + 1 + at;
return (&input[..name_end], Some(&input[name_end + 1..]));
}
}
return (input, None);
}
if let Some(at) = input.find('@') {
return (&input[..at], Some(&input[at + 1..]));
}
(input, None)
}
pub(crate) fn encode_package_name(name: &str) -> String {
if let Some(rest) = name.strip_prefix('@')
&& let Some((scope, pkg)) = rest.split_once('/')
{
return format!("@{scope}%2F{pkg}");
}
name.to_string()
}
#[cfg(test)]
mod encode_package_name_tests {
use super::encode_package_name;
#[test]
fn scoped_name_encodes_slash() {
assert_eq!(encode_package_name("@scope/pkg"), "@scope%2Fpkg");
}
#[test]
fn plain_name_passthrough() {
assert_eq!(encode_package_name("lodash"), "lodash");
}
#[test]
fn malformed_scoped_name_passthrough() {
assert_eq!(encode_package_name("@scope"), "@scope");
}
}
#[cfg(test)]
mod split_name_spec_tests {
use super::split_name_spec;
#[test]
fn plain_name() {
assert_eq!(split_name_spec("lodash"), ("lodash", None));
}
#[test]
fn name_with_version() {
assert_eq!(
split_name_spec("lodash@4.17.21"),
("lodash", Some("4.17.21"))
);
}
#[test]
fn name_with_range() {
assert_eq!(split_name_spec("lodash@^4"), ("lodash", Some("^4")));
}
#[test]
fn name_with_tag() {
assert_eq!(split_name_spec("react@next"), ("react", Some("next")));
}
#[test]
fn scoped_no_version() {
assert_eq!(split_name_spec("@babel/core"), ("@babel/core", None));
}
#[test]
fn scoped_with_version() {
assert_eq!(
split_name_spec("@babel/core@7.0.0"),
("@babel/core", Some("7.0.0"))
);
}
}
pub(crate) async fn ensure_installed(no_install: bool) -> miette::Result<()> {
if no_install {
return Ok(());
}
let initial_cwd = crate::dirs::cwd()?;
let cwd = crate::dirs::find_project_root(&initial_cwd).unwrap_or(initial_cwd);
let (skip_auto_install, optimistic_repeat) = with_settings_ctx(&cwd, |ctx| {
(
aube_settings::resolved::aube_no_auto_install(ctx),
aube_settings::resolved::optimistic_repeat_install(ctx),
)
});
if skip_auto_install {
return Ok(());
}
let g = global_frozen_flags();
let needs = if optimistic_repeat {
crate::state::check_needs_install(&cwd)
} else {
Some("optimisticRepeatInstall=false".to_string())
};
let verify_mode = resolve_verify_deps_before_run(&cwd)?;
let forced_flag = if g.frozen {
Some("--frozen-lockfile")
} else if g.no_frozen {
Some("--no-frozen-lockfile")
} else if g.prefer_frozen {
Some("--prefer-frozen-lockfile")
} else {
None
};
let Some(reason) = needs.or_else(|| forced_flag.map(|f| format!("global {f} flag"))) else {
return Ok(());
};
match verify_mode {
VerifyDepsBeforeRun::Skip => return Ok(()),
VerifyDepsBeforeRun::Warn => {
eprintln!("Dependencies need install before run: {reason}");
return Ok(());
}
VerifyDepsBeforeRun::Error => {
return Err(miette!(
"dependencies need install before run: {reason}\nRun `aube install`, or set verifyDepsBeforeRun=install to let aube do it automatically."
));
}
VerifyDepsBeforeRun::Install => {}
}
eprintln!("Auto-installing: {reason}");
let mode = chained_frozen_mode(install::FrozenMode::Prefer);
let mut opts = install::InstallOptions::with_mode(mode);
opts.strict_no_lockfile = g.frozen;
install::run(opts).await?;
Ok(())
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum VerifyDepsBeforeRun {
Install,
Warn,
Error,
Skip,
}
fn resolve_verify_deps_before_run(cwd: &std::path::Path) -> miette::Result<VerifyDepsBeforeRun> {
let npmrc = aube_registry::config::load_npmrc_entries(cwd);
let empty_ws = std::collections::BTreeMap::new();
let env = aube_settings::values::capture_env();
let ctx = aube_settings::ResolveCtx {
npmrc: &npmrc,
workspace_yaml: &empty_ws,
env: &env,
cli: &[],
};
let raw = aube_settings::resolved::verify_deps_before_run(&ctx);
Ok(match raw.trim().to_ascii_lowercase().as_str() {
"false" | "0" => VerifyDepsBeforeRun::Skip,
"warn" => VerifyDepsBeforeRun::Warn,
"error" => VerifyDepsBeforeRun::Error,
"prompt" | "install" => VerifyDepsBeforeRun::Install,
_ => VerifyDepsBeforeRun::Install,
})
}
pub(crate) fn remove_existing(path: &std::path::Path) -> miette::Result<()> {
if path.symlink_metadata().is_err() {
return Ok(());
}
if path.is_dir() && !path.is_symlink() {
std::fs::remove_dir_all(path)
.into_diagnostic()
.wrap_err_with(|| format!("failed to remove {}", path.display()))?;
} else {
std::fs::remove_file(path)
.into_diagnostic()
.wrap_err_with(|| format!("failed to remove {}", path.display()))?;
}
Ok(())
}
pub(crate) fn workspace_importer_path(
workspace_root: &std::path::Path,
dir: &std::path::Path,
) -> miette::Result<String> {
let rel = dir.strip_prefix(workspace_root).map_err(|_| {
miette!(
"workspace package {} is outside {}",
dir.display(),
workspace_root.display()
)
})?;
if rel.as_os_str().is_empty() {
Ok(".".to_string())
} else {
Ok(rel.to_string_lossy().replace('\\', "/"))
}
}
pub(crate) fn symlink_dir(src: &std::path::Path, dst: &std::path::Path) -> std::io::Result<()> {
aube_linker::create_dir_link(src, dst)
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum DepFilter {
All,
ProdOnly,
DevOnly,
}
impl DepFilter {
pub(crate) fn from_flags(prod: bool, dev: bool) -> Self {
match (prod, dev) {
(true, _) => Self::ProdOnly,
(_, true) => Self::DevOnly,
_ => Self::All,
}
}
pub(crate) fn keeps(self, dep_type: aube_lockfile::DepType) -> bool {
use aube_lockfile::DepType;
matches!(
(self, dep_type),
(Self::All, _)
| (Self::ProdOnly, DepType::Production | DepType::Optional)
| (Self::DevOnly, DepType::Dev)
)
}
}
#[cfg(test)]
mod dep_filter_tests {
use super::*;
use aube_lockfile::DepType;
#[test]
fn all_keeps_everything() {
let f = DepFilter::from_flags(false, false);
assert!(f.keeps(DepType::Production));
assert!(f.keeps(DepType::Dev));
assert!(f.keeps(DepType::Optional));
}
#[test]
fn prod_keeps_production_and_optional() {
let f = DepFilter::from_flags(true, false);
assert!(f.keeps(DepType::Production));
assert!(f.keeps(DepType::Optional));
assert!(!f.keeps(DepType::Dev));
}
#[test]
fn dev_keeps_only_dev() {
let f = DepFilter::from_flags(false, true);
assert!(!f.keeps(DepType::Production));
assert!(!f.keeps(DepType::Optional));
assert!(f.keeps(DepType::Dev));
}
#[test]
fn prod_wins_over_dev_when_both_set() {
let f = DepFilter::from_flags(true, true);
assert!(f.keeps(DepType::Production));
assert!(!f.keeps(DepType::Dev));
}
}