diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index f7f148ae54..0c24ff8fa8 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -18,7 +18,7 @@ export declare class AppLifeCycle { __init(doneCallback: () => any): void registerRunningTask(taskId: string, parserAndWriter: ExternalObject<[ParserArc, WriterArc]>): void registerRunningTaskWithEmptyParser(taskId: string): void - appendTaskOutput(taskId: string, output: string): void + appendTaskOutput(taskId: string, output: string, isPtyOutput: boolean): void setTaskStatus(taskId: string, status: TaskStatus): void registerForcedShutdownCallback(forcedShutdownCallback: () => any): void __setCloudMessage(message: string): Promise diff --git a/packages/nx/src/native/tui/lifecycle.rs b/packages/nx/src/native/tui/lifecycle.rs index e1d500ecfc..e345e952ad 100644 --- a/packages/nx/src/native/tui/lifecycle.rs +++ b/packages/nx/src/native/tui/lifecycle.rs @@ -260,9 +260,12 @@ impl AppLifeCycle { } #[napi] - pub fn append_task_output(&mut self, task_id: String, output: String) { - let mut app = self.app.lock().unwrap(); - app.append_task_output(task_id, output) + pub fn append_task_output(&mut self, task_id: String, output: String, is_pty_output: bool) { + // If its from a pty, we already have it in the parser, so we don't need to append it again + if !is_pty_output { + let mut app = self.app.lock().unwrap(); + app.append_task_output(task_id, output) + } } #[napi] diff --git a/packages/nx/src/tasks-runner/life-cycle.ts b/packages/nx/src/tasks-runner/life-cycle.ts index 25309da528..10d373eb6d 100644 --- a/packages/nx/src/tasks-runner/life-cycle.ts +++ b/packages/nx/src/tasks-runner/life-cycle.ts @@ -71,7 +71,7 @@ export interface LifeCycle { registerRunningTaskWithEmptyParser?(taskId: string): void; - appendTaskOutput?(taskId: string, output: string): void; + appendTaskOutput?(taskId: string, output: string, isPtyTask: boolean): void; setTaskStatus?(taskId: string, status: NativeTaskStatus): void; @@ -175,10 +175,10 @@ export class CompositeLifeCycle implements LifeCycle { } } - appendTaskOutput(taskId: string, output: string): void { + appendTaskOutput(taskId: string, output: string, isPtyTask: boolean): void { for (let l of this.lifeCycles) { if (l.appendTaskOutput) { - l.appendTaskOutput(taskId, output); + l.appendTaskOutput(taskId, output, isPtyTask); } } } diff --git a/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.spec.ts b/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.spec.ts index 665b27f47a..91fb1c0f73 100644 --- a/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.spec.ts +++ b/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.spec.ts @@ -60,6 +60,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => { }); lifeCycle.startTasks([dep], null); + lifeCycle.appendTaskOutput(dep.id, 'boom', true); lifeCycle.endTasks( [ { @@ -118,6 +119,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => { }); lifeCycle.startTasks([dep], null); + lifeCycle.appendTaskOutput(dep.id, ':)', true); lifeCycle.endTasks( [ { @@ -131,6 +133,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => { ); lifeCycle.printTaskTerminalOutput(dep, 'success', ':)'); lifeCycle.startTasks([target], null); + lifeCycle.appendTaskOutput(target.id, "Wait, I'm not done yet", true); lifeCycle.endCommand(); const lines = getOutputLines(printSummary); @@ -140,6 +143,9 @@ describe('getTuiTerminalSummaryLifeCycle', () => { > nx run test:pre-test :) + > nx run test:test + + Wait, I'm not done yet ——————————————————————————————————————————————————————————————————————————————— NX Cancelled running target test for project test (37w) @@ -174,6 +180,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => { }); lifeCycle.startTasks([dep], null); + lifeCycle.appendTaskOutput(dep.id, ':)', true); lifeCycle.endTasks( [ { @@ -236,6 +243,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => { }); lifeCycle.startTasks([target], null); + lifeCycle.appendTaskOutput(target.id, 'I was a happy dev server', true); lifeCycle.setTaskStatus(target.id, NativeTaskStatus.Stopped); lifeCycle.printTaskTerminalOutput( target, @@ -295,7 +303,9 @@ describe('getTuiTerminalSummaryLifeCycle', () => { resolveRenderIsDonePromise: jest.fn().mockResolvedValue(null), }); - lifeCycle.startTasks([bar, foo], null); + lifeCycle.startTasks([foo, bar], null); + lifeCycle.appendTaskOutput(foo.id, ':)', true); + lifeCycle.appendTaskOutput(bar.id, 'boom', true); lifeCycle.endTasks( [ { @@ -378,6 +388,8 @@ describe('getTuiTerminalSummaryLifeCycle', () => { }); lifeCycle.startTasks([bar, foo], null); + lifeCycle.appendTaskOutput(foo.id, 'Stop, in the name of', true); + lifeCycle.appendTaskOutput(bar.id, 'Love', true); lifeCycle.endTasks( [ { @@ -396,6 +408,11 @@ describe('getTuiTerminalSummaryLifeCycle', () => { expect(lines.join('\n')).toMatchInlineSnapshot(` " + > nx run bar:test + + Love + + ◼ nx run bar:test ✔ nx run foo:test ——————————————————————————————————————————————————————————————————————————————— @@ -446,7 +463,9 @@ describe('getTuiTerminalSummaryLifeCycle', () => { resolveRenderIsDonePromise: jest.fn().mockResolvedValue(null), }); - lifeCycle.startTasks([bar, foo], null); + lifeCycle.startTasks([foo, bar], null); + lifeCycle.appendTaskOutput(foo.id, ':)', true); + lifeCycle.appendTaskOutput(bar.id, ':)', true); lifeCycle.endTasks( [ { diff --git a/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.ts b/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.ts index f095f410ab..9d3d61d538 100644 --- a/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.ts +++ b/packages/nx/src/tasks-runner/life-cycles/tui-summary-life-cycle.ts @@ -7,7 +7,7 @@ import type { TaskStatus } from '../tasks-runner'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils'; import { prettyTime } from './pretty-time'; import { viewLogsFooterRows } from './view-logs-utils'; -import figures = require('figures'); +import * as figures from 'figures'; import { getTasksHistoryLifeCycle } from './task-history-life-cycle'; import { getLeafTasks } from '../task-graph-utils'; @@ -50,23 +50,27 @@ export function getTuiTerminalSummaryLifeCycle({ const inProgressTasks = new Set(); const stoppedTasks = new Set(); - const tasksToTerminalOutputs: Record< - string, - { terminalOutput: string; taskStatus: TaskStatus } - > = {}; - const taskIdsInOrderOfCompletion: string[] = []; + const tasksToTerminalOutputs: Record = {}; + const tasksToTaskStatus: Record = {}; + + const taskIdsInTheOrderTheyStart: string[] = []; lifeCycle.startTasks = (tasks) => { for (let t of tasks) { + tasksToTerminalOutputs[t.id] ??= ''; + taskIdsInTheOrderTheyStart.push(t.id); inProgressTasks.add(t.id); } }; - lifeCycle.printTaskTerminalOutput = (task, taskStatus, terminalOutput) => { - taskIdsInOrderOfCompletion.push(task.id); - tasksToTerminalOutputs[task.id] = { terminalOutput, taskStatus }; + lifeCycle.appendTaskOutput = (taskId, output) => { + tasksToTerminalOutputs[taskId] += output; }; + // TODO(@AgentEnder): The following 2 methods should be one but will need more refactoring + lifeCycle.printTaskTerminalOutput = (task, taskStatus) => { + tasksToTaskStatus[task.id] = taskStatus; + }; lifeCycle.setTaskStatus = (taskId, taskStatus) => { if (taskStatus === NativeTaskStatus.Stopped) { stoppedTasks.add(taskId); @@ -149,8 +153,9 @@ export function getTuiTerminalSummaryLifeCycle({ // Prints task outputs in the order they were completed // above the summary, since run-one should print all task results. - for (const taskId of taskIdsInOrderOfCompletion) { - const { terminalOutput, taskStatus } = tasksToTerminalOutputs[taskId]; + for (const taskId of taskIdsInTheOrderTheyStart) { + const taskStatus = tasksToTaskStatus[taskId]; + const terminalOutput = tasksToTerminalOutputs[taskId]; output.logCommandOutput(taskId, taskStatus, terminalOutput); } @@ -266,9 +271,19 @@ export function getTuiTerminalSummaryLifeCycle({ const lines: string[] = ['']; - for (const taskId of taskIdsInOrderOfCompletion) { - const { terminalOutput, taskStatus } = tasksToTerminalOutputs[taskId]; - if (taskStatus === 'failure') { + for (const taskId of taskIdsInTheOrderTheyStart) { + const taskStatus = tasksToTaskStatus[taskId]; + const terminalOutput = tasksToTerminalOutputs[taskId]; + // Task Status is null? + if (!taskStatus) { + output.logCommandOutput(taskId, taskStatus, terminalOutput); + output.addNewline(); + lines.push( + `${LEFT_PAD}${output.colors.cyan( + figures.squareSmallFilled + )}${SPACER}${output.colors.gray('nx run ')}${taskId}` + ); + } else if (taskStatus === 'failure') { output.logCommandOutput(taskId, taskStatus, terminalOutput); output.addNewline(); lines.push( diff --git a/packages/nx/src/tasks-runner/task-orchestrator.ts b/packages/nx/src/tasks-runner/task-orchestrator.ts index 73257cc5b8..89d4e40167 100644 --- a/packages/nx/src/tasks-runner/task-orchestrator.ts +++ b/packages/nx/src/tasks-runner/task-orchestrator.ts @@ -568,10 +568,13 @@ export class TaskOrchestrator { task.id, runningTask.getParserAndWriter() ); + runningTask.onOutput((output) => { + this.options.lifeCycle.appendTaskOutput(task.id, output, true); + }); } else { this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); runningTask.onOutput((output) => { - this.options.lifeCycle.appendTaskOutput(task.id, output); + this.options.lifeCycle.appendTaskOutput(task.id, output, false); }); } } @@ -640,10 +643,13 @@ export class TaskOrchestrator { task.id, runningTask.getParserAndWriter() ); + runningTask.onOutput((output) => { + this.options.lifeCycle.appendTaskOutput(task.id, output, true); + }); } else if ('onOutput' in runningTask) { this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); runningTask.onOutput((output) => { - this.options.lifeCycle.appendTaskOutput(task.id, output); + this.options.lifeCycle.appendTaskOutput(task.id, output, false); }); } }