From c87fa19f7932cefa1abeac4dd85ade3983780e14 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 30 Nov 2022 15:15:08 -0800 Subject: [PATCH] cli: avoid interactions when running integrated (#167780) Fixes https://github.com/microsoft/vscode/issues/167760 The VS Code CLI gets run from a bash/shell script. This prevents interactions--in the former case, it doesn't look like a tty, and in the latter case batch scripts don't seem to support having interactive subprocesses. This PR avoids interactions if stdin is not a tty, prompting the user to use the flag instead. Use of the flag is also persisted like an interactive agreement prompt. --- cli/src/constants.rs | 5 +++++ cli/src/tunnels/dev_tunnels.rs | 4 ++-- cli/src/tunnels/legal.rs | 23 +++++++++++------------ src/vs/code/node/cli.ts | 13 +++++-------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/cli/src/constants.rs b/cli/src/constants.rs index 3596e152cda..ca5b32c8e3d 100644 --- a/cli/src/constants.rs +++ b/cli/src/constants.rs @@ -61,6 +61,8 @@ pub const QUALITYLESS_SERVER_NAME: &str = concatcp!(QUALITYLESS_PRODUCT_NAME, " /// Web URL the editor is hosted at. For VS Code, this is vscode.dev. pub const EDITOR_WEB_URL: Option<&'static str> = option_env!("VSCODE_CLI_EDITOR_WEB_URL"); +const NONINTERACTIVE_VAR: &str = "VSCODE_CLI_NONINTERACTIVE"; + pub fn get_default_user_agent() -> String { format!( "vscode-server-launcher/{}", @@ -94,4 +96,7 @@ lazy_static! { /// Map of qualities to the server name pub static ref SERVER_NAME_MAP: Option> = option_env!("VSCODE_CLI_SERVER_NAME_MAP").and_then(|s| serde_json::from_str(s).unwrap()); + + /// Whether i/o interactions are allowed in the current CLI. + pub static ref IS_INTERACTIVE_CLI: bool = atty::is(atty::Stream::Stdin) && std::env::var(NONINTERACTIVE_VAR).is_err(); } diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index aeee42cb911..8759b721ca7 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ use crate::auth; use crate::constants::{ - CONTROL_PORT, PROTOCOL_VERSION_TAG, PROTOCOL_VERSION_TAG_PREFIX, TUNNEL_SERVICE_USER_AGENT, + CONTROL_PORT, PROTOCOL_VERSION_TAG, PROTOCOL_VERSION_TAG_PREFIX, TUNNEL_SERVICE_USER_AGENT, IS_INTERACTIVE_CLI, }; use crate::state::{LauncherPaths, PersistedState}; use crate::util::errors::{ @@ -659,7 +659,7 @@ impl DevTunnels { } let mut placeholder_name = name_generator::generate_name(MAX_TUNNEL_NAME_LENGTH); - if use_random_name { + if use_random_name || !*IS_INTERACTIVE_CLI { while !is_name_free(&placeholder_name) { placeholder_name = name_generator::generate_name(MAX_TUNNEL_NAME_LENGTH); } diff --git a/cli/src/tunnels/legal.rs b/cli/src/tunnels/legal.rs index 1f115222071..1e3d7b1bac3 100644 --- a/cli/src/tunnels/legal.rs +++ b/cli/src/tunnels/legal.rs @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -use crate::constants::PRODUCT_NAME_LONG; +use crate::constants::{IS_INTERACTIVE_CLI, PRODUCT_NAME_LONG}; use crate::state::{LauncherPaths, PersistedState}; use crate::util::errors::{AnyError, MissingLegalConsent}; use crate::util::input::prompt_yn; @@ -25,10 +25,6 @@ pub fn require_consent( None => return Ok(()), } - if accept_server_license_terms { - return Ok(()); - } - let prompt = match LICENSE_PROMPT { Some(p) => p, None => return Ok(()), @@ -37,13 +33,19 @@ pub fn require_consent( let license: PersistedState = PersistedState::new(paths.root().join("license_consent.json")); - let mut save = false; let mut load = license.load(); + if let Some(true) = load.consented { + return Ok(()); + } - if !load.consented.unwrap_or(false) { + if accept_server_license_terms { + load.consented = Some(true); + } else if !*IS_INTERACTIVE_CLI { + return Err(MissingLegalConsent("Run this command again with --accept-server-license-terms to indicate your agreement.".to_string()) + .into()); + } else { match prompt_yn(prompt) { Ok(true) => { - save = true; load.consented = Some(true); } Ok(false) => { @@ -56,9 +58,6 @@ pub fn require_consent( } } - if save { - license.save(load)?; - } - + license.save(load)?; Ok(()) } diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index bcefa8782e3..b2ef0aa70fa 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChildProcess, spawn, SpawnOptions } from 'child_process'; +import { ChildProcess, ChildProcessWithoutNullStreams, spawn, SpawnOptions } from 'child_process'; import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync } from 'fs'; import { homedir, release, tmpdir } from 'os'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; @@ -55,7 +55,7 @@ export async function main(argv: string[]): Promise { return; } return new Promise((resolve, reject) => { - let tunnelProcess; + let tunnelProcess: ChildProcessWithoutNullStreams; if (process.env['VSCODE_DEV']) { tunnelProcess = spawn('cargo', ['run', '--', 'tunnel', ...argv.slice(5)], { cwd: join(getAppRoot(), 'cli') }); } else { @@ -67,12 +67,9 @@ export async function main(argv: string[]): Promise { const tunnelArgs = argv.slice(3); tunnelProcess = spawn(tunnelCommand, ['tunnel', ...tunnelArgs]); } - tunnelProcess.stdout.on('data', data => { - console.log(data.toString()); - }); - tunnelProcess.stderr.on('data', data => { - console.error(data.toString()); - }); + + tunnelProcess.stdout.pipe(process.stdout); + tunnelProcess.stderr.pipe(process.stderr); tunnelProcess.on('exit', resolve); tunnelProcess.on('error', reject); });