use clap::Parser;
use dev_scope::prelude::*;
use human_panic::setup_panic;
use std::env;
use tracing::{debug, enabled, error, info, warn, Level};
#[derive(Parser)]
#[clap(author, version, about)]
struct Cli {
#[clap(flatten)]
logging: LoggingOpts,
#[arg(short, long)]
successful_exit: Vec<i32>,
#[clap(flatten)]
config_options: ConfigOptions,
#[arg(required = true)]
utility: String,
args: Vec<String>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_panic!();
dotenv::dotenv().ok();
let opts = Cli::parse();
let (_guard, file_location) = opts
.logging
.with_new_default(tracing::level_filters::LevelFilter::WARN)
.configure_logging(&opts.config_options.get_run_id(), "intercept");
let exit_code = run_command(opts).await.unwrap_or_else(|e| {
error!(target: "user", "Fatal error {:?}", e);
1
});
if exit_code != 0 || enabled!(Level::DEBUG) {
info!(target: "user", "More detailed logs at {}", file_location);
}
std::process::exit(exit_code);
}
async fn run_command(opts: Cli) -> anyhow::Result<i32> {
let mut command = vec![opts.utility];
command.extend(opts.args);
let current_dir = std::env::current_dir()?;
let path = env::var("PATH").unwrap_or_default();
let capture = OutputCapture::capture_output(CaptureOpts {
working_dir: ¤t_dir,
args: &command,
output_dest: OutputDestination::StandardOut,
path: &path,
env_vars: Default::default(),
})
.await?;
let mut accepted_exit_codes = vec![0];
accepted_exit_codes.extend(opts.successful_exit);
let exit_code = capture.exit_code.unwrap_or(-1);
if accepted_exit_codes.contains(&exit_code) {
return Ok(exit_code);
}
error!(target: "user", "Command failed, checking for a known error");
let found_config = opts.config_options.load_config().await.unwrap_or_else(|e| {
error!(target: "user", "Unable to load configs from disk: {:?}", e);
FoundConfig::empty(env::current_dir().unwrap())
});
let command_output = capture.generate_output();
for known_error in found_config.known_error.values() {
debug!("Checking known error {}", known_error.name());
if known_error.regex.is_match(&command_output) {
info!(target: "always", "Known error '{}' found", known_error.name());
info!(target: "always", "\t==> {}", known_error.help_text);
}
}
if found_config.report_upload.is_empty() {
return Ok(exit_code);
}
let ans = inquire::Confirm::new("Do you want to upload a bug report?")
.with_default(true)
.with_help_message(
"This will allow you to share the error with other engineers for support.",
)
.prompt();
let report_builder = ReportBuilder::new(&capture, &found_config).await?;
if let Ok(true) = ans {
if let Err(e) = report_builder.distribute_report().await {
warn!(target: "user", "Unable to upload report: {}", e);
}
} else {
report_builder.write_local_report().ok();
}
Ok(exit_code)
}