Skip to main content

cargo_compatible/
lib.rs

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}