1pub mod cli;
2pub mod compat;
3pub mod explain;
4pub mod identity;
5pub mod index;
6pub mod manifest_edit;
7pub mod metadata;
8pub mod model;
9pub mod report;
10pub mod resolution;
11pub mod temp_workspace;
12
13use anyhow::Result;
14use clap::Parser;
15use cli::{Cli, Commands, OutputFormat, ResolveCommand};
16use compat::analyze_current_workspace;
17use explain::build_explain_report;
18use index::registry_lookup_for_workspace;
19use manifest_edit::{apply_manifest_suggestions, suggest_manifest_changes};
20use metadata::{load_workspace, select_packages};
21use report::{
22 render_explain_report, render_manifest_suggestions_report, render_resolve_report,
23 render_scan_report,
24};
25use resolution::{apply_candidate_lockfile, build_candidate_resolution};
26use std::fs;
27use std::path::PathBuf;
28
29pub fn run() -> Result<()> {
30 let cli = Cli::parse();
31 dispatch(cli)
32}
33
34fn dispatch(cli: Cli) -> Result<()> {
35 match cli.command {
36 Commands::Scan(command) => {
37 let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
38 let selection = select_packages(&workspace, &command.selection)?;
39 let report = analyze_current_workspace(&workspace, &selection)?;
40 print_output(command.format, render_scan_report(&report, command.format)?)?;
41 }
42 Commands::Resolve(command) => {
43 let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
44 let selection = select_packages(&workspace, &command.selection)?;
45 let report = build_candidate_resolution(&workspace, &selection, &command)?;
46 let rendered = render_resolve_report(&report, command.format)?;
47 if let Some(path) = command.write_report.as_ref() {
48 persist_text(path, rendered.as_bytes())?;
49 }
50 if let Some(path) = command.write_candidate.as_ref() {
51 if let Some(candidate) = report.candidate_lockfile.as_ref() {
52 persist_text(path, candidate.as_bytes())?;
53 }
54 }
55 print_output(command.format, rendered)?;
56 }
57 Commands::ApplyLock(command) => {
58 let workspace = load_workspace(command.manifest_path.as_deref())?;
59 let applied = apply_candidate_lockfile(
60 &workspace.workspace_root,
61 command
62 .candidate_lockfile
63 .unwrap_or_else(default_candidate_lockfile_path),
64 )?;
65 println!("{applied}");
66 }
67 Commands::SuggestManifest(command) => {
68 let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
69 let selection = select_packages(&workspace, &command.selection)?;
70 let resolution = build_candidate_resolution(
71 &workspace,
72 &selection,
73 &ResolveCommand {
74 selection: command.selection.clone(),
75 format: command.format,
76 write_candidate: None,
77 write_report: None,
78 },
79 )?;
80 let registry = registry_lookup_for_workspace(&workspace.workspace_root)?;
81 let suggestions = suggest_manifest_changes(
82 &workspace,
83 &selection,
84 &resolution,
85 registry.as_ref(),
86 command.allow_major,
87 )?;
88 if command.write_manifests {
89 apply_manifest_suggestions(&suggestions)?;
90 }
91 print_output(
92 command.format,
93 render_manifest_suggestions_report(
94 &workspace,
95 &selection,
96 &resolution,
97 &suggestions,
98 command.format,
99 command.write_manifests,
100 )?,
101 )?;
102 }
103 Commands::Explain(command) => {
104 let workspace = load_workspace(command.selection.manifest_path.as_deref())?;
105 let selection = select_packages(&workspace, &command.selection)?;
106 let report = build_explain_report(&workspace, &selection, &command)?;
107 print_output(
108 command.format,
109 render_explain_report(&report, command.format)?,
110 )?;
111 }
112 }
113
114 Ok(())
115}
116
117fn persist_text(path: &PathBuf, contents: &[u8]) -> Result<()> {
118 if let Some(parent) = path.parent() {
119 fs::create_dir_all(parent)?;
120 }
121 fs::write(path, contents)?;
122 Ok(())
123}
124
125fn print_output(format: OutputFormat, contents: String) -> Result<()> {
126 match format {
127 OutputFormat::Human | OutputFormat::Markdown | OutputFormat::Json => {
128 println!("{contents}");
129 }
130 }
131
132 Ok(())
133}
134
135fn default_candidate_lockfile_path() -> PathBuf {
136 PathBuf::from(".cargo-compatible/candidate/Cargo.lock")
137}