From cb5eeb74758c5fc5c911dcc496ea866ed601a3fb Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Mon, 22 Jan 2024 12:25:52 -0500 Subject: [PATCH] feat(core): forward stdin to commands started via rust (#21195) Co-authored-by: Jonathan Cammisuli --- .prettierignore | 2 +- Cargo.lock | 58 +++++++++++++++---- packages/nx/Cargo.toml | 2 +- .../nx/src/command-line/affected/affected.ts | 2 - .../nx/src/command-line/release/publish.ts | 2 - .../nx/src/command-line/run-many/run-many.ts | 2 - packages/nx/src/command-line/run/run-one.ts | 2 - packages/nx/src/native/command.rs | 25 +++++++- packages/nx/src/native/tests/command.spec.ts | 19 +++--- 9 files changed, 81 insertions(+), 33 deletions(-) diff --git a/.prettierignore b/.prettierignore index 2930b83bc3ea0..f8d347e3e6db5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -16,7 +16,7 @@ packages/jest/src/schematics/**/files/**/*.json packages/nx/src/plugins/js/lock-file/__fixtures__/**/*.* packages/**/schematics/**/files/**/*.html packages/**/generators/**/files/**/*.html -packages/nx/src/native/ +packages/nx/src/native/**/*.rs nx-dev/nx-dev/.next/ nx-dev/nx-dev/public/documentation graph/client/src/assets/environment.js diff --git a/Cargo.lock b/Cargo.lock index dd6b5bcce2f6a..aa12934578a58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,6 +346,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "ctor" version = "0.2.0" @@ -1503,6 +1528,7 @@ dependencies = [ "assert_fs", "colored", "crossbeam-channel", + "crossterm", "dashmap", "dunce", "fs_extra", @@ -1526,7 +1552,6 @@ dependencies = [ "swc_ecma_dep_graph", "swc_ecma_parser", "swc_ecma_visit", - "term_size", "thiserror", "tokio", "tracing", @@ -2080,6 +2105,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2383,16 +2429,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "termios" version = "0.2.2" diff --git a/packages/nx/Cargo.toml b/packages/nx/Cargo.toml index bfaee51f85266..bb61e54066719 100644 --- a/packages/nx/Cargo.toml +++ b/packages/nx/Cargo.toml @@ -42,7 +42,7 @@ swc_common = "0.31.16" swc_ecma_parser = { version = "0.137.1", features = ["typescript"] } swc_ecma_visit = "0.93.0" swc_ecma_ast = "0.107.0" -term_size = "0.3.2" +crossterm = "0.27.0" [lib] crate-type = ['cdylib'] diff --git a/packages/nx/src/command-line/affected/affected.ts b/packages/nx/src/command-line/affected/affected.ts index e62c2e9134759..82c296b970318 100644 --- a/packages/nx/src/command-line/affected/affected.ts +++ b/packages/nx/src/command-line/affected/affected.ts @@ -114,8 +114,6 @@ export async function affected( extraTargetDependencies, { excludeTaskDependencies: false, loadDotEnvFiles: true } ); - // fix for https://github.com/nrwl/nx/issues/1666 - if (process.stdin['unref']) (process.stdin as any).unref(); process.exit(status); } break; diff --git a/packages/nx/src/command-line/release/publish.ts b/packages/nx/src/command-line/release/publish.ts index 414e4c3d4857e..989a8074a25c4 100644 --- a/packages/nx/src/command-line/release/publish.ts +++ b/packages/nx/src/command-line/release/publish.ts @@ -177,8 +177,6 @@ async function runPublishOnProjects( ); if (status !== 0) { - // fix for https://github.com/nrwl/nx/issues/1666 - if (process.stdin['unref']) (process.stdin as any).unref(); process.exit(status); } } diff --git a/packages/nx/src/command-line/run-many/run-many.ts b/packages/nx/src/command-line/run-many/run-many.ts index ddbfc34bb0628..24fb990be6494 100644 --- a/packages/nx/src/command-line/run-many/run-many.ts +++ b/packages/nx/src/command-line/run-many/run-many.ts @@ -75,8 +75,6 @@ export async function runMany( extraTargetDependencies, extraOptions ); - // fix for https://github.com/nrwl/nx/issues/1666 - if (process.stdin['unref']) (process.stdin as any).unref(); process.exit(status); } } diff --git a/packages/nx/src/command-line/run/run-one.ts b/packages/nx/src/command-line/run/run-one.ts index fbf64fe3c2d80..c010baadc3192 100644 --- a/packages/nx/src/command-line/run/run-one.ts +++ b/packages/nx/src/command-line/run/run-one.ts @@ -89,8 +89,6 @@ export async function runOne( extraTargetDependencies, extraOptions ); - // fix for https://github.com/nrwl/nx/issues/1666 - if (process.stdin['unref']) (process.stdin as any).unref(); process.exit(status); } } diff --git a/packages/nx/src/native/command.rs b/packages/nx/src/native/command.rs index 8e3c167b11df9..31a4675d4cbf6 100644 --- a/packages/nx/src/native/command.rs +++ b/packages/nx/src/native/command.rs @@ -5,6 +5,8 @@ use std::{ use anyhow::anyhow; use crossbeam_channel::{bounded, unbounded, Receiver}; +use crossterm::terminal::{self, disable_raw_mode, enable_raw_mode}; +use crossterm::tty::IsTty; use napi::threadsafe_function::ErrorStrategy::Fatal; use napi::threadsafe_function::ThreadsafeFunction; use napi::threadsafe_function::ThreadsafeFunctionCallMode::NonBlocking; @@ -126,10 +128,10 @@ pub fn run_command( let pty_system = NativePtySystem::default(); - let (w, h) = term_size::dimensions().unwrap_or((80, 24)); + let (w, h) = terminal::size().unwrap_or((80, 24)); let pair = pty_system.openpty(PtySize { - rows: h as u16, - cols: w as u16, + rows: h, + cols: w, pixel_width: 0, pixel_height: 0, })?; @@ -148,6 +150,8 @@ pub fn run_command( let reader = pair.master.try_clone_reader()?; let mut stdout = std::io::stdout(); + + // Output -> stdout handling std::thread::spawn(move || { let mut reader = BufReader::new(reader); let mut buffer = [0; 8 * 1024]; @@ -182,10 +186,25 @@ pub fn run_command( let process_killer = child.clone_killer(); let (exit_tx, exit_rx) = bounded(1); + + let mut writer = pair.master.take_writer()?; + + // Stdin -> pty stdin + if std::io::stdout().is_tty() { + std::thread::spawn(move || { + enable_raw_mode().expect("Failed to enter raw terminal mode"); + let mut stdin = std::io::stdin(); + #[allow(clippy::redundant_pattern_matching)] + // ignore errors that come from copying the stream + if let Ok(_) = std::io::copy(&mut stdin, &mut writer) {} + }); + } + std::thread::spawn(move || { let exit = child.wait().unwrap(); // make sure that master is only dropped after we wait on the child. Otherwise windows does not like it drop(pair.master); + disable_raw_mode().expect("Failed to restore non-raw terminal"); exit_tx.send(exit.exit_code()).ok(); }); diff --git a/packages/nx/src/native/tests/command.spec.ts b/packages/nx/src/native/tests/command.spec.ts index 800f86c58b10a..1d81b79b13add 100644 --- a/packages/nx/src/native/tests/command.spec.ts +++ b/packages/nx/src/native/tests/command.spec.ts @@ -10,39 +10,40 @@ describe('runCommand', () => { }); it('should kill a running command', () => { const childProcess = new PseudoTtyProcess( - runCommand( - 'sleep 3 && echo "hello world" > file.txt', - process.cwd() - ) + runCommand('sleep 3 && echo "hello world" > file.txt', process.cwd()) ); childProcess.onExit((exit_code) => { expect(exit_code).not.toEqual(0); }); childProcess.kill(); expect(childProcess.isAlive).toEqual(false); - }, 1000); it('should subscribe to output', (done) => { const childProcess = runCommand('echo "hello world"', process.cwd()); - childProcess.onOutput((output) => { - expect(output.trim()).toEqual('hello world'); + let output = ''; + childProcess.onOutput((chunk) => { + output += chunk; }); childProcess.onExit(() => { + expect(output.trim()).toEqual('hello world'); done(); }); }); it('should be tty', (done) => { const childProcess = runCommand('node -p "process.stdout.isTTY"'); + let output = ''; childProcess.onOutput((out) => { - let output = JSON.stringify(out.trim()); + output += out.trim(); // check to make sure that we have ansi sequence characters only available in tty terminals - expect(output).toMatchInlineSnapshot(`""\\u001b[33mtrue\\u001b[39m""`); }); childProcess.onExit((_) => { + expect(JSON.stringify(output)).toMatchInlineSnapshot( + `""\\u001b[33mtrue\\u001b[39m""` + ); done(); }); });