use clap::Args;
use serde::Serialize;
use homeboy::component;
use homeboy::context;
use homeboy::git;
use homeboy::version;
use super::CmdResult;
#[derive(Args)]
pub struct StatusArgs {
#[arg(long)]
pub uncommitted: bool,
#[arg(long)]
pub needs_bump: bool,
#[arg(long)]
pub ready: bool,
#[arg(long)]
pub docs_only: bool,
#[arg(long, short = 'a')]
pub all: bool,
}
#[derive(Debug, Serialize)]
pub struct StatusOutput {
pub command: &'static str,
pub total: usize,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub uncommitted: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub needs_bump: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub ready_to_deploy: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub docs_only: Vec<String>,
pub clean: usize,
}
pub fn run_json(args: StatusArgs) -> CmdResult<StatusOutput> {
let (context_output, _) = context::run(None)?;
let relevant_ids: std::collections::HashSet<String> = context_output
.matched_components
.iter()
.chain(context_output.contained_components.iter())
.cloned()
.collect();
let all_components = component::list().unwrap_or_default();
let show_all = args.all || relevant_ids.is_empty();
let components: Vec<component::Component> = if show_all {
all_components
} else {
all_components
.into_iter()
.filter(|c| relevant_ids.contains(&c.id))
.collect()
};
let total = components.len();
let mut uncommitted = Vec::new();
let mut needs_bump = Vec::new();
let mut ready_to_deploy = Vec::new();
let mut docs_only = Vec::new();
let mut clean: usize = 0;
for comp in &components {
match classify_component(comp) {
ComponentStatus::Uncommitted => uncommitted.push(comp.id.clone()),
ComponentStatus::NeedsBump => needs_bump.push(comp.id.clone()),
ComponentStatus::DocsOnly => docs_only.push(comp.id.clone()),
ComponentStatus::Clean => ready_to_deploy.push(comp.id.clone()),
ComponentStatus::Unknown => clean += 1,
}
}
let has_filter = args.uncommitted || args.needs_bump || args.ready || args.docs_only;
if has_filter {
if !args.uncommitted {
uncommitted.clear();
}
if !args.needs_bump {
needs_bump.clear();
}
if !args.ready {
ready_to_deploy.clear();
}
if !args.docs_only {
docs_only.clear();
}
}
Ok((
StatusOutput {
command: "status",
total,
uncommitted,
needs_bump,
ready_to_deploy,
docs_only,
clean,
},
0,
))
}
enum ComponentStatus {
Uncommitted,
NeedsBump,
DocsOnly,
Clean,
Unknown,
}
fn classify_component(component: &component::Component) -> ComponentStatus {
let path = &component.local_path;
let current_version = version::read_component_version(component)
.ok()
.map(|info| info.version);
let baseline = match git::detect_baseline_with_version(path, current_version.as_deref()) {
Ok(b) => b,
Err(_) => return ComponentStatus::Unknown,
};
let commits = git::get_commits_since_tag(path, baseline.reference.as_deref())
.ok()
.unwrap_or_default();
let counts = git::categorize_commits(path, &commits);
let uncommitted = git::get_uncommitted_changes(path)
.ok()
.map(|u| u.has_changes)
.unwrap_or(false);
if uncommitted {
ComponentStatus::Uncommitted
} else if counts.code > 0 {
ComponentStatus::NeedsBump
} else if counts.docs_only > 0 {
ComponentStatus::DocsOnly
} else {
ComponentStatus::Clean
}
}