feat(core): forward stdin to commands started via rust (#21195)

Co-authored-by: Jonathan Cammisuli <jon@cammisuli.ca>
This commit is contained in:
Craigory Coppola 2024-01-22 12:25:52 -05:00 committed by GitHub
parent 33e13910b1
commit cb5eeb7475
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 81 additions and 33 deletions

View File

@ -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

58
Cargo.lock generated
View File

@ -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"

View File

@ -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']

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
});

View File

@ -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();
});
});