use std::path::Path;
pub mod analysis;
pub mod git;
pub mod glob;
pub mod traits;
pub mod types;
#[cfg(feature = "cargo-workspace")]
pub mod cargo;
#[cfg(feature = "node-workspace")]
pub mod node;
pub use analysis::{
build_dependency_map, find_affected_by_dependencies, find_packages_using_dependencies,
find_transitive_dependents,
};
#[cfg(feature = "git-diff")]
pub use analysis::{get_affected_from_git, get_affected_with_transitive_analysis};
pub use glob::{contains_glob_chars, expand_workspace_globs, get_or_compile_glob};
pub use traits::{
Lockfile, LockfileDiffParser, LockfileEntry, Package, Workspace, parse_dependency_name,
};
pub use types::{AffectedPackageInfo, DependencyKind, ExternalDependency, WorkspaceDependency};
#[cfg(feature = "cargo-workspace")]
pub use cargo::{CargoLockDiffParser, CargoLockfile, CargoPackage, CargoWorkspace};
#[cfg(feature = "node-workspace")]
pub use node::{NodeLockDiffParser, NodeLockfile, NodePackage, NodePackageManager, NodeWorkspace};
type BoxError = Box<dyn std::error::Error + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, clap::ValueEnum)]
pub enum WorkspaceType {
Cargo,
Node,
}
impl WorkspaceType {
#[must_use]
pub const fn display_name(self) -> &'static str {
match self {
Self::Cargo => "Cargo",
Self::Node => "Node.js",
}
}
}
impl std::fmt::Display for WorkspaceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.display_name())
}
}
pub async fn detect_workspaces(
root: &Path,
filter: Option<&[WorkspaceType]>,
) -> Result<Vec<Box<dyn Workspace>>, BoxError> {
log::debug!("Detecting workspaces at: {}", root.display());
let mut workspaces: Vec<Box<dyn Workspace>> = Vec::new();
#[cfg(feature = "cargo-workspace")]
if filter.is_none_or(|f| f.contains(&WorkspaceType::Cargo))
&& let Some(ws) = CargoWorkspace::detect(root).await?
{
log::info!("Detected Cargo workspace at: {}", root.display());
workspaces.push(Box::new(ws));
}
#[cfg(feature = "node-workspace")]
if filter.is_none_or(|f| f.contains(&WorkspaceType::Node))
&& let Some(ws) = NodeWorkspace::detect(root).await?
{
log::info!(
"Detected Node.js workspace ({:?}) at: {}",
ws.package_manager(),
root.display()
);
workspaces.push(Box::new(ws));
}
log::debug!("Found {} workspace(s)", workspaces.len());
Ok(workspaces)
}
#[must_use]
pub fn select_primary_workspace(workspaces: Vec<Box<dyn Workspace>>) -> Option<Box<dyn Workspace>> {
workspaces.into_iter().next()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workspace_type_display() {
assert_eq!(WorkspaceType::Cargo.to_string(), "Cargo");
assert_eq!(WorkspaceType::Node.to_string(), "Node.js");
}
#[test]
fn test_workspace_type_ordering() {
assert!(WorkspaceType::Cargo < WorkspaceType::Node);
}
#[test]
fn test_select_primary_workspace_empty() {
let workspaces: Vec<Box<dyn Workspace>> = vec![];
assert!(select_primary_workspace(workspaces).is_none());
}
}