mod legacy_args;
use std::process::Command;
use clap::Parser;
use cli::{
commands::{args, serve_web, tunnels, update, version, CommandContext},
constants::get_default_user_agent,
desktop, log,
state::LauncherPaths,
util::{
errors::{wrap, AnyError},
is_integrated_cli,
prereqs::PreReqChecker,
},
};
use legacy_args::try_parse_legacy;
use opentelemetry::sdk::trace::TracerProvider as SdkTracerProvider;
use opentelemetry::trace::TracerProvider;
#[tokio::main]
async fn main() -> Result<(), std::convert::Infallible> {
let raw_args = std::env::args_os().collect::<Vec<_>>();
let parsed = try_parse_legacy(&raw_args)
.map(|core| args::AnyCli::Integrated(args::IntegratedCli { core }))
.unwrap_or_else(|| {
if let Ok(true) = is_integrated_cli() {
args::AnyCli::Integrated(args::IntegratedCli::parse_from(&raw_args))
} else {
args::AnyCli::Standalone(args::StandaloneCli::parse_from(&raw_args))
}
});
let core = parsed.core();
let context_paths = LauncherPaths::migrate(core.global_options.cli_data_dir.clone()).unwrap();
let context_args = core.clone();
let context_no_logger = || CommandContext {
http: reqwest::ClientBuilder::new()
.user_agent(get_default_user_agent())
.build()
.unwrap(),
paths: context_paths,
log: make_logger(&context_args),
args: context_args,
};
macro_rules! context {
() => {{
let context = context_no_logger();
log::install_global_logger(context.log.clone());
context
}};
}
let result = match parsed {
args::AnyCli::Standalone(args::StandaloneCli {
subcommand: Some(cmd),
..
}) => match cmd {
args::StandaloneCommands::Update(args) => update::update(context!(), args).await,
},
args::AnyCli::Standalone(args::StandaloneCli { core: c, .. })
| args::AnyCli::Integrated(args::IntegratedCli { core: c, .. }) => match c.subcommand {
None => {
let context = context!();
let ca = context.args.get_base_code_args();
start_code(context, ca).await
}
Some(args::Commands::Extension(extension_args)) => {
let context = context!();
let mut ca = context.args.get_base_code_args();
extension_args.add_code_args(&mut ca);
start_code(context, ca).await
}
Some(args::Commands::Status) => {
let context = context!();
let mut ca = context.args.get_base_code_args();
ca.push("--status".to_string());
start_code(context, ca).await
}
Some(args::Commands::Version(version_args)) => match version_args.subcommand {
args::VersionSubcommand::Use(use_version_args) => {
version::switch_to(context!(), use_version_args).await
}
args::VersionSubcommand::Show => version::show(context!()).await,
},
Some(args::Commands::CommandShell(cs_args)) => {
tunnels::command_shell(context!(), cs_args).await
}
Some(args::Commands::ServeWeb(sw_args)) => {
serve_web::serve_web(context!(), sw_args).await
}
Some(args::Commands::Tunnel(mut tunnel_args)) => match tunnel_args.subcommand.take() {
Some(args::TunnelSubcommand::Prune) => tunnels::prune(context!()).await,
Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context!()).await,
Some(args::TunnelSubcommand::Kill) => tunnels::kill(context!()).await,
Some(args::TunnelSubcommand::Restart) => tunnels::restart(context!()).await,
Some(args::TunnelSubcommand::Status) => tunnels::status(context!()).await,
Some(args::TunnelSubcommand::Rename(rename_args)) => {
tunnels::rename(context!(), rename_args).await
}
Some(args::TunnelSubcommand::User(user_command)) => {
tunnels::user(context!(), user_command).await
}
Some(args::TunnelSubcommand::Service(service_args)) => {
tunnels::service(context_no_logger(), tunnel_args, service_args).await
}
Some(args::TunnelSubcommand::ForwardInternal(forward_args)) => {
tunnels::forward(context_no_logger(), forward_args).await
}
None => tunnels::serve(context_no_logger(), tunnel_args.serve_args).await,
},
},
};
match result {
Err(e) => print_and_exit(e),
Ok(code) => std::process::exit(code),
}
}
fn make_logger(core: &args::CliCore) -> log::Logger {
let log_level = if core.global_options.verbose {
log::Level::Trace
} else {
core.global_options.log.unwrap_or(log::Level::Info)
};
let tracer = SdkTracerProvider::builder().build().tracer("codecli");
let mut log = log::Logger::new(tracer, log_level);
if let Some(f) = &core.global_options.log_to_file {
log = log
.with_sink(log::FileLogSink::new(log_level, f).expect("expected to make file logger"))
}
log
}
fn print_and_exit<E>(err: E) -> !
where
E: std::fmt::Display,
{
log::emit(log::Level::Error, "", &format!("{err}"));
std::process::exit(1);
}
async fn start_code(context: CommandContext, args: Vec<String>) -> Result<i32, AnyError> {
let platform = PreReqChecker::new().verify().await?;
let version_manager =
desktop::CodeVersionManager::new(context.log.clone(), &context.paths, platform);
let version = match &context.args.editor_options.code_options.use_version {
Some(v) => desktop::RequestedVersion::try_from(v.as_str())?,
None => version_manager.get_preferred_version(),
};
let binary = match version_manager.try_get_entrypoint(&version).await {
Some(ep) => ep,
None => {
desktop::prompt_to_install(&version);
return Ok(1);
}
};
let code = Command::new(&binary)
.args(args)
.status()
.map(|s| s.code().unwrap_or(1))
.map_err(|e| wrap(e, format!("error running editor from {}", binary.display())))?;
Ok(code)
}