mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge vscode source through release 1.79.2 (#23482)
* log when an editor action doesn't run because of enablement * notebooks create/dispose editors. this means controllers must be created eagerly (😢) and that notebooks need a custom way of plugging comparision keys for session. works unless creating another session for the same cell of a duplicated editor * Set offSide to sql lang configuration to true (#183461) * Fixes #181764 (#183550) * fix typo * Always scroll down and focus the input (#183557) * Fixes #180386 (#183561) * cli: ensure ordering of rpc server messages (#183558) * cli: ensure ordering of rpc server messages Sending lots of messages to a stream would block them around the async tokio mutex, which is "fair" so doesn't preserve ordering. Instead, use the write_loop approach I introduced to the server_multiplexer for the same reason some time ago. * fix clippy * update for May endgame * testing: allow invalidateTestResults to take an array (#183569) * Document `ShareProvider` API proposal (#183568) * Document `ShareProvider` API proposal * Remove mention of VS Code from JSDoc * Add support for rendering svg and md in welcome message (#183580) * Remove toggle setting more eagerly (#183584) * rm message abt macOS * Change text (#183589) * Change text * Accidentally changed the wrong file * cli: improve output for code tunnel status (#183571) * testing: allow invalidateTestResults to take an array * cli: improve output for code tunnel status Fixes #183570 * [json/css/html] update services (#183595) * Add experimental setting to enable this dialog * Fix exporting chat model to JSON before it is initialized (#183597) * minimum scrolling to reveal the next cell on shift+enter (#183600) do minimum scrolling to reveal the next cell on Execute cell and select next * Fixing Jupyter notebook issue 13263 (#183527) fix for the issue, still need to understand why there is strange focusing * Tweak proposed API JSDoc (#183590) * Tweak proposed API JSDoc * workbench -> workspace * fix ? operator * Use active editor and show progress when sharing (#183603) Use active editor and show progress * use scroll setting variable correctly * Schedule welcome widget to show once between typing. (#183606) * Schedule dialog to show once between typing * Don't re-render if already displayed once * Add F10 keybinding for debugger step, even on Web. (#183510) Fixes #181792. Previously, for Web the keyboard shortcut was Alt-F10, because it was believed that F10 could not be bound on browsers. This turned out to be incorrect, so we make the shortcut consistent (F10) with desktop VSCode which is also what many other debuggers use. We keep Alt-F10 on web as a secondary keybinding to keep the experience some web users may have gotten used to by now. * Also pass process.env * Restore missing chat clear commands (#183651) * chore: update electron@22.5.4 (#183716) * Show remote indicator in web when remoteAuthority is set (#183728) * feat: .vuerc as json file (#153017) Co-authored-by: Martin Aeschlimann <martinae@microsoft.com> * Delete --compatibility=1.63 code from the server (#183738) * Copy vscode.dev link to tunnel generates an invalid link when an untitled workspace is open (#183739) * Recent devcontainer display string corrupted on Get Started page (#183740) * Improve "next codeblock" navigation (#183744) * Improve "next codeblock" navigation Operate on the current focused response, or the last one, and scroll to the selected item * Normalize command title * Git - run git status if similarityThreshold changes (#183762) * fix aria-label issue in kb editor fixes A11y_GradeB_VSCode_Keyboard shortcut reads words together - Blind: Arrow key navigation to row Find the binding keys and "when" cell data are read together resulting in a word " CTRL + FeditorFocus instead of CTRL + F editorFocus" #182490 * Status - fix compact padding (#183768) * Remove angle brackets from VB brackets (#183782) Fixes #183359 * Update language config schema with more details about brackets. (#183779) * fix comment (#183812) * Support for `Notebook` CodeAction Kind (#183457) * nb kind support -- wip * allow notebook codeactions around single cell edit check * move notebook code action type out of editor --------- Co-authored-by: rebornix <penn.lv@gmail.com> * cli: fix connection default being applied (#183827) * cli: bump to openssl 1.1.1u (#183828) * Implement "delete" action for chat history (#183609) * Use desired file name when generating new md pasted file paths (#183861) Fixes #183851 * Default to filename for markdown new file if empty (#183864) Fixes #183848 * Fix small typo (#183865) Fixes #183819 * Noop when moving a symbol into the file it is already in (#183866) Fixes #183793 * Adjust codeAction validation to account for notebook kind (#183859) * Make JS/TS `go to configuration` commands work on non-`file:` file systems (#183688) Make `go to project` commands work on non-`file:` file systems Fixes #183685 * Can't do regex search after opening notebook (#183884) Fixes #183858 * Default to current dir for `move to file` select (#183875) Fixes #183870 `showOpenDialog` seems to ignore `defaultUri` if the file doesn't exist * Use `<...>` style markdown links when needed (#183876) Fixes #183849 * Remove check for context keys * Update xterm package * Enable updating a chat model without triggering incremental typing (#183894) * Enable chat "move" commands on empty sessions (#183895) * Enable chat "move" commands on empty sessions and also imported sessions * Fix command name * Fix some chat keybindings on windows (#183896) * "Revert File" on inactive editors are ignored (fix #177557) (#183903) * Empty reason while switching profile (fix #183775) (#183904) * fix https://github.com/microsoft/vscode-internalbacklog/issues/4278 (#183910) * fix https://github.com/microsoft/vscode/issues/183770 (#183914) * code --status displays a lot of errors before actual status output (fix #183787) (#183915) * joh/icy manatee (#183917) * Use idle value for widget of interactive editor controller https://github.com/microsoft/vscode/issues/183820 * also make preview editors idle values https://github.com/microsoft/vscode/issues/183820 * Fix #183777 (#183929) * Fix #182309 (#183925) * Tree checkbox item -> items (#183931) Fixes #183826 * Fixes #183909 (#183940) * Fix #183837 (#183943) fix #183837 * Git - fix #183941 (#183944) * Update xterm.css Fixes #181242 * chore: add @ulugbekna and @aiday-mar to my-endgame notebook (#183946) * Revert "When snippet mode is active, make `Tab` not accept suggestion but advance placeholder" This reverts commit 50a80cdb61511343996ff1d41d0b676c3d329f48. * revert not focusing completion list when quick suggest happens during snippet * change `snippetsPreventQuickSuggestions` default to false * Fix #181446 (#183956) * fix https://github.com/microsoft/vscode-internalbacklog/issues/4298 (#183957) * fix: remove extraneous incorrect context keys (#183959) These were actually getting added in getTestItemContextOverlay, and the test ID was using the extended ID which extensions do not know about. Fixes #183612 * Fixes https://github.com/microsoft/monaco-editor/issues/3920 (#183960) * fix https://github.com/microsoft/vscode-internalbacklog/issues/4324 (#183961) * fix #183030 * fix #180826 (#183962) * make message more generic for interactive editor help * . * fix #183968 * Keep codeblock toolbar visible when focused * Fix when clause on "Run in terminal" command * add important info to help menu * fix #183970 * Set `isRefactoring` for all TS refactoring edits (#183982) * consolidate * Disable move to file in TS versions < 5.2 (#183992) There are still a few key bugs with refactoring. We will ship this as a preview for TS 5.2+ instead of for 5.1 * Polish query accepting (#183995) We shouldn't send the same request to Copilot if the query hasn't changed. So if the query is the same, we short circut. Fixes https://github.com/microsoft/vscode-internalbacklog/issues/4286 Also, when we open in chat, we should use the last accepted query, not what's in the input box. Fixes https://github.com/microsoft/vscode-internalbacklog/issues/4280 * Allow widget to have focus (#184000) So that selecting non-code text works. Fixes https://github.com/microsoft/vscode-internalbacklog/issues/4294 * Fix microsoft/vscode-internalbacklog#4257. Mitigate zindex for zone widgets. (#184001) * Change welcome dialog contribution to Eventually * Misc fixes * Workspace folder picker entry descriptions are suboptimal for some filesystems (fix #183418) (#184018) * cli - ignore std error unless verbose (#183787) (#184031) * joh/inquisitive meerkat (#184034) * only stash sessions that are none empty https://github.com/microsoft/vscode-internalbacklog/issues/4281 * only unstash a session once - unless new exchanges are made, https://github.com/microsoft/vscode-internalbacklog/issues/4281 * account for all exchange types * Improve declared components (#184039) * make sure to read setting (#184040) d'oh, related to https://github.com/microsoft/vscode/issues/173387#issuecomment-1571696644 * [html] update service (#184049) [html] update service. FIxes #181176 * reset context keys on reset/hide (#184042) fixes https://github.com/microsoft/vscode-internalbacklog/issues/4330 * use `Lazy`, not `IdleValue` for the IE widget held by the eager controller (#184048) https://github.com/microsoft/vscode/issues/183820 * fix https://github.com/microsoft/vscode-internalbacklog/issues/4333 (#184067) * use undo-loop instead of undo-edit when discarding chat session (#184063) * use undo-loop instead of undo-edit when discarding chat session fixes https://github.com/microsoft/vscode-internalbacklog/issues/4118 * fix tests, wait for correct state * Add logging to node download (#184070) Add logging to node download. For #182951 * re-enable default zone widget revealing when showing (#184072) fixes https://github.com/microsoft/vscode-internalbacklog/issues/4332, also fixes https://github.com/microsoft/vscode-internalbacklog/issues/3784 * fix #178202 * Allow APIs in stable (#184062) * Fix microsoft/vscode-internalbacklog#4206. Override List view whitespace css for monaco editor (#184087) * Fix JSDoc grammatical error (#184090) * Pick up TS 5.1.3 (#184091) Fixes #182931 * Misc fixes * update distro (#184097) * chore: update electron@22.5.5 (#184116) * Extension host veto is registered multiple times on restart (fix #183778) (#184127) Extension host veto is registered multiple times on restart (#183778) * Do not auto start the local web worker extension host (#184137) * Allow embedders to intercept trustedTypes.createPolicy calls (#184136) Allow embedders to intercept trustedTypes.createPolicy calls (#184100) * fix: reading from console output for --status on windows and linux (#184138) fix: reading from console output for --status on windows and linux (#184118) * Misc fixes * code --status displays a lot of errors before actual status output (fix #183787) (#184200) fix 183787 * (cherry-pick to 1.79 from main) Handle galleryExtension failure in featuredExtensionService (#184205) Handle galleryExtension failure in featuredExtensionService (#184198) Handle galleryExtension failure * Fix #184183. Multiple output height updates are skipped. (#184188) * Post merge init fixes * Misc build issues * disable toggle inline diff of `alt` down https://github.com/microsoft/vscode-internalbacklog/issues/4342 * Take into account already activated extensions when computing running locations (#184303) Take into account already activated extensions when computing running locations (fixes #184180) * Avoid `extensionService.getExtension` and use `ActivationKind.Immediate` to allow that URI handling works while resolving (#184310) Avoid `extensionService.getExtension` and use `ActivationKind.Immediate` to allow that URI handling works while resolving (fixes #182217) * WIP * rm fish auto injection * More breaks * Fix Port Attributes constructor (#184412) * WIP * WIP * Allow extensions to get at the exports of other extensions during resolving (#184487) Allow extensions to get at the exports of other extensions during resolving (fixes #184472) * do not auto finish session when inline chat widgets have focus re https://github.com/microsoft/vscode-internalbacklog/issues/4354 * fix compile errors caused by new base method * WIP * WIP * WIP * WIP * Build errors * unc - fix path traversal bypass * Bump version * cherry-pick prod changes from main * Disable sandbox * Build break from merge * bump version * Merge pull request #184739 from max06/max06/issue184659 Restore ShellIntegration for fish (#184659) * Git - only add --find-renames if the value is not the default one (#185053) Git - only add --find-renames if the value is not the default one (#184992) * Cherry-pick: Revert changes to render featured extensions when available (#184747) Revert changes to render featured extensions when available. (#184573) * Lower timeouts for experimentation and gallery service * Revert changes to render extensions when available * Add audio cues * fix: disable app sandbox when --no-sandbox is present (#184913) * fix: disable app sandbox when --no-sandbox is present (#184897) * fix: loading minimist in packaged builds * Runtime errors * UNC allow list checks cannot be disabled in extension host (fix #184989) (#185085) * UNC allow list checks cannot be disabled in extension host (#184989) * Update src/vs/base/node/unc.js Co-authored-by: Robo <hop2deep@gmail.com> --------- Co-authored-by: Robo <hop2deep@gmail.com> * Add notebook extension * Fix mangling issues * Fix mangling issues * npm install * npm install * Issues blocking bundle * Fix build folder compile errors * Fix windows bundle build * Linting fixes * Fix sqllint issues * Update yarn.lock files * Fix unit tests * Fix a couple breaks from test fixes * Bump distro * redo the checkbox style * Update linux build container dockerfile * Bump build image tag * Bump native watch dog package * Bump node-pty * Bump distro * Fix documnetation error * Update distro * redo the button styles * Update datasource TS * Add missing yarn.lock files * Windows setup fix * Turn off extension unit tests while investigating * color box style * Remove appx * Turn off test log upload * update dropdownlist style * fix universal app build error (#23488) * Skip flaky bufferContext vscode test --------- Co-authored-by: Johannes <johannes.rieken@gmail.com> Co-authored-by: Henning Dieterichs <hdieterichs@microsoft.com> Co-authored-by: Julien Richard <jairbubbles@hotmail.com> Co-authored-by: Charles Gagnon <chgagnon@microsoft.com> Co-authored-by: Megan Rogge <merogge@microsoft.com> Co-authored-by: meganrogge <megan.rogge@microsoft.com> Co-authored-by: Rob Lourens <roblourens@gmail.com> Co-authored-by: Connor Peet <connor@peet.io> Co-authored-by: Joyce Er <joyce.er@microsoft.com> Co-authored-by: Bhavya U <bhavyau@microsoft.com> Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Co-authored-by: Martin Aeschlimann <martinae@microsoft.com> Co-authored-by: Aaron Munger <aamunger@microsoft.com> Co-authored-by: Aiday Marlen Kyzy <amarlenkyzy@microsoft.com> Co-authored-by: rebornix <penn.lv@gmail.com> Co-authored-by: Ole <oler@google.com> Co-authored-by: Jean Pierre <jeanp413@hotmail.com> Co-authored-by: Robo <hop2deep@gmail.com> Co-authored-by: Yash Singh <saiansh2525@gmail.com> Co-authored-by: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Co-authored-by: Ulugbek Abdullaev <ulugbekna@gmail.com> Co-authored-by: Alex Ross <alros@microsoft.com> Co-authored-by: Michael Lively <milively@microsoft.com> Co-authored-by: Matt Bierner <matb@microsoft.com> Co-authored-by: Andrea Mah <31675041+andreamah@users.noreply.github.com> Co-authored-by: Benjamin Pasero <benjamin.pasero@microsoft.com> Co-authored-by: Sandeep Somavarapu <sasomava@microsoft.com> Co-authored-by: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Co-authored-by: Tyler James Leonhardt <me@tylerleonhardt.com> Co-authored-by: Alexandru Dima <alexdima@microsoft.com> Co-authored-by: Joao Moreno <Joao.Moreno@microsoft.com> Co-authored-by: Alan Ren <alanren@microsoft.com>
This commit is contained in:
688
cli/src/commands/args.rs
Normal file
688
cli/src/commands/args.rs
Normal file
@@ -0,0 +1,688 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::{fmt, path::PathBuf};
|
||||
|
||||
use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
|
||||
use clap::{ArgEnum, Args, Parser, Subcommand};
|
||||
use const_format::concatcp;
|
||||
|
||||
const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI");
|
||||
const HELP_COMMANDS: &str = "Usage: {name} [options][paths...]
|
||||
|
||||
To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')";
|
||||
|
||||
const STANDALONE_TEMPLATE: &str = concatcp!(
|
||||
CLI_NAME,
|
||||
" Standalone - {version}
|
||||
|
||||
",
|
||||
HELP_COMMANDS,
|
||||
"
|
||||
Running editor commands requires installing ",
|
||||
constants::QUALITYLESS_PRODUCT_NAME,
|
||||
", and may differ slightly.
|
||||
|
||||
{all-args}"
|
||||
);
|
||||
const INTEGRATED_TEMPLATE: &str = concatcp!(
|
||||
CLI_NAME,
|
||||
" - {version}
|
||||
|
||||
",
|
||||
HELP_COMMANDS,
|
||||
"
|
||||
|
||||
{all-args}"
|
||||
);
|
||||
|
||||
const COMMIT_IN_VERSION: &str = match constants::VSCODE_CLI_COMMIT {
|
||||
Some(c) => c,
|
||||
None => "unknown",
|
||||
};
|
||||
const NUMBER_IN_VERSION: &str = match constants::VSCODE_CLI_VERSION {
|
||||
Some(c) => c,
|
||||
None => "dev",
|
||||
};
|
||||
const VERSION: &str = concatcp!(NUMBER_IN_VERSION, " (commit ", COMMIT_IN_VERSION, ")");
|
||||
|
||||
#[derive(Parser, Debug, Default)]
|
||||
#[clap(
|
||||
help_template = INTEGRATED_TEMPLATE,
|
||||
long_about = None,
|
||||
version = VERSION,
|
||||
)]
|
||||
pub struct IntegratedCli {
|
||||
#[clap(flatten)]
|
||||
pub core: CliCore,
|
||||
}
|
||||
|
||||
/// Common CLI shared between intergated and standalone interfaces.
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
pub struct CliCore {
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Default)]
|
||||
#[clap(
|
||||
help_template = STANDALONE_TEMPLATE,
|
||||
long_about = None,
|
||||
version = VERSION,
|
||||
)]
|
||||
pub struct StandaloneCli {
|
||||
#[clap(flatten)]
|
||||
pub core: CliCore,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: Option<StandaloneCommands>,
|
||||
}
|
||||
|
||||
pub enum AnyCli {
|
||||
Integrated(IntegratedCli),
|
||||
Standalone(StandaloneCli),
|
||||
}
|
||||
|
||||
impl AnyCli {
|
||||
pub fn core(&self) -> &CliCore {
|
||||
match self {
|
||||
AnyCli::Integrated(cli) => &cli.core,
|
||||
AnyCli::Standalone(cli) => &cli.core,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CliCore {
|
||||
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 CliCore> for CodeServerArgs {
|
||||
fn from(cli: &'a CliCore) -> 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 StandaloneCommands {
|
||||
/// Updates the CLI.
|
||||
Update(StandaloneUpdateArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct StandaloneUpdateArgs {
|
||||
/// Only check for updates, without actually updating the CLI.
|
||||
#[clap(long)]
|
||||
pub check: bool,
|
||||
}
|
||||
|
||||
#[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 editor extensions.
|
||||
#[clap(name = "ext")]
|
||||
Extension(ExtensionArgs),
|
||||
|
||||
/// Print process usage and diagnostics information.
|
||||
Status,
|
||||
|
||||
/// Changes the version of the editor you're using.
|
||||
Version(VersionArgs),
|
||||
|
||||
/// Runs the control server on process stdin/stdout
|
||||
#[clap(hide = true)]
|
||||
CommandShell,
|
||||
}
|
||||
|
||||
#[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>) {
|
||||
self.desktop_code_options.add_code_args(target);
|
||||
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 version of the editor in use.
|
||||
Use(UseVersionArgs),
|
||||
|
||||
/// Shows the currently configured editor version.
|
||||
Show,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct UseVersionArgs {
|
||||
/// The version of the editor 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 where the version can be found.
|
||||
#[clap(long, value_name = "path")]
|
||||
pub install_dir: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Default, Clone)]
|
||||
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 the desktop editor 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 the editor.
|
||||
#[clap(long, value_name = "dir")]
|
||||
pub user_data_dir: Option<String>,
|
||||
|
||||
/// Sets the editor 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, Clone)]
|
||||
pub struct GlobalOptions {
|
||||
/// Directory where CLI metadata 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 to a file in addition to stdout. Used when running as a service.
|
||||
#[clap(long, global = true, hide = true)]
|
||||
pub log_to_file: Option<PathBuf>,
|
||||
|
||||
/// 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, Clone)]
|
||||
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,
|
||||
|
||||
/// Shows all telemetry events which the editor 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 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,
|
||||
|
||||
/// Prevents the machine going to sleep while this command runs.
|
||||
#[clap(long)]
|
||||
pub no_sleep: bool,
|
||||
|
||||
/// Sets the machine name for port forwarding service
|
||||
#[clap(long)]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// Optional parent process id. If provided, the server will be stopped when the process of the given pid no longer exists
|
||||
#[clap(long, hide = true)]
|
||||
pub parent_process_id: Option<String>,
|
||||
|
||||
/// If set, the user accepts the server license terms and the server will be started without a user prompt.
|
||||
#[clap(long)]
|
||||
pub accept_server_license_terms: 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,
|
||||
|
||||
/// Stops any running tunnel on the system.
|
||||
Kill,
|
||||
|
||||
/// Restarts any running tunnel on the system.
|
||||
Restart,
|
||||
|
||||
/// Gets whether there is a tunnel running on the current machineiou.
|
||||
Status,
|
||||
|
||||
/// 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),
|
||||
|
||||
/// (Preview) 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(TunnelServiceInstallArgs),
|
||||
|
||||
/// Uninstalls and stops the tunnel service.
|
||||
Uninstall,
|
||||
|
||||
/// Shows logs for the running service.
|
||||
Log,
|
||||
|
||||
/// Internal command for running the service
|
||||
#[clap(hide = true)]
|
||||
InternalRun,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct TunnelServiceInstallArgs {
|
||||
/// If set, the user accepts the server license terms and the server will be started without a user prompt.
|
||||
#[clap(long)]
|
||||
pub accept_server_license_terms: bool,
|
||||
}
|
||||
|
||||
#[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 Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use crate::{log, state::LauncherPaths};
|
||||
|
||||
use super::args::CliCore;
|
||||
|
||||
pub struct CommandContext {
|
||||
pub log: log::Logger,
|
||||
pub paths: LauncherPaths,
|
||||
pub args: CliCore,
|
||||
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 Source EULA. 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")
|
||||
}
|
||||
427
cli/src/commands/tunnels.rs
Normal file
427
cli/src/commands/tunnels.rs
Normal file
@@ -0,0 +1,427 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use async_trait::async_trait;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{str::FromStr, time::Duration};
|
||||
use sysinfo::Pid;
|
||||
|
||||
use super::{
|
||||
args::{
|
||||
AuthProvider, CliCore, ExistingTunnelArgs, TunnelRenameArgs, TunnelServeArgs,
|
||||
TunnelServiceSubCommands, TunnelUserSubCommands,
|
||||
},
|
||||
CommandContext,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
auth::Auth,
|
||||
constants::{APPLICATION_NAME, TUNNEL_CLI_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME},
|
||||
log,
|
||||
state::LauncherPaths,
|
||||
tunnels::{
|
||||
code_server::CodeServerArgs,
|
||||
create_service_manager, dev_tunnels, legal,
|
||||
paths::get_all_servers,
|
||||
protocol, serve_stream,
|
||||
shutdown_signal::ShutdownRequest,
|
||||
singleton_client::do_single_rpc_call,
|
||||
singleton_server::{
|
||||
make_singleton_server, start_singleton_server, BroadcastLogSink, SingletonServerArgs,
|
||||
},
|
||||
Next, ServeStreamParams, ServiceContainer, ServiceManager,
|
||||
},
|
||||
util::{
|
||||
app_lock::AppMutex,
|
||||
errors::{wrap, AnyError, CodeError},
|
||||
prereqs::PreReqChecker,
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
singleton::{acquire_singleton, SingletonConnection},
|
||||
tunnels::{
|
||||
dev_tunnels::ActiveTunnel,
|
||||
singleton_client::{start_singleton_client, SingletonClientArgs},
|
||||
SleepInhibitor,
|
||||
},
|
||||
};
|
||||
|
||||
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: CliCore,
|
||||
}
|
||||
|
||||
impl TunnelServiceContainer {
|
||||
fn new(args: CliCore) -> Self {
|
||||
Self { args }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ServiceContainer for TunnelServiceContainer {
|
||||
async fn run_service(
|
||||
&mut self,
|
||||
log: log::Logger,
|
||||
launcher_paths: LauncherPaths,
|
||||
) -> Result<(), AnyError> {
|
||||
let csa = (&self.args).into();
|
||||
serve_with_csa(
|
||||
launcher_paths,
|
||||
log,
|
||||
TunnelServeArgs {
|
||||
random_name: true, // avoid prompting
|
||||
..Default::default()
|
||||
},
|
||||
csa,
|
||||
TUNNEL_SERVICE_LOCK_NAME,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn command_shell(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
serve_stream(
|
||||
tokio::io::stdin(),
|
||||
tokio::io::stderr(),
|
||||
ServeStreamParams {
|
||||
log: ctx.log,
|
||||
launcher_paths: ctx.paths,
|
||||
platform,
|
||||
requires_auth: true,
|
||||
exit_barrier: ShutdownRequest::create_rx([ShutdownRequest::CtrlC]),
|
||||
code_server_args: (&ctx.args).into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
pub async fn service(
|
||||
ctx: CommandContext,
|
||||
service_args: TunnelServiceSubCommands,
|
||||
) -> Result<i32, AnyError> {
|
||||
let manager = create_service_manager(ctx.log.clone(), &ctx.paths);
|
||||
match service_args {
|
||||
TunnelServiceSubCommands::Install(args) => {
|
||||
// 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, args.accept_server_license_terms)?;
|
||||
|
||||
let current_exe =
|
||||
std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?;
|
||||
|
||||
manager
|
||||
.register(
|
||||
current_exe,
|
||||
&[
|
||||
"--verbose",
|
||||
"--cli-data-dir",
|
||||
ctx.paths.root().as_os_str().to_string_lossy().as_ref(),
|
||||
"tunnel",
|
||||
"service",
|
||||
"internal-run",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
ctx.log.result(format!("Service successfully installed! You can use `{} tunnel service log` to monitor it, and `{} tunnel service uninstall` to remove it.", APPLICATION_NAME, APPLICATION_NAME));
|
||||
}
|
||||
TunnelServiceSubCommands::Uninstall => {
|
||||
manager.unregister().await?;
|
||||
}
|
||||
TunnelServiceSubCommands::Log => {
|
||||
manager.show_logs().await?;
|
||||
}
|
||||
TunnelServiceSubCommands::InternalRun => {
|
||||
manager
|
||||
.run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args))
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub async fn restart(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
do_single_rpc_call::<_, ()>(
|
||||
&ctx.paths.tunnel_lockfile(),
|
||||
ctx.log,
|
||||
protocol::singleton::METHOD_RESTART,
|
||||
protocol::EmptyObject {},
|
||||
)
|
||||
.await
|
||||
.map(|_| 0)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub async fn kill(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
do_single_rpc_call::<_, ()>(
|
||||
&ctx.paths.tunnel_lockfile(),
|
||||
ctx.log,
|
||||
protocol::singleton::METHOD_SHUTDOWN,
|
||||
protocol::EmptyObject {},
|
||||
)
|
||||
.await
|
||||
.map(|_| 0)
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
pub async fn status(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
let status = do_single_rpc_call::<_, protocol::singleton::Status>(
|
||||
&ctx.paths.tunnel_lockfile(),
|
||||
ctx.log.clone(),
|
||||
protocol::singleton::METHOD_STATUS,
|
||||
protocol::EmptyObject {},
|
||||
)
|
||||
.await;
|
||||
|
||||
match status {
|
||||
Err(CodeError::NoRunningTunnel) => {
|
||||
ctx.log.result(CodeError::NoRunningTunnel.to_string());
|
||||
Ok(1)
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
Ok(s) => {
|
||||
ctx.log.result(serde_json::to_string(&s).unwrap());
|
||||
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;
|
||||
|
||||
let no_sleep = match gateway_args.no_sleep.then(SleepInhibitor::new) {
|
||||
Some(i) => match i.await {
|
||||
Ok(i) => Some(i),
|
||||
Err(e) => {
|
||||
warning!(log, "Could not inhibit sleep: {}", e);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
legal::require_consent(&paths, gateway_args.accept_server_license_terms)?;
|
||||
|
||||
let csa = (&args).into();
|
||||
let result = serve_with_csa(paths, log, gateway_args, csa, TUNNEL_CLI_LOCK_NAME).await;
|
||||
drop(no_sleep);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn get_connection_token(tunnel: &ActiveTunnel) -> String {
|
||||
let mut hash = Sha256::new();
|
||||
hash.update(tunnel.id.as_bytes());
|
||||
let result = hash.finalize();
|
||||
base64::encode_config(result, base64::URL_SAFE_NO_PAD)
|
||||
}
|
||||
|
||||
async fn serve_with_csa(
|
||||
paths: LauncherPaths,
|
||||
mut log: log::Logger,
|
||||
gateway_args: TunnelServeArgs,
|
||||
mut csa: CodeServerArgs,
|
||||
app_mutex_name: Option<&'static str>,
|
||||
) -> Result<i32, AnyError> {
|
||||
let log_broadcast = BroadcastLogSink::new();
|
||||
log = log.tee(log_broadcast.clone());
|
||||
log::install_global_logger(log.clone()); // re-install so that library logs are captured
|
||||
|
||||
// Intentionally read before starting the server. If the server updated and
|
||||
// respawn is requested, the old binary will get renamed, and then
|
||||
// current_exe will point to the wrong path.
|
||||
let current_exe = std::env::current_exe().unwrap();
|
||||
|
||||
let mut vec = vec![
|
||||
ShutdownRequest::CtrlC,
|
||||
ShutdownRequest::ExeUninstalled(current_exe.to_owned()),
|
||||
];
|
||||
if let Some(p) = gateway_args
|
||||
.parent_process_id
|
||||
.and_then(|p| Pid::from_str(&p).ok())
|
||||
{
|
||||
vec.push(ShutdownRequest::ParentProcessKilled(p));
|
||||
}
|
||||
let shutdown = ShutdownRequest::create_rx(vec);
|
||||
|
||||
let server = loop {
|
||||
if shutdown.is_open() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
match acquire_singleton(paths.tunnel_lockfile()).await {
|
||||
Ok(SingletonConnection::Client(stream)) => {
|
||||
debug!(log, "starting as client to singleton");
|
||||
let should_exit = start_singleton_client(SingletonClientArgs {
|
||||
log: log.clone(),
|
||||
shutdown: shutdown.clone(),
|
||||
stream,
|
||||
})
|
||||
.await;
|
||||
if should_exit {
|
||||
return Ok(0);
|
||||
}
|
||||
}
|
||||
Ok(SingletonConnection::Singleton(server)) => break server,
|
||||
Err(e) => {
|
||||
warning!(log, "error access singleton, retrying: {}", e);
|
||||
tokio::time::sleep(Duration::from_secs(2)).await
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
debug!(log, "starting as new singleton");
|
||||
|
||||
let mut server =
|
||||
make_singleton_server(log_broadcast.clone(), log.clone(), server, shutdown.clone());
|
||||
let platform = spanf!(log, log.span("prereq"), PreReqChecker::new().verify())?;
|
||||
let _lock = app_mutex_name.map(AppMutex::new);
|
||||
|
||||
let auth = Auth::new(&paths, log.clone());
|
||||
let mut dt = dev_tunnels::DevTunnels::new(&log, auth, &paths);
|
||||
loop {
|
||||
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.name.as_deref(), gateway_args.random_name)
|
||||
.await
|
||||
}?;
|
||||
|
||||
csa.connection_token = Some(get_connection_token(&tunnel));
|
||||
|
||||
let mut r = start_singleton_server(SingletonServerArgs {
|
||||
log: log.clone(),
|
||||
tunnel,
|
||||
paths: &paths,
|
||||
code_server_args: &csa,
|
||||
platform,
|
||||
log_broadcast: &log_broadcast,
|
||||
shutdown: shutdown.clone(),
|
||||
server: &mut server,
|
||||
})
|
||||
.await?;
|
||||
r.tunnel.close().await.ok();
|
||||
|
||||
match r.next {
|
||||
Next::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(current_exe)
|
||||
.args(args)
|
||||
.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));
|
||||
}
|
||||
Next::Exit => return Ok(0),
|
||||
Next::Restart => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
50
cli/src/commands/update.rs
Normal file
50
cli/src/commands/update.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use indicatif::ProgressBar;
|
||||
|
||||
use crate::{
|
||||
constants::PRODUCT_NAME_LONG,
|
||||
self_update::SelfUpdate,
|
||||
update_service::UpdateService,
|
||||
util::{errors::AnyError, http::ReqwestSimpleHttp, input::ProgressBarReporter},
|
||||
};
|
||||
|
||||
use super::{args::StandaloneUpdateArgs, CommandContext};
|
||||
|
||||
pub async fn update(ctx: CommandContext, args: StandaloneUpdateArgs) -> Result<i32, AnyError> {
|
||||
let update_service = UpdateService::new(
|
||||
ctx.log.clone(),
|
||||
Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())),
|
||||
);
|
||||
let update_service = SelfUpdate::new(&update_service)?;
|
||||
|
||||
let current_version = update_service.get_current_release().await?;
|
||||
if update_service.is_up_to_date_with(¤t_version) {
|
||||
ctx.log.result(format!(
|
||||
"{} is already to to date ({})",
|
||||
PRODUCT_NAME_LONG, current_version.commit
|
||||
));
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
if args.check {
|
||||
ctx.log
|
||||
.result(format!("Update to {} is available", current_version));
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let pb = ProgressBar::new(1);
|
||||
pb.set_message("Downloading...");
|
||||
update_service
|
||||
.do_update(¤t_version, ProgressBarReporter::from(pb))
|
||||
.await?;
|
||||
ctx.log
|
||||
.result(format!("Successfully updated to {}", current_version));
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
62
cli/src/commands/version.rs
Normal file
62
cli/src/commands/version.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::{
|
||||
desktop::{prompt_to_install, CodeVersionManager, RequestedVersion},
|
||||
log,
|
||||
util::{
|
||||
errors::{AnyError, NoInstallInUserProvidedPath},
|
||||
prereqs::PreReqChecker,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{args::UseVersionArgs, CommandContext};
|
||||
|
||||
pub async fn switch_to(ctx: CommandContext, args: UseVersionArgs) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform);
|
||||
let version = RequestedVersion::try_from(args.name.as_str())?;
|
||||
|
||||
let maybe_path = match args.install_dir {
|
||||
Some(d) => Some(
|
||||
CodeVersionManager::get_entrypoint_for_install_dir(&PathBuf::from(&d))
|
||||
.await
|
||||
.ok_or(NoInstallInUserProvidedPath(d))?,
|
||||
),
|
||||
None => vm.try_get_entrypoint(&version).await,
|
||||
};
|
||||
|
||||
match maybe_path {
|
||||
Some(p) => {
|
||||
vm.set_preferred_version(version.clone(), p.clone()).await?;
|
||||
print_now_using(&ctx.log, &version, &p);
|
||||
Ok(0)
|
||||
}
|
||||
None => {
|
||||
prompt_to_install(&version);
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn show(ctx: CommandContext) -> Result<i32, AnyError> {
|
||||
let platform = PreReqChecker::new().verify().await?;
|
||||
let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform);
|
||||
|
||||
let version = vm.get_preferred_version();
|
||||
println!("Current quality: {}", version);
|
||||
match vm.try_get_entrypoint(&version).await {
|
||||
Some(p) => println!("Installation path: {}", p.display()),
|
||||
None => println!("No existing installation found"),
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn print_now_using(log: &log::Logger, version: &RequestedVersion, path: &Path) {
|
||||
log.result(format!("Now using {} from {}", version, path.display()));
|
||||
}
|
||||
Reference in New Issue
Block a user