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