mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-03 15:01:57 +01:00
move cli to top level
This commit is contained in:
590
cli/src/commands/args.rs
Normal file
590
cli/src/commands/args.rs
Normal file
@@ -0,0 +1,590 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
|
||||
use clap::{ArgEnum, Args, Parser, Subcommand};
|
||||
|
||||
const TEMPLATE: &str = "
|
||||
Visual Studio Code CLI - {version}
|
||||
|
||||
Usage: code-insiders.exe [options][paths...]
|
||||
|
||||
To read output from another program, append '-' (e.g. 'echo Hello World | code-insiders.exe -')
|
||||
|
||||
{all-args}";
|
||||
|
||||
#[derive(Parser, Debug, Default)]
|
||||
#[clap(
|
||||
help_template = TEMPLATE,
|
||||
long_about = None,
|
||||
name = "Visual Studio Code CLI",
|
||||
version = match constants::LAUNCHER_VERSION { Some(v) => v, None => "dev" },
|
||||
)]
|
||||
pub struct Cli {
|
||||
/// One or more files, folders, or URIs to open.
|
||||
#[clap(name = "paths")]
|
||||
pub open_paths: Vec<String>,
|
||||
|
||||
#[clap(flatten, next_help_heading = Some("EDITOR OPTIONS"))]
|
||||
pub editor_options: EditorOptions,
|
||||
|
||||
#[clap(flatten, next_help_heading = Some("EDITOR TROUBLESHOOTING"))]
|
||||
pub troubleshooting: EditorTroubleshooting,
|
||||
|
||||
#[clap(flatten, next_help_heading = Some("GLOBAL OPTIONS"))]
|
||||
pub global_options: GlobalOptions,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: Option<Commands>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn get_base_code_args(&self) -> Vec<String> {
|
||||
let mut args = self.open_paths.clone();
|
||||
self.editor_options.add_code_args(&mut args);
|
||||
self.troubleshooting.add_code_args(&mut args);
|
||||
self.global_options.add_code_args(&mut args);
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Cli> for CodeServerArgs {
|
||||
fn from(cli: &'a Cli) -> Self {
|
||||
let mut args = CodeServerArgs {
|
||||
log: cli.global_options.log,
|
||||
accept_server_license_terms: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
args.log = cli.global_options.log;
|
||||
args.accept_server_license_terms = true;
|
||||
|
||||
if cli.global_options.verbose {
|
||||
args.verbose = true;
|
||||
}
|
||||
|
||||
if cli.global_options.disable_telemetry {
|
||||
args.telemetry_level = Some(options::TelemetryLevel::Off);
|
||||
} else if cli.global_options.telemetry_level.is_some() {
|
||||
args.telemetry_level = cli.global_options.telemetry_level;
|
||||
}
|
||||
|
||||
args
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
|
||||
pub enum Commands {
|
||||
/// Create a tunnel that's accessible on vscode.dev from anywhere.
|
||||
/// Run `code tunnel --help` for more usage info.
|
||||
Tunnel(TunnelArgs),
|
||||
|
||||
/// Manage VS Code extensions.
|
||||
#[clap(name = "ext")]
|
||||
Extension(ExtensionArgs),
|
||||
|
||||
/// Print process usage and diagnostics information.
|
||||
Status,
|
||||
|
||||
/// Changes the version of VS Code you're using.
|
||||
Version(VersionArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ExtensionArgs {
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: ExtensionSubcommand,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub desktop_code_options: DesktopCodeOptions,
|
||||
}
|
||||
|
||||
impl ExtensionArgs {
|
||||
pub fn add_code_args(&self, target: &mut Vec<String>) {
|
||||
if let Some(ed) = &self.desktop_code_options.extensions_dir {
|
||||
target.push(ed.to_string());
|
||||
}
|
||||
|
||||
self.subcommand.add_code_args(target);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum ExtensionSubcommand {
|
||||
/// List installed extensions.
|
||||
List(ListExtensionArgs),
|
||||
/// Install an extension.
|
||||
Install(InstallExtensionArgs),
|
||||
/// Uninstall an extension.
|
||||
Uninstall(UninstallExtensionArgs),
|
||||
}
|
||||
|
||||
impl ExtensionSubcommand {
|
||||
pub fn add_code_args(&self, target: &mut Vec<String>) {
|
||||
match self {
|
||||
ExtensionSubcommand::List(args) => {
|
||||
target.push("--list-extensions".to_string());
|
||||
if args.show_versions {
|
||||
target.push("--show-versions".to_string());
|
||||
}
|
||||
if let Some(category) = &args.category {
|
||||
target.push(format!("--category={}", category));
|
||||
}
|
||||
}
|
||||
ExtensionSubcommand::Install(args) => {
|
||||
for id in args.id_or_path.iter() {
|
||||
target.push(format!("--install-extension={}", id));
|
||||
}
|
||||
if args.pre_release {
|
||||
target.push("--pre-release".to_string());
|
||||
}
|
||||
if args.force {
|
||||
target.push("--force".to_string());
|
||||
}
|
||||
}
|
||||
ExtensionSubcommand::Uninstall(args) => {
|
||||
for id in args.id.iter() {
|
||||
target.push(format!("--uninstall-extension={}", id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ListExtensionArgs {
|
||||
/// Filters installed extensions by provided category, when using --list-extensions.
|
||||
#[clap(long, value_name = "category")]
|
||||
pub category: Option<String>,
|
||||
|
||||
/// Show versions of installed extensions, when using --list-extensions.
|
||||
#[clap(long)]
|
||||
pub show_versions: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct InstallExtensionArgs {
|
||||
/// Either an extension id or a path to a VSIX. The identifier of an
|
||||
/// extension is '${publisher}.${name}'. Use '--force' argument to update
|
||||
/// to latest version. To install a specific version provide '@${version}'.
|
||||
/// For example: 'vscode.csharp@1.2.3'.
|
||||
#[clap(name = "ext-id | id")]
|
||||
pub id_or_path: Vec<String>,
|
||||
|
||||
/// Installs the pre-release version of the extension
|
||||
#[clap(long)]
|
||||
pub pre_release: bool,
|
||||
|
||||
/// Update to the latest version of the extension if it's already installed.
|
||||
#[clap(long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct UninstallExtensionArgs {
|
||||
/// One or more extension identifiers to uninstall. The identifier of an
|
||||
/// extension is '${publisher}.${name}'. Use '--force' argument to update
|
||||
/// to latest version.
|
||||
#[clap(name = "ext-id")]
|
||||
pub id: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct VersionArgs {
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: VersionSubcommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum VersionSubcommand {
|
||||
/// Switches the instance of VS Code in use.
|
||||
Use(UseVersionArgs),
|
||||
/// Uninstalls a instance of VS Code.
|
||||
Uninstall(UninstallVersionArgs),
|
||||
/// Lists installed VS Code instances.
|
||||
List(OutputFormatOptions),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct UseVersionArgs {
|
||||
/// The version of VS Code you want to use. Can be "stable", "insiders",
|
||||
/// a version number, or an absolute path to an existing install.
|
||||
#[clap(value_name = "stable | insiders | x.y.z | path")]
|
||||
pub name: String,
|
||||
|
||||
/// The directory the version should be installed into, if it's not already installed.
|
||||
#[clap(long, value_name = "path")]
|
||||
pub install_dir: Option<String>,
|
||||
|
||||
/// Reinstall the version even if it's already installed.
|
||||
#[clap(long)]
|
||||
pub reinstall: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct UninstallVersionArgs {
|
||||
/// The version of VS Code to uninstall. Can be "stable", "insiders", or a
|
||||
/// version number previous passed to `code version use <version>`.
|
||||
#[clap(value_name = "stable | insiders | x.y.z")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
pub struct EditorOptions {
|
||||
/// Compare two files with each other.
|
||||
#[clap(short, long, value_names = &["file", "file"])]
|
||||
pub diff: Vec<String>,
|
||||
|
||||
/// Add folder(s) to the last active window.
|
||||
#[clap(short, long, value_name = "folder")]
|
||||
pub add: Option<String>,
|
||||
|
||||
/// Open a file at the path on the specified line and character position.
|
||||
#[clap(short, long, value_name = "file:line[:character]")]
|
||||
pub goto: Option<String>,
|
||||
|
||||
/// Force to open a new window.
|
||||
#[clap(short, long)]
|
||||
pub new_window: bool,
|
||||
|
||||
/// Force to open a file or folder in an
|
||||
#[clap(short, long)]
|
||||
pub reuse_window: bool,
|
||||
|
||||
/// Wait for the files to be closed before returning.
|
||||
#[clap(short, long)]
|
||||
pub wait: bool,
|
||||
|
||||
/// The locale to use (e.g. en-US or zh-TW).
|
||||
#[clap(long, value_name = "locale")]
|
||||
pub locale: Option<String>,
|
||||
|
||||
/// Enables proposed API features for extensions. Can receive one or
|
||||
/// more extension IDs to enable individually.
|
||||
#[clap(long, value_name = "ext-id")]
|
||||
pub enable_proposed_api: Vec<String>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub code_options: DesktopCodeOptions,
|
||||
}
|
||||
|
||||
impl EditorOptions {
|
||||
pub fn add_code_args(&self, target: &mut Vec<String>) {
|
||||
if !self.diff.is_empty() {
|
||||
target.push("--diff".to_string());
|
||||
for file in self.diff.iter() {
|
||||
target.push(file.clone());
|
||||
}
|
||||
}
|
||||
if let Some(add) = &self.add {
|
||||
target.push("--add".to_string());
|
||||
target.push(add.clone());
|
||||
}
|
||||
if let Some(goto) = &self.goto {
|
||||
target.push("--goto".to_string());
|
||||
target.push(goto.clone());
|
||||
}
|
||||
if self.new_window {
|
||||
target.push("--new-window".to_string());
|
||||
}
|
||||
if self.reuse_window {
|
||||
target.push("--reuse-window".to_string());
|
||||
}
|
||||
if self.wait {
|
||||
target.push("--wait".to_string());
|
||||
}
|
||||
if let Some(locale) = &self.locale {
|
||||
target.push(format!("--locale={}", locale));
|
||||
}
|
||||
if !self.enable_proposed_api.is_empty() {
|
||||
for id in self.enable_proposed_api.iter() {
|
||||
target.push(format!("--enable-proposed-api={}", id));
|
||||
}
|
||||
}
|
||||
self.code_options.add_code_args(target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Arguments applicable whenever VS Code desktop is launched
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct DesktopCodeOptions {
|
||||
/// Set the root path for extensions.
|
||||
#[clap(long, value_name = "dir")]
|
||||
pub extensions_dir: Option<String>,
|
||||
|
||||
/// Specifies the directory that user data is kept in. Can be used to
|
||||
/// open multiple distinct instances of Code.
|
||||
#[clap(long, value_name = "dir")]
|
||||
pub user_data_dir: Option<String>,
|
||||
|
||||
/// Sets the VS Code version to use for this command. The preferred version
|
||||
/// can be persisted with `code version use <version>`. Can be "stable",
|
||||
/// "insiders", a version number, or an absolute path to an existing install.
|
||||
#[clap(long, value_name = "stable | insiders | x.y.z | path")]
|
||||
pub use_version: Option<String>,
|
||||
}
|
||||
|
||||
/// Argument specifying the output format.
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct OutputFormatOptions {
|
||||
/// Set the data output formats.
|
||||
#[clap(arg_enum, long, value_name = "format", default_value_t = OutputFormat::Text)]
|
||||
pub format: OutputFormat,
|
||||
}
|
||||
|
||||
impl DesktopCodeOptions {
|
||||
pub fn add_code_args(&self, target: &mut Vec<String>) {
|
||||
if let Some(extensions_dir) = &self.extensions_dir {
|
||||
target.push(format!("--extensions-dir={}", extensions_dir));
|
||||
}
|
||||
if let Some(user_data_dir) = &self.user_data_dir {
|
||||
target.push(format!("--user-data-dir={}", user_data_dir));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
pub struct GlobalOptions {
|
||||
/// Directory where CLI metadata, such as VS Code installations, should be stored.
|
||||
#[clap(long, env = "VSCODE_CLI_DATA_DIR", global = true)]
|
||||
pub cli_data_dir: Option<String>,
|
||||
|
||||
/// Print verbose output (implies --wait).
|
||||
#[clap(long, global = true)]
|
||||
pub verbose: bool,
|
||||
|
||||
/// Log level to use.
|
||||
#[clap(long, arg_enum, value_name = "level", global = true)]
|
||||
pub log: Option<log::Level>,
|
||||
|
||||
/// Disable telemetry for the current command, even if it was previously
|
||||
/// accepted as part of the license prompt or specified in '--telemetry-level'
|
||||
#[clap(long, global = true, hide = true)]
|
||||
pub disable_telemetry: bool,
|
||||
|
||||
/// Sets the initial telemetry level
|
||||
#[clap(arg_enum, long, global = true, hide = true)]
|
||||
pub telemetry_level: Option<options::TelemetryLevel>,
|
||||
}
|
||||
|
||||
impl GlobalOptions {
|
||||
pub fn add_code_args(&self, target: &mut Vec<String>) {
|
||||
if self.verbose {
|
||||
target.push("--verbose".to_string());
|
||||
}
|
||||
if let Some(log) = self.log {
|
||||
target.push(format!("--log={}", log));
|
||||
}
|
||||
if self.disable_telemetry {
|
||||
target.push("--disable-telemetry".to_string());
|
||||
}
|
||||
if let Some(telemetry_level) = &self.telemetry_level {
|
||||
target.push(format!("--telemetry-level={}", telemetry_level));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default)]
|
||||
pub struct EditorTroubleshooting {
|
||||
/// Run CPU profiler during startup.
|
||||
#[clap(long)]
|
||||
pub prof_startup: bool,
|
||||
|
||||
/// Disable all installed extensions.
|
||||
#[clap(long)]
|
||||
pub disable_extensions: bool,
|
||||
|
||||
/// Disable an extension.
|
||||
#[clap(long, value_name = "ext-id")]
|
||||
pub disable_extension: Vec<String>,
|
||||
|
||||
/// Turn sync on or off.
|
||||
#[clap(arg_enum, long, value_name = "on | off")]
|
||||
pub sync: Option<SyncState>,
|
||||
|
||||
/// Allow debugging and profiling of extensions. Check the developer tools for the connection URI.
|
||||
#[clap(long, value_name = "port")]
|
||||
pub inspect_extensions: Option<u16>,
|
||||
|
||||
/// Allow debugging and profiling of extensions with the extension host
|
||||
/// being paused after start. Check the developer tools for the connection URI.
|
||||
#[clap(long, value_name = "port")]
|
||||
pub inspect_brk_extensions: Option<u16>,
|
||||
|
||||
/// Disable GPU hardware acceleration.
|
||||
#[clap(long)]
|
||||
pub disable_gpu: bool,
|
||||
|
||||
/// Max memory size for a window (in Mbytes).
|
||||
#[clap(long, value_name = "memory")]
|
||||
pub max_memory: Option<usize>,
|
||||
|
||||
/// Shows all telemetry events which VS code collects.
|
||||
#[clap(long)]
|
||||
pub telemetry: bool,
|
||||
}
|
||||
|
||||
impl EditorTroubleshooting {
|
||||
pub fn add_code_args(&self, target: &mut Vec<String>) {
|
||||
if self.prof_startup {
|
||||
target.push("--prof-startup".to_string());
|
||||
}
|
||||
if self.disable_extensions {
|
||||
target.push("--disable-extensions".to_string());
|
||||
}
|
||||
for id in self.disable_extension.iter() {
|
||||
target.push(format!("--disable-extension={}", id));
|
||||
}
|
||||
if let Some(sync) = &self.sync {
|
||||
target.push(format!("--sync={}", sync));
|
||||
}
|
||||
if let Some(port) = &self.inspect_extensions {
|
||||
target.push(format!("--inspect-extensions={}", port));
|
||||
}
|
||||
if let Some(port) = &self.inspect_brk_extensions {
|
||||
target.push(format!("--inspect-brk-extensions={}", port));
|
||||
}
|
||||
if self.disable_gpu {
|
||||
target.push("--disable-gpu".to_string());
|
||||
}
|
||||
if let Some(memory) = &self.max_memory {
|
||||
target.push(format!("--max-memory={}", memory));
|
||||
}
|
||||
if self.telemetry {
|
||||
target.push("--telemetry".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Clone, Copy, Debug)]
|
||||
pub enum SyncState {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
impl fmt::Display for SyncState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
SyncState::Off => write!(f, "off"),
|
||||
SyncState::On => write!(f, "on"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(ArgEnum, Clone, Copy, Debug)]
|
||||
pub enum OutputFormat {
|
||||
Json,
|
||||
Text,
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug, Default)]
|
||||
pub struct ExistingTunnelArgs {
|
||||
/// Name you'd like to assign preexisting tunnel to use to connect the tunnel
|
||||
#[clap(long, hide = true)]
|
||||
pub tunnel_name: Option<String>,
|
||||
|
||||
/// Token to authenticate and use preexisting tunnel
|
||||
#[clap(long, hide = true)]
|
||||
pub host_token: Option<String>,
|
||||
|
||||
/// ID of preexisting tunnel to use to connect the tunnel
|
||||
#[clap(long, hide = true)]
|
||||
pub tunnel_id: Option<String>,
|
||||
|
||||
/// Cluster of preexisting tunnel to use to connect the tunnel
|
||||
#[clap(long, hide = true)]
|
||||
pub cluster: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone, Default)]
|
||||
pub struct TunnelServeArgs {
|
||||
/// Optional details to connect to an existing tunnel
|
||||
#[clap(flatten, next_help_heading = Some("ADVANCED OPTIONS"))]
|
||||
pub tunnel: ExistingTunnelArgs,
|
||||
|
||||
/// Randomly name machine for port forwarding service
|
||||
#[clap(long)]
|
||||
pub random_name: bool,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct TunnelArgs {
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: Option<TunnelSubcommand>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub serve_args: TunnelServeArgs,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum TunnelSubcommand {
|
||||
/// Delete all servers which are currently not running.
|
||||
Prune,
|
||||
|
||||
/// Rename the name of this machine associated with port forwarding service.
|
||||
Rename(TunnelRenameArgs),
|
||||
|
||||
/// Remove this machine's association with the port forwarding service.
|
||||
Unregister,
|
||||
|
||||
#[clap(subcommand)]
|
||||
User(TunnelUserSubCommands),
|
||||
|
||||
/// Manages the tunnel when installed as a system service,
|
||||
#[clap(subcommand)]
|
||||
Service(TunnelServiceSubCommands),
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum TunnelServiceSubCommands {
|
||||
/// Installs or re-installs the tunnel service on the machine.
|
||||
Install,
|
||||
|
||||
/// Uninstalls and stops the tunnel service.
|
||||
Uninstall,
|
||||
|
||||
/// Internal command for running the service
|
||||
#[clap(hide = true)]
|
||||
InternalRun,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct TunnelRenameArgs {
|
||||
/// The name you'd like to rename your machine to.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum TunnelUserSubCommands {
|
||||
/// Log in to port forwarding service
|
||||
Login(LoginArgs),
|
||||
|
||||
/// Log out of port forwarding service
|
||||
Logout,
|
||||
|
||||
/// Show the account that's logged into port forwarding service
|
||||
Show,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct LoginArgs {
|
||||
/// An access token to store for authentication. Note: this will not be
|
||||
/// refreshed if it expires!
|
||||
#[clap(long, requires = "provider")]
|
||||
pub access_token: Option<String>,
|
||||
|
||||
/// The auth provider to use. If not provided, a prompt will be shown.
|
||||
#[clap(arg_enum, long)]
|
||||
pub provider: Option<AuthProvider>,
|
||||
}
|
||||
|
||||
#[derive(clap::ArgEnum, Debug, Clone, Copy)]
|
||||
pub enum AuthProvider {
|
||||
Microsoft,
|
||||
Github,
|
||||
}
|
||||
15
cli/src/commands/context.rs
Normal file
15
cli/src/commands/context.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use crate::{log, state::LauncherPaths};
|
||||
|
||||
use super::args::Cli;
|
||||
|
||||
pub struct CommandContext {
|
||||
pub log: log::Logger,
|
||||
pub paths: LauncherPaths,
|
||||
pub args: Cli,
|
||||
pub http: reqwest::Client,
|
||||
}
|
||||
135
cli/src/commands/output.rs
Normal file
135
cli/src/commands/output.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use std::io::{BufWriter, Write};
|
||||
|
||||
use super::args::OutputFormat;
|
||||
|
||||
pub struct Column {
|
||||
max_width: usize,
|
||||
heading: &'static str,
|
||||
data: Vec<String>,
|
||||
}
|
||||
|
||||
impl Column {
|
||||
pub fn new(heading: &'static str) -> Self {
|
||||
Column {
|
||||
max_width: heading.len(),
|
||||
heading,
|
||||
data: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_row(&mut self, row: String) {
|
||||
self.max_width = std::cmp::max(self.max_width, row.len());
|
||||
self.data.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputFormat {
|
||||
pub fn print_table(&self, table: OutputTable) -> Result<(), std::io::Error> {
|
||||
match *self {
|
||||
OutputFormat::Json => JsonTablePrinter().print(table, &mut std::io::stdout()),
|
||||
OutputFormat::Text => TextTablePrinter().print(table, &mut std::io::stdout()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OutputTable {
|
||||
cols: Vec<Column>,
|
||||
}
|
||||
|
||||
impl OutputTable {
|
||||
pub fn new(cols: Vec<Column>) -> Self {
|
||||
OutputTable { cols }
|
||||
}
|
||||
}
|
||||
|
||||
trait TablePrinter {
|
||||
fn print(&self, table: OutputTable, out: &mut dyn std::io::Write)
|
||||
-> Result<(), std::io::Error>;
|
||||
}
|
||||
|
||||
pub struct JsonTablePrinter();
|
||||
|
||||
impl TablePrinter for JsonTablePrinter {
|
||||
fn print(
|
||||
&self,
|
||||
table: OutputTable,
|
||||
out: &mut dyn std::io::Write,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let mut bw = BufWriter::new(out);
|
||||
bw.write_all(b"[")?;
|
||||
|
||||
if !table.cols.is_empty() {
|
||||
let data_len = table.cols[0].data.len();
|
||||
for i in 0..data_len {
|
||||
if i > 0 {
|
||||
bw.write_all(b",{")?;
|
||||
} else {
|
||||
bw.write_all(b"{")?;
|
||||
}
|
||||
for col in &table.cols {
|
||||
serde_json::to_writer(&mut bw, col.heading)?;
|
||||
bw.write_all(b":")?;
|
||||
serde_json::to_writer(&mut bw, &col.data[i])?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bw.write_all(b"]")?;
|
||||
bw.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/// Type that prints the output as an ASCII, markdown-style table.
|
||||
pub struct TextTablePrinter();
|
||||
|
||||
impl TablePrinter for TextTablePrinter {
|
||||
fn print(
|
||||
&self,
|
||||
table: OutputTable,
|
||||
out: &mut dyn std::io::Write,
|
||||
) -> Result<(), std::io::Error> {
|
||||
let mut bw = BufWriter::new(out);
|
||||
|
||||
let sizes = table.cols.iter().map(|c| c.max_width).collect::<Vec<_>>();
|
||||
|
||||
// print headers
|
||||
write_columns(&mut bw, table.cols.iter().map(|c| c.heading), &sizes)?;
|
||||
// print --- separators
|
||||
write_columns(
|
||||
&mut bw,
|
||||
table.cols.iter().map(|c| "-".repeat(c.max_width)),
|
||||
&sizes,
|
||||
)?;
|
||||
// print each column
|
||||
if !table.cols.is_empty() {
|
||||
let data_len = table.cols[0].data.len();
|
||||
for i in 0..data_len {
|
||||
write_columns(&mut bw, table.cols.iter().map(|c| &c.data[i]), &sizes)?;
|
||||
}
|
||||
}
|
||||
|
||||
bw.flush()
|
||||
}
|
||||
}
|
||||
|
||||
fn write_columns<T>(
|
||||
mut w: impl Write,
|
||||
cols: impl Iterator<Item = T>,
|
||||
sizes: &[usize],
|
||||
) -> Result<(), std::io::Error>
|
||||
where
|
||||
T: Display,
|
||||
{
|
||||
w.write_all(b"|")?;
|
||||
for (i, col) in cols.enumerate() {
|
||||
write!(w, " {:width$} |", col, width = sizes[i])?;
|
||||
}
|
||||
w.write_all(b"\r\n")
|
||||
}
|
||||
261
cli/src/commands/tunnels.rs
Normal file
261
cli/src/commands/tunnels.rs
Normal file
@@ -0,0 +1,261 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::process::Stdio;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use super::{
|
||||
args::{
|
||||
AuthProvider, Cli, ExistingTunnelArgs, TunnelRenameArgs, TunnelServeArgs,
|
||||
TunnelServiceSubCommands, TunnelUserSubCommands,
|
||||
},
|
||||
CommandContext,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
auth::Auth,
|
||||
log::{self, Logger},
|
||||
state::LauncherPaths,
|
||||
tunnels::{
|
||||
code_server::CodeServerArgs, create_service_manager, dev_tunnels, legal,
|
||||
paths::get_all_servers, ServiceContainer, ServiceManager,
|
||||
},
|
||||
util::{
|
||||
errors::{wrap, AnyError},
|
||||
prereqs::PreReqChecker,
|
||||
},
|
||||
};
|
||||
|
||||
impl From<AuthProvider> for crate::auth::AuthProvider {
|
||||
fn from(auth_provider: AuthProvider) -> Self {
|
||||
match auth_provider {
|
||||
AuthProvider::Github => crate::auth::AuthProvider::Github,
|
||||
AuthProvider::Microsoft => crate::auth::AuthProvider::Microsoft,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExistingTunnelArgs> for Option<dev_tunnels::ExistingTunnel> {
|
||||
fn from(d: ExistingTunnelArgs) -> Option<dev_tunnels::ExistingTunnel> {
|
||||
if let (Some(tunnel_id), Some(tunnel_name), Some(cluster), Some(host_token)) =
|
||||
(d.tunnel_id, d.tunnel_name, d.cluster, d.host_token)
|
||||
{
|
||||
Some(dev_tunnels::ExistingTunnel {
|
||||
tunnel_id,
|
||||
tunnel_name,
|
||||
host_token,
|
||||
cluster,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TunnelServiceContainer {
|
||||
args: Cli,
|
||||
}
|
||||
|
||||
impl TunnelServiceContainer {
|
||||
fn new(args: Cli) -> Self {
|
||||
Self { args }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ServiceContainer for TunnelServiceContainer {
|
||||
async fn run_service(
|
||||
&mut self,
|
||||
log: log::Logger,
|
||||
launcher_paths: LauncherPaths,
|
||||
shutdown_rx: oneshot::Receiver<()>,
|
||||
) -> Result<(), AnyError> {
|
||||
let csa = (&self.args).into();
|
||||
serve_with_csa(
|
||||
launcher_paths,
|
||||
log,
|
||||
TunnelServeArgs {
|
||||
random_name: true, // avoid prompting
|
||||
..Default::default()
|
||||
},
|
||||
csa,
|
||||
Some(shutdown_rx),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn service(
|
||||
ctx: CommandContext,
|
||||
service_args: TunnelServiceSubCommands,
|
||||
) -> Result<i32, AnyError> {
|
||||
let manager = create_service_manager(ctx.log.clone());
|
||||
match service_args {
|
||||
TunnelServiceSubCommands::Install => {
|
||||
// ensure logged in, otherwise subsequent serving will fail
|
||||
Auth::new(&ctx.paths, ctx.log.clone())
|
||||
.get_credential()
|
||||
.await?;
|
||||
|
||||
// likewise for license consent
|
||||
legal::require_consent(&ctx.paths)?;
|
||||
|
||||
let current_exe =
|
||||
std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?;
|
||||
|
||||
manager.register(
|
||||
current_exe,
|
||||
&[
|
||||
"--cli-data-dir",
|
||||
ctx.paths.root().as_os_str().to_string_lossy().as_ref(),
|
||||
"tunnel",
|
||||
"service",
|
||||
"internal-run",
|
||||
],
|
||||
)?;
|
||||
ctx.log.result("Service successfully installed! You can use `code tunnel service log` to monitor it, and `code tunnel service uninstall` to remove it.");
|
||||
}
|
||||
TunnelServiceSubCommands::Uninstall => {
|
||||
manager.unregister()?;
|
||||
}
|
||||
TunnelServiceSubCommands::InternalRun => {
|
||||
manager.run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn user(ctx: CommandContext, user_args: TunnelUserSubCommands) -> Result<i32, AnyError> {
|
||||
let auth = Auth::new(&ctx.paths, ctx.log.clone());
|
||||
match user_args {
|
||||
TunnelUserSubCommands::Login(login_args) => {
|
||||
auth.login(
|
||||
login_args.provider.map(|p| p.into()),
|
||||
login_args.access_token.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
TunnelUserSubCommands::Logout => {
|
||||
auth.clear_credentials()?;
|
||||
}
|
||||
TunnelUserSubCommands::Show => {
|
||||
if let Ok(Some(_)) = auth.get_current_credential() {
|
||||
ctx.log.result("logged in");
|
||||
} else {
|
||||
ctx.log.result("not logged in");
|
||||
return Ok(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Remove the tunnel used by this gateway, if any.
|
||||
pub async fn rename(ctx: CommandContext, rename_args: TunnelRenameArgs) -> Result<i32, AnyError> {
|
||||
let auth = Auth::new(&ctx.paths, ctx.log.clone());
|
||||
let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths);
|
||||
dt.rename_tunnel(&rename_args.name).await?;
|
||||
ctx.log.result(&format!(
|
||||
"Successfully renamed this gateway to {}",
|
||||
&rename_args.name
|
||||
));
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Remove the tunnel used by this gateway, if any.
|
||||
pub async fn unregister(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
let auth = Auth::new(&ctx.paths, ctx.log.clone());
|
||||
let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths);
|
||||
dt.remove_tunnel().await?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Removes unused servers.
|
||||
pub async fn prune(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
get_all_servers(&ctx.paths)
|
||||
.into_iter()
|
||||
.map(|s| s.server_paths(&ctx.paths))
|
||||
.filter(|s| s.get_running_pid().is_none())
|
||||
.try_for_each(|s| {
|
||||
ctx.log
|
||||
.result(&format!("Deleted {}", s.server_dir.display()));
|
||||
s.delete()
|
||||
})
|
||||
.map_err(AnyError::from)?;
|
||||
|
||||
ctx.log.result("Successfully removed all unused servers");
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Starts the gateway server.
|
||||
pub async fn serve(ctx: CommandContext, gateway_args: TunnelServeArgs) -> Result<i32, AnyError> {
|
||||
let CommandContext {
|
||||
log, paths, args, ..
|
||||
} = ctx;
|
||||
|
||||
legal::require_consent(&paths)?;
|
||||
|
||||
let csa = (&args).into();
|
||||
serve_with_csa(paths, log, gateway_args, csa, None).await
|
||||
}
|
||||
|
||||
async fn serve_with_csa(
|
||||
paths: LauncherPaths,
|
||||
log: Logger,
|
||||
gateway_args: TunnelServeArgs,
|
||||
csa: CodeServerArgs,
|
||||
shutdown_rx: Option<oneshot::Receiver<()>>,
|
||||
) -> Result<i32, AnyError> {
|
||||
let platform = spanf!(log, log.span("prereq"), PreReqChecker::new().verify())?;
|
||||
|
||||
let auth = Auth::new(&paths, log.clone());
|
||||
let mut dt = dev_tunnels::DevTunnels::new(&log, auth, &paths);
|
||||
let tunnel = if let Some(d) = gateway_args.tunnel.clone().into() {
|
||||
dt.start_existing_tunnel(d).await
|
||||
} else {
|
||||
dt.start_new_launcher_tunnel(gateway_args.random_name).await
|
||||
}?;
|
||||
|
||||
let shutdown_tx = if let Some(tx) = shutdown_rx {
|
||||
tx
|
||||
} else {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
tokio::spawn(async move {
|
||||
tokio::signal::ctrl_c().await.ok();
|
||||
tx.send(()).ok();
|
||||
});
|
||||
rx
|
||||
};
|
||||
|
||||
let mut r = crate::tunnels::serve(&log, tunnel, &paths, &csa, platform, shutdown_tx).await?;
|
||||
r.tunnel.close().await.ok();
|
||||
|
||||
if r.respawn {
|
||||
warning!(log, "respawn requested, starting new server");
|
||||
// reuse current args, but specify no-forward since tunnels will
|
||||
// already be running in this process, and we cannot do a login
|
||||
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
||||
let exit = std::process::Command::new(std::env::current_exe().unwrap())
|
||||
.args(args)
|
||||
.stdout(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.stdin(Stdio::inherit())
|
||||
.spawn()
|
||||
.map_err(|e| wrap(e, "error respawning after update"))?
|
||||
.wait()
|
||||
.map_err(|e| wrap(e, "error waiting for child"))?;
|
||||
|
||||
return Ok(exit.code().unwrap_or(1));
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
66
cli/src/commands/version.rs
Normal file
66
cli/src/commands/version.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use crate::{
|
||||
desktop::{CodeVersionManager, RequestedVersion},
|
||||
log,
|
||||
update_service::UpdateService,
|
||||
util::{errors::AnyError, prereqs::PreReqChecker},
|
||||
};
|
||||
|
||||
use super::{
|
||||
args::{OutputFormatOptions, UninstallVersionArgs, UseVersionArgs},
|
||||
output::{Column, OutputTable},
|
||||
CommandContext,
|
||||
};
|
||||
|
||||
pub async fn switch_to(ctx: CommandContext, args: UseVersionArgs) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(&ctx.paths, platform);
|
||||
let version = RequestedVersion::try_from(args.name.as_str())?;
|
||||
|
||||
if !args.reinstall && vm.try_get_entrypoint(&version).await.is_some() {
|
||||
vm.set_preferred_version(&version)?;
|
||||
print_now_using(&ctx.log, &version);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let update_service = UpdateService::new(ctx.log.clone(), ctx.http.clone());
|
||||
vm.install(&update_service, &version).await?;
|
||||
vm.set_preferred_version(&version)?;
|
||||
print_now_using(&ctx.log, &version);
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn list(ctx: CommandContext, args: OutputFormatOptions) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(&ctx.paths, platform);
|
||||
|
||||
let mut name = Column::new("Installation");
|
||||
let mut command = Column::new("Command");
|
||||
for version in vm.list() {
|
||||
name.add_row(version.to_string());
|
||||
command.add_row(version.get_command());
|
||||
}
|
||||
args.format
|
||||
.print_table(OutputTable::new(vec![name, command]))
|
||||
.ok();
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn uninstall(ctx: CommandContext, args: UninstallVersionArgs) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(&ctx.paths, platform);
|
||||
let version = RequestedVersion::try_from(args.name.as_str())?;
|
||||
vm.uninstall(&version).await?;
|
||||
ctx.log
|
||||
.result(&format!("VS Code {} uninstalled successfully", version));
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn print_now_using(log: &log::Logger, version: &RequestedVersion) {
|
||||
log.result(&format!("Now using VS Code {}", version));
|
||||
}
|
||||
Reference in New Issue
Block a user