use std::fs::File;
use std::io::BufWriter;
use std::io::{self};
use std::path::PathBuf;
use std::process::ExitCode;
use apollo_federation::connectors::migration::AGENT_GUIDE;
use apollo_federation::connectors::migration::analyze;
use clap::Parser;
use clap::Subcommand;
use clap::ValueEnum;
const VERSION: &str = match option_env!("CONNECT_MIGRATE_VERSION") {
Some(v) => v,
None => "0.0.0-dev",
};
#[derive(Parser, Debug)]
#[command(
name = "connect-migrate",
about = "Help upgrade Apollo Connectors schemas across connect/v0.X spec versions",
long_about = None,
version = VERSION,
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
AgentGuide,
Analyze {
#[arg(default_value = ".")]
paths: Vec<PathBuf>,
#[arg(long, short)]
output: Option<PathBuf>,
#[arg(long, value_enum, default_value_t = OutputFormat::Markdown)]
format: OutputFormat,
},
}
#[derive(Copy, Clone, Debug, ValueEnum)]
enum OutputFormat {
Markdown,
Json,
}
fn main() -> ExitCode {
let cli = Cli::parse();
match cli.command {
Command::AgentGuide => {
print!("{}", AGENT_GUIDE);
ExitCode::SUCCESS
}
Command::Analyze {
paths,
output,
format,
} => match run_analyze(paths, output, format) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("connect-migrate analyze: {e}");
ExitCode::FAILURE
}
},
}
}
fn run_analyze(
paths: Vec<PathBuf>,
output: Option<PathBuf>,
format: OutputFormat,
) -> io::Result<()> {
let project_root = match paths.as_slice() {
[only] if only.is_dir() => only.clone(),
_ => std::env::current_dir()?,
};
let report = analyze::analyze(&paths, &project_root);
let stdout = io::stdout();
let writing_to_path = matches!(&output, Some(p) if p.as_os_str() != "-");
let mut sink: Box<dyn std::io::Write> = match output.as_deref() {
Some(p) if p.as_os_str() != "-" => Box::new(BufWriter::new(File::create(p)?)),
_ => Box::new(BufWriter::new(stdout.lock())),
};
match format {
OutputFormat::Markdown => {
analyze::write_markdown(&mut sink, &report, &project_root, VERSION)?;
}
OutputFormat::Json => {
analyze::write_jsonl(&mut sink, &report.sites)?;
}
}
let total = report.sites.len();
let kept = report
.sites
.iter()
.filter(|s| matches!(s.recommendation, analyze::Recommendation::KeepV03))
.count();
let embraced = report
.sites
.iter()
.filter(|s| matches!(s.recommendation, analyze::Recommendation::EmbraceV04))
.count();
let ambiguous = report
.sites
.iter()
.filter(|s| matches!(s.recommendation, analyze::Recommendation::Ambiguous))
.count();
eprintln!(
"scanned {files} file(s); analyzed {analyzed} `@connect` directive(s); {total} site(s) need attention ({kept} keep-v0.3 · {embraced} embrace-v0.4 · {ambiguous} ambiguous) — {kind}",
files = report.files_scanned,
analyzed = report.directives_analyzed,
kind = report.result_kind().as_str(),
);
if writing_to_path && let Some(path) = output.as_deref() {
eprintln!("wrote: {}", path.display());
}
Ok(())
}