Skip to content

Commit c71e11c

Browse files
committed
feat(core): implement onOutput for batch processes
1 parent 25b8550 commit c71e11c

File tree

10 files changed

+252
-7
lines changed

10 files changed

+252
-7
lines changed

packages/nx/src/native/index.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ export declare class AppLifeCycle {
2323
registerForcedShutdownCallback(forcedShutdownCallback: () => any): void
2424
__setCloudMessage(message: string): Promise<void>
2525
setEstimatedTaskTimings(timings: Record<string, number>): void
26+
registerRunningBatch(batchId: string, batchInfo: BatchInfo): void
27+
appendBatchOutput(batchId: string, output: string): void
28+
setBatchStatus(batchId: string, status: string): void
2629
}
2730

2831
export declare class ChildProcess {
@@ -193,6 +196,11 @@ export declare class WorkspaceContext {
193196
getFilesInDirectory(directory: string): Array<string>
194197
}
195198

199+
export interface BatchInfo {
200+
executorName: string
201+
taskIds: Array<string>
202+
}
203+
196204
export interface CachedResult {
197205
code: number
198206
terminalOutput?: string

packages/nx/src/native/tui/action.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::native::tasks::types::{Task, TaskResult};
22

3-
use super::{app::Focus, components::tasks_list::TaskStatus};
3+
use super::{app::{BatchInfo, BatchStatus, Focus}, components::tasks_list::TaskStatus};
44

55
#[derive(Debug, Clone, PartialEq, Eq)]
66
pub enum Action {
@@ -36,4 +36,7 @@ pub enum Action {
3636
SendConsoleMessage(String),
3737
ConsoleMessengerAvailable(bool),
3838
EndCommand,
39+
StartBatch(String, BatchInfo),
40+
UpdateBatchStatus(String, BatchStatus),
41+
AppendBatchOutput(String, String),
3942
}

packages/nx/src/native/tui/app.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ use crate::native::ide::nx_console::messaging::NxConsoleMessageConnection;
4545
use crate::native::tui::graph_utils::get_failed_dependencies;
4646
use crate::native::utils::time::current_timestamp_millis;
4747

48+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49+
pub enum BatchStatus {
50+
Running,
51+
Success,
52+
Failure,
53+
}
54+
55+
#[derive(Debug, Clone, PartialEq, Eq)]
56+
pub struct BatchInfo {
57+
pub executor_name: String,
58+
pub task_ids: Vec<String>,
59+
}
60+
61+
#[derive(Debug, Clone)]
62+
pub struct BatchState {
63+
pub info: BatchInfo, // executor_name, task_ids
64+
pub status: BatchStatus, // Running/Success/Failure
65+
}
66+
4867
pub struct App {
4968
pub components: Vec<Box<dyn Component>>,
5069
pub quit_at: Option<std::time::Instant>,
@@ -79,6 +98,9 @@ pub struct App {
7998
debug_state: TuiWidgetState,
8099
console_messenger: Option<NxConsoleMessageConnection>,
81100
estimated_task_timings: HashMap<String, i64>,
101+
// Batch tracking
102+
batch_states: HashMap<String, BatchState>, // batch_id → BatchState
103+
batch_pty_instances: HashMap<String, Arc<PtyInstance>>, // batch_id → PTY
82104
}
83105

84106
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -162,6 +184,9 @@ impl App {
162184
debug_state: TuiWidgetState::default().set_default_display_level(LevelFilter::Debug),
163185
console_messenger: None,
164186
estimated_task_timings: HashMap::new(),
187+
// Initialize batch tracking
188+
batch_states: HashMap::new(),
189+
batch_pty_instances: HashMap::new(),
165190
})
166191
}
167192

@@ -377,6 +402,44 @@ impl App {
377402
pty.process_output(output.as_bytes());
378403
}
379404

405+
// Batch methods
406+
pub fn register_running_batch(&mut self, batch_id: String, batch_info: BatchInfo) {
407+
// Create PTY instance for batch output
408+
let pty = PtyInstance::non_interactive();
409+
self.batch_pty_instances.insert(batch_id.clone(), Arc::new(pty));
410+
411+
// Store batch state with initial Running status
412+
self.batch_states.insert(batch_id.clone(), BatchState {
413+
info: batch_info.clone(),
414+
status: BatchStatus::Running, // Always starts as Running
415+
});
416+
417+
// Trigger resize for new PTY instance
418+
let _ = self.debounce_pty_resize();
419+
420+
self.dispatch_action(Action::StartBatch(batch_id, batch_info));
421+
}
422+
423+
pub fn append_batch_output(&mut self, batch_id: String, output: String) {
424+
if let Some(pty) = self.batch_pty_instances.get_mut(&batch_id) {
425+
pty.process_output(output.as_bytes());
426+
}
427+
self.dispatch_action(Action::AppendBatchOutput(batch_id, output));
428+
}
429+
430+
pub fn set_batch_status(&mut self, batch_id: String, status_str: String) {
431+
if let Some(batch_state) = self.batch_states.get_mut(&batch_id) {
432+
let new_status = match status_str.as_str() {
433+
"success" => BatchStatus::Success,
434+
"failure" => BatchStatus::Failure,
435+
_ => BatchStatus::Running,
436+
};
437+
438+
batch_state.status = new_status;
439+
self.dispatch_action(Action::UpdateBatchStatus(batch_id, new_status));
440+
}
441+
}
442+
380443
pub fn handle_event(
381444
&mut self,
382445
event: tui::Event,
@@ -1512,6 +1575,16 @@ impl App {
15121575
}
15131576
}
15141577

1578+
for pty in self.batch_pty_instances.values() {
1579+
// Use the first terminal pane area as reference for batch PTY dimensions
1580+
// This ensures batch PTY instances have consistent dimensions with task PTYs
1581+
if let Some(first_pane_area) = self.layout_areas.as_ref().unwrap().terminal_panes.first() {
1582+
let (pty_height, pty_width) = TerminalPane::calculate_pty_dimensions(*first_pane_area);
1583+
let mut pty_clone = pty.as_ref().clone();
1584+
pty_clone.resize(pty_height, pty_width)?;
1585+
}
1586+
}
1587+
15151588
// Sort tasks if needed after all resizing is complete
15161589
if needs_sort {
15171590
self.dispatch_action(Action::SortTasks);

packages/nx/src/native/tui/lifecycle.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::native::{
1212
pseudo_terminal::pseudo_terminal::{ParserArc, WriterArc},
1313
};
1414

15-
use super::app::App;
15+
use super::app::{App, BatchInfo as RustBatchInfo};
1616
use super::components::tasks_list::TaskStatus;
1717
use super::config::{AutoExit, TuiCliArgs as RustTuiCliArgs, TuiConfig as RustTuiConfig};
1818
use super::tui::Tui;
@@ -63,6 +63,22 @@ pub enum RunMode {
6363
RunMany,
6464
}
6565

66+
#[napi(object)]
67+
#[derive(Clone)]
68+
pub struct BatchInfo {
69+
pub executor_name: String,
70+
pub task_ids: Vec<String>,
71+
}
72+
73+
impl From<BatchInfo> for RustBatchInfo {
74+
fn from(js: BatchInfo) -> Self {
75+
Self {
76+
executor_name: js.executor_name,
77+
task_ids: js.task_ids,
78+
}
79+
}
80+
}
81+
6682
#[napi]
6783
#[derive(Clone)]
6884
pub struct AppLifeCycle {
@@ -308,6 +324,25 @@ impl AppLifeCycle {
308324
self.app.lock().set_estimated_task_timings(timings);
309325
Ok(())
310326
}
327+
328+
// Batch lifecycle methods
329+
#[napi]
330+
pub fn register_running_batch(&mut self, batch_id: String, batch_info: BatchInfo) -> napi::Result<()> {
331+
self.app.lock().register_running_batch(batch_id, batch_info.into());
332+
Ok(())
333+
}
334+
335+
#[napi]
336+
pub fn append_batch_output(&mut self, batch_id: String, output: String) -> napi::Result<()> {
337+
self.app.lock().append_batch_output(batch_id, output);
338+
Ok(())
339+
}
340+
341+
#[napi]
342+
pub fn set_batch_status(&mut self, batch_id: String, status: String) -> napi::Result<()> {
343+
self.app.lock().set_batch_status(batch_id, status);
344+
Ok(())
345+
}
311346
}
312347

313348
#[napi]

packages/nx/src/tasks-runner/forked-process-task-runner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export class ForkedProcessTaskRunner {
6363
}
6464

6565
const p = fork(workerPath, {
66-
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
66+
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
6767
env: {
6868
...env,
6969
NX_FORKED_TASK_EXECUTOR: 'true',

packages/nx/src/tasks-runner/life-cycle.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Task } from '../config/task-graph';
22
import { ExternalObject, TaskStatus as NativeTaskStatus } from '../native';
3-
import { RunningTask } from './running-tasks/running-task';
43
import { TaskStatus } from './tasks-runner';
54

65
/**
@@ -22,6 +21,13 @@ export interface TaskMetadata {
2221
groupId: number;
2322
}
2423

24+
export interface BatchInfo {
25+
executorName: string;
26+
taskIds: string[];
27+
}
28+
29+
export type BatchStatus = 'running' | 'success' | 'failure';
30+
2531
export interface LifeCycle {
2632
startCommand?(parallel?: number): void | Promise<void>;
2733

@@ -70,6 +76,13 @@ export interface LifeCycle {
7076
registerForcedShutdownCallback?(callback: () => void): void;
7177

7278
setEstimatedTaskTimings?(timings: Record<string, number>): void;
79+
80+
// Batch-specific lifecycle methods
81+
registerRunningBatch?(batchId: string, batchInfo: BatchInfo): void;
82+
83+
appendBatchOutput?(batchId: string, output: string): void;
84+
85+
setBatchStatus?(batchId: string, status: BatchStatus): void;
7386
}
7487

7588
export class CompositeLifeCycle implements LifeCycle {
@@ -200,4 +213,28 @@ export class CompositeLifeCycle implements LifeCycle {
200213
}
201214
}
202215
}
216+
217+
registerRunningBatch(batchId: string, batchInfo: BatchInfo): void {
218+
for (let l of this.lifeCycles) {
219+
if (l.registerRunningBatch) {
220+
l.registerRunningBatch(batchId, batchInfo);
221+
}
222+
}
223+
}
224+
225+
appendBatchOutput(batchId: string, output: string): void {
226+
for (let l of this.lifeCycles) {
227+
if (l.appendBatchOutput) {
228+
l.appendBatchOutput(batchId, output);
229+
}
230+
}
231+
}
232+
233+
setBatchStatus(batchId: string, status: BatchStatus): void {
234+
for (let l of this.lifeCycles) {
235+
if (l.setBatchStatus) {
236+
l.setBatchStatus(batchId, status);
237+
}
238+
}
239+
}
203240
}

packages/nx/src/tasks-runner/running-tasks/batch-process.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { signalToCode } from '../../utils/exit-codes';
99
export class BatchProcess {
1010
private exitCallbacks: Array<(code: number) => void> = [];
1111
private resultsCallbacks: Array<(results: BatchResults) => void> = [];
12+
private outputCallbacks: Array<(output: string) => void> = [];
13+
private terminalOutput: string = '';
1214

1315
constructor(
1416
private childProcess: ChildProcess,
@@ -41,6 +43,38 @@ export class BatchProcess {
4143
cb(code);
4244
}
4345
});
46+
47+
// Capture stdout output
48+
if (this.childProcess.stdout) {
49+
this.childProcess.stdout.on('data', (chunk) => {
50+
const output = chunk.toString();
51+
this.terminalOutput += output;
52+
53+
// Maintain current terminal output behavior
54+
process.stdout.write(chunk);
55+
56+
// Notify callbacks for TUI
57+
for (const cb of this.outputCallbacks) {
58+
cb(output);
59+
}
60+
});
61+
}
62+
63+
// Capture stderr output
64+
if (this.childProcess.stderr) {
65+
this.childProcess.stderr.on('data', (chunk) => {
66+
const output = chunk.toString();
67+
this.terminalOutput += output;
68+
69+
// Maintain current terminal output behavior
70+
process.stderr.write(chunk);
71+
72+
// Notify callbacks for TUI
73+
for (const cb of this.outputCallbacks) {
74+
cb(output);
75+
}
76+
});
77+
}
4478
}
4579

4680
onExit(cb: (code: number) => void) {
@@ -51,6 +85,10 @@ export class BatchProcess {
5185
this.resultsCallbacks.push(cb);
5286
}
5387

88+
onOutput(cb: (output: string) => void) {
89+
this.outputCallbacks.push(cb);
90+
}
91+
5492
async getResults(): Promise<BatchResults> {
5593
return Promise.race<BatchResults>([
5694
new Promise((_, rej) => {
@@ -81,4 +119,8 @@ export class BatchProcess {
81119
this.childProcess.kill(signal);
82120
}
83121
}
122+
123+
getTerminalOutput(): string {
124+
return this.terminalOutput;
125+
}
84126
}

0 commit comments

Comments
 (0)