/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ mod legacy_args; use std::process::Command; use clap::Parser; use cli::{ commands::{agent_host, 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::>(); 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(); // gets a command context without installing the global logger 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, }; // gets a command context with the global logger installer. Usually what most commands want. 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::AgentHost(ah_args)) => { agent_host::agent_host(context!(), ah_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(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) -> Result { // todo: once the integrated CLI takes the place of the Node.js CLI, this should // redirect to the current installation without using the CodeVersionManager. 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) }