fix(core): tui summary should capture more terminal outputs (#31113)

## Current Behavior
Some task outputs are missing in terminal outputs

## Expected Behavior
Task outputs are present

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Craigory Coppola 2025-05-07 18:37:21 -04:00 committed by GitHub
parent 6f9cce78ac
commit 98d3354855
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 68 additions and 25 deletions

View File

@ -18,7 +18,7 @@ export declare class AppLifeCycle {
__init(doneCallback: () => any): void __init(doneCallback: () => any): void
registerRunningTask(taskId: string, parserAndWriter: ExternalObject<[ParserArc, WriterArc]>): void registerRunningTask(taskId: string, parserAndWriter: ExternalObject<[ParserArc, WriterArc]>): void
registerRunningTaskWithEmptyParser(taskId: string): 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 setTaskStatus(taskId: string, status: TaskStatus): void
registerForcedShutdownCallback(forcedShutdownCallback: () => any): void registerForcedShutdownCallback(forcedShutdownCallback: () => any): void
__setCloudMessage(message: string): Promise<void> __setCloudMessage(message: string): Promise<void>

View File

@ -260,9 +260,12 @@ impl AppLifeCycle {
} }
#[napi] #[napi]
pub fn append_task_output(&mut self, task_id: String, output: String) { pub fn append_task_output(&mut self, task_id: String, output: String, is_pty_output: bool) {
let mut app = self.app.lock().unwrap(); // If its from a pty, we already have it in the parser, so we don't need to append it again
app.append_task_output(task_id, output) if !is_pty_output {
let mut app = self.app.lock().unwrap();
app.append_task_output(task_id, output)
}
} }
#[napi] #[napi]

View File

@ -71,7 +71,7 @@ export interface LifeCycle {
registerRunningTaskWithEmptyParser?(taskId: string): void; registerRunningTaskWithEmptyParser?(taskId: string): void;
appendTaskOutput?(taskId: string, output: string): void; appendTaskOutput?(taskId: string, output: string, isPtyTask: boolean): void;
setTaskStatus?(taskId: string, status: NativeTaskStatus): 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) { for (let l of this.lifeCycles) {
if (l.appendTaskOutput) { if (l.appendTaskOutput) {
l.appendTaskOutput(taskId, output); l.appendTaskOutput(taskId, output, isPtyTask);
} }
} }
} }

View File

@ -60,6 +60,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
}); });
lifeCycle.startTasks([dep], null); lifeCycle.startTasks([dep], null);
lifeCycle.appendTaskOutput(dep.id, 'boom', true);
lifeCycle.endTasks( lifeCycle.endTasks(
[ [
{ {
@ -118,6 +119,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
}); });
lifeCycle.startTasks([dep], null); lifeCycle.startTasks([dep], null);
lifeCycle.appendTaskOutput(dep.id, ':)', true);
lifeCycle.endTasks( lifeCycle.endTasks(
[ [
{ {
@ -131,6 +133,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
); );
lifeCycle.printTaskTerminalOutput(dep, 'success', ':)'); lifeCycle.printTaskTerminalOutput(dep, 'success', ':)');
lifeCycle.startTasks([target], null); lifeCycle.startTasks([target], null);
lifeCycle.appendTaskOutput(target.id, "Wait, I'm not done yet", true);
lifeCycle.endCommand(); lifeCycle.endCommand();
const lines = getOutputLines(printSummary); const lines = getOutputLines(printSummary);
@ -140,6 +143,9 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
> nx run test:pre-test > nx run test:pre-test
:) :)
> nx run test:test
Wait, I'm not done yet
NX Cancelled running target test for project test (37w) NX Cancelled running target test for project test (37w)
@ -174,6 +180,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
}); });
lifeCycle.startTasks([dep], null); lifeCycle.startTasks([dep], null);
lifeCycle.appendTaskOutput(dep.id, ':)', true);
lifeCycle.endTasks( lifeCycle.endTasks(
[ [
{ {
@ -236,6 +243,7 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
}); });
lifeCycle.startTasks([target], null); lifeCycle.startTasks([target], null);
lifeCycle.appendTaskOutput(target.id, 'I was a happy dev server', true);
lifeCycle.setTaskStatus(target.id, NativeTaskStatus.Stopped); lifeCycle.setTaskStatus(target.id, NativeTaskStatus.Stopped);
lifeCycle.printTaskTerminalOutput( lifeCycle.printTaskTerminalOutput(
target, target,
@ -295,7 +303,9 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
resolveRenderIsDonePromise: jest.fn().mockResolvedValue(null), 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( lifeCycle.endTasks(
[ [
{ {
@ -378,6 +388,8 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
}); });
lifeCycle.startTasks([bar, foo], null); lifeCycle.startTasks([bar, foo], null);
lifeCycle.appendTaskOutput(foo.id, 'Stop, in the name of', true);
lifeCycle.appendTaskOutput(bar.id, 'Love', true);
lifeCycle.endTasks( lifeCycle.endTasks(
[ [
{ {
@ -396,6 +408,11 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
expect(lines.join('\n')).toMatchInlineSnapshot(` expect(lines.join('\n')).toMatchInlineSnapshot(`
" "
> nx run bar:test
Love
nx run bar:test
nx run foo:test nx run foo:test
@ -446,7 +463,9 @@ describe('getTuiTerminalSummaryLifeCycle', () => {
resolveRenderIsDonePromise: jest.fn().mockResolvedValue(null), 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( lifeCycle.endTasks(
[ [
{ {

View File

@ -7,7 +7,7 @@ import type { TaskStatus } from '../tasks-runner';
import { formatFlags, formatTargetsAndProjects } from './formatting-utils'; import { formatFlags, formatTargetsAndProjects } from './formatting-utils';
import { prettyTime } from './pretty-time'; import { prettyTime } from './pretty-time';
import { viewLogsFooterRows } from './view-logs-utils'; import { viewLogsFooterRows } from './view-logs-utils';
import figures = require('figures'); import * as figures from 'figures';
import { getTasksHistoryLifeCycle } from './task-history-life-cycle'; import { getTasksHistoryLifeCycle } from './task-history-life-cycle';
import { getLeafTasks } from '../task-graph-utils'; import { getLeafTasks } from '../task-graph-utils';
@ -50,23 +50,27 @@ export function getTuiTerminalSummaryLifeCycle({
const inProgressTasks = new Set<string>(); const inProgressTasks = new Set<string>();
const stoppedTasks = new Set<string>(); const stoppedTasks = new Set<string>();
const tasksToTerminalOutputs: Record< const tasksToTerminalOutputs: Record<string, string> = {};
string, const tasksToTaskStatus: Record<string, TaskStatus> = {};
{ terminalOutput: string; taskStatus: TaskStatus }
> = {}; const taskIdsInTheOrderTheyStart: string[] = [];
const taskIdsInOrderOfCompletion: string[] = [];
lifeCycle.startTasks = (tasks) => { lifeCycle.startTasks = (tasks) => {
for (let t of tasks) { for (let t of tasks) {
tasksToTerminalOutputs[t.id] ??= '';
taskIdsInTheOrderTheyStart.push(t.id);
inProgressTasks.add(t.id); inProgressTasks.add(t.id);
} }
}; };
lifeCycle.printTaskTerminalOutput = (task, taskStatus, terminalOutput) => { lifeCycle.appendTaskOutput = (taskId, output) => {
taskIdsInOrderOfCompletion.push(task.id); tasksToTerminalOutputs[taskId] += output;
tasksToTerminalOutputs[task.id] = { terminalOutput, taskStatus };
}; };
// 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) => { lifeCycle.setTaskStatus = (taskId, taskStatus) => {
if (taskStatus === NativeTaskStatus.Stopped) { if (taskStatus === NativeTaskStatus.Stopped) {
stoppedTasks.add(taskId); stoppedTasks.add(taskId);
@ -149,8 +153,9 @@ export function getTuiTerminalSummaryLifeCycle({
// Prints task outputs in the order they were completed // Prints task outputs in the order they were completed
// above the summary, since run-one should print all task results. // above the summary, since run-one should print all task results.
for (const taskId of taskIdsInOrderOfCompletion) { for (const taskId of taskIdsInTheOrderTheyStart) {
const { terminalOutput, taskStatus } = tasksToTerminalOutputs[taskId]; const taskStatus = tasksToTaskStatus[taskId];
const terminalOutput = tasksToTerminalOutputs[taskId];
output.logCommandOutput(taskId, taskStatus, terminalOutput); output.logCommandOutput(taskId, taskStatus, terminalOutput);
} }
@ -266,9 +271,19 @@ export function getTuiTerminalSummaryLifeCycle({
const lines: string[] = ['']; const lines: string[] = [''];
for (const taskId of taskIdsInOrderOfCompletion) { for (const taskId of taskIdsInTheOrderTheyStart) {
const { terminalOutput, taskStatus } = tasksToTerminalOutputs[taskId]; const taskStatus = tasksToTaskStatus[taskId];
if (taskStatus === 'failure') { 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.logCommandOutput(taskId, taskStatus, terminalOutput);
output.addNewline(); output.addNewline();
lines.push( lines.push(

View File

@ -568,10 +568,13 @@ export class TaskOrchestrator {
task.id, task.id,
runningTask.getParserAndWriter() runningTask.getParserAndWriter()
); );
runningTask.onOutput((output) => {
this.options.lifeCycle.appendTaskOutput(task.id, output, true);
});
} else { } else {
this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id);
runningTask.onOutput((output) => { 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, task.id,
runningTask.getParserAndWriter() runningTask.getParserAndWriter()
); );
runningTask.onOutput((output) => {
this.options.lifeCycle.appendTaskOutput(task.id, output, true);
});
} else if ('onOutput' in runningTask) { } else if ('onOutput' in runningTask) {
this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id); this.options.lifeCycle.registerRunningTaskWithEmptyParser(task.id);
runningTask.onOutput((output) => { runningTask.onOutput((output) => {
this.options.lifeCycle.appendTaskOutput(task.id, output); this.options.lifeCycle.appendTaskOutput(task.id, output, false);
}); });
} }
} }