mirror of
https://github.com/microsoft/vscode.git
synced 2026-07-25 15:35:43 +01:00
1fe8359ed0
Closes https://github.com/microsoft/vscode/issues/168492 This implements @aeschli's 'server server' concept in a new `code serve-web` command. Command line args are similar to the standalone web server. The first time a user hits that page, the latest version of the VS Code web server will be downloaded and run. Thanks to Martin's previous PRs, all resources the page requests are prefixed with `/<quality-<commit>`. The latest release version is cached, but when the page is loaded again and there's a new release, a the new server version will be downloaded and started up. Behind the scenes the servers all listen on named pipes/sockets and the CLI acts as a proxy server to those sockets. Servers without connections for an hour will be shut down automatically.
188 lines
6.0 KiB
Rust
188 lines
6.0 KiB
Rust
/*---------------------------------------------------------------------------------------------
|
|
* 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::{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();
|
|
|
|
// 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::Tunnel(tunnel_args)) => match tunnel_args.subcommand {
|
|
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(), 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> {
|
|
// 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)
|
|
}
|