use clap::{Parser, ValueEnum};
use indicatif::{ProgressBar, ProgressStyle};
use miette::{IntoDiagnostic, Result};
use std::path::PathBuf;
use verifyos_cli::core::engine::Engine;
use verifyos_cli::report::{
apply_baseline, build_report, render_json, render_markdown, render_sarif, render_table,
};
use verifyos_cli::rules::ats::AtsAuditRule;
use verifyos_cli::rules::bundle_metadata::BundleMetadataConsistencyRule;
use verifyos_cli::rules::core::{RuleStatus, Severity};
use verifyos_cli::rules::entitlements::EntitlementsMismatchRule;
use verifyos_cli::rules::entitlements::EntitlementsProvisioningMismatchRule;
use verifyos_cli::rules::export_compliance::ExportComplianceRule;
use verifyos_cli::rules::info_plist::InfoPlistCapabilitiesRule;
use verifyos_cli::rules::info_plist::InfoPlistRequiredKeysRule;
use verifyos_cli::rules::info_plist::LSApplicationQueriesSchemesAuditRule;
use verifyos_cli::rules::info_plist::UIRequiredDeviceCapabilitiesAuditRule;
use verifyos_cli::rules::info_plist::UsageDescriptionsRule;
use verifyos_cli::rules::info_plist::UsageDescriptionsValueRule;
use verifyos_cli::rules::nested_bundles::NestedBundleDebugEntitlementRule;
use verifyos_cli::rules::nested_bundles::NestedBundleEntitlementsRule;
use verifyos_cli::rules::permissions::CameraUsageDescriptionRule;
use verifyos_cli::rules::privacy::MissingPrivacyManifestRule;
use verifyos_cli::rules::privacy_manifest::PrivacyManifestCompletenessRule;
use verifyos_cli::rules::private_api::PrivateApiRule;
use verifyos_cli::rules::signing::EmbeddedCodeSignatureTeamRule;
#[derive(Clone, Debug, ValueEnum)]
enum OutputFormat {
Table,
Json,
Sarif,
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(short, long)]
app: PathBuf,
#[arg(long, value_enum, default_value_t = OutputFormat::Table)]
format: OutputFormat,
#[arg(long)]
baseline: Option<PathBuf>,
#[arg(long)]
md_out: Option<PathBuf>,
}
fn main() -> Result<()> {
let args = Args::parse();
let pb = ProgressBar::new_spinner();
pb.set_style(
ProgressStyle::default_spinner()
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
.template("{spinner:.green} [{elapsed_precise}] {msg}")
.into_diagnostic()?,
);
pb.set_message("Analyzing app bundle...");
pb.enable_steady_tick(std::time::Duration::from_millis(100));
let mut engine = Engine::new();
engine.register_rule(Box::new(MissingPrivacyManifestRule));
engine.register_rule(Box::new(PrivacyManifestCompletenessRule));
engine.register_rule(Box::new(CameraUsageDescriptionRule));
engine.register_rule(Box::new(UsageDescriptionsRule));
engine.register_rule(Box::new(UsageDescriptionsValueRule));
engine.register_rule(Box::new(InfoPlistRequiredKeysRule));
engine.register_rule(Box::new(InfoPlistCapabilitiesRule));
engine.register_rule(Box::new(LSApplicationQueriesSchemesAuditRule));
engine.register_rule(Box::new(UIRequiredDeviceCapabilitiesAuditRule));
engine.register_rule(Box::new(ExportComplianceRule));
engine.register_rule(Box::new(AtsAuditRule));
engine.register_rule(Box::new(EntitlementsMismatchRule));
engine.register_rule(Box::new(EntitlementsProvisioningMismatchRule));
engine.register_rule(Box::new(NestedBundleDebugEntitlementRule));
engine.register_rule(Box::new(NestedBundleEntitlementsRule));
engine.register_rule(Box::new(BundleMetadataConsistencyRule));
engine.register_rule(Box::new(PrivateApiRule));
engine.register_rule(Box::new(EmbeddedCodeSignatureTeamRule));
let results = engine
.run(&args.app)
.map_err(|e| miette::miette!("Engine orchestrator failed: {}", e))?;
pb.finish_with_message("Analysis complete!");
let mut report = build_report(results);
let mut suppressed = None;
if let Some(path) = args.baseline {
let baseline_raw = std::fs::read_to_string(path).into_diagnostic()?;
let baseline: verifyos_cli::report::ReportData =
serde_json::from_str(&baseline_raw).into_diagnostic()?;
let summary = apply_baseline(&mut report, &baseline);
suppressed = Some(summary.suppressed);
}
match args.format {
OutputFormat::Table => println!("{}", render_table(&report)),
OutputFormat::Json => println!("{}", render_json(&report).into_diagnostic()?),
OutputFormat::Sarif => println!("{}", render_sarif(&report).into_diagnostic()?),
}
if let Some(path) = args.md_out {
let markdown = render_markdown(&report, suppressed);
std::fs::write(path, markdown).into_diagnostic()?;
}
let has_errors = report.results.iter().any(|r| {
matches!(r.status, RuleStatus::Fail | RuleStatus::Error)
&& matches!(r.severity, Severity::Error)
});
if has_errors {
std::process::exit(1);
}
Ok(())
}