diff --git a/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle-old.ts b/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle-old.ts index 7b0097829d..e1c62c9400 100644 --- a/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle-old.ts +++ b/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle-old.ts @@ -12,6 +12,7 @@ import { LifeCycle, TaskResult } from '../life-cycle'; export class LegacyTaskHistoryLifeCycle implements LifeCycle { private startTimings: Record = {}; private taskRuns: TaskRun[] = []; + private flakyTasks: string[]; startTasks(tasks: Task[]): void { for (let task of tasks) { @@ -38,7 +39,7 @@ export class LegacyTaskHistoryLifeCycle implements LifeCycle { async endCommand() { await writeTaskRunsToHistory(this.taskRuns); const history = await getHistoryForHashes(this.taskRuns.map((t) => t.hash)); - const flakyTasks: string[] = []; + this.flakyTasks = []; // check if any hash has different exit codes => flaky for (let hash in history) { @@ -46,7 +47,7 @@ export class LegacyTaskHistoryLifeCycle implements LifeCycle { history[hash].length > 1 && history[hash].some((run) => run.code !== history[hash][0].code) ) { - flakyTasks.push( + this.flakyTasks.push( serializeTarget( history[hash][0].project, history[hash][0].target, @@ -59,14 +60,18 @@ export class LegacyTaskHistoryLifeCycle implements LifeCycle { if (isTuiEnabled()) { return; } - if (flakyTasks.length > 0) { + this.printFlakyTasksMessage(); + } + + printFlakyTasksMessage() { + if (this.flakyTasks.length > 0) { output.warn({ title: `Nx detected ${ - flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks' + this.flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks' }`, bodyLines: [ , - ...flakyTasks.map((t) => ` ${t}`), + ...this.flakyTasks.map((t) => ` ${t}`), '', `Flaky tasks can disrupt your CI pipeline. Automatically retry them with Nx Cloud. Learn more at https://nx.dev/ci/features/flaky-tasks`, ], diff --git a/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle.ts b/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle.ts index 8a6131b11a..5652bf108f 100644 --- a/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle.ts +++ b/packages/nx/src/tasks-runner/life-cycles/task-history-life-cycle.ts @@ -1,19 +1,50 @@ import { Task } from '../../config/task-graph'; -import type { TaskRun as NativeTaskRun } from '../../native'; +import { IS_WASM, type TaskRun as NativeTaskRun } from '../../native'; import { output } from '../../utils/output'; import { serializeTarget } from '../../utils/serialize-target'; import { getTaskHistory, TaskHistory } from '../../utils/task-history'; import { isTuiEnabled } from '../is-tui-enabled'; import { LifeCycle, TaskResult } from '../life-cycle'; +import { LegacyTaskHistoryLifeCycle } from './task-history-life-cycle-old'; +import { isNxCloudUsed } from '../../utils/nx-cloud-utils'; +import { readNxJson } from '../../config/nx-json'; interface TaskRun extends NativeTaskRun { target: Task['target']; } +let tasksHistoryLifeCycle: TaskHistoryLifeCycle | LegacyTaskHistoryLifeCycle; + +export function getTasksHistoryLifeCycle(): + | TaskHistoryLifeCycle + | LegacyTaskHistoryLifeCycle + | null { + if (!isNxCloudUsed(readNxJson())) { + if (!tasksHistoryLifeCycle) { + tasksHistoryLifeCycle = + process.env.NX_DISABLE_DB !== 'true' && !IS_WASM + ? new TaskHistoryLifeCycle() + : new LegacyTaskHistoryLifeCycle(); + } + return tasksHistoryLifeCycle; + } + return null; +} + export class TaskHistoryLifeCycle implements LifeCycle { private startTimings: Record = {}; private taskRuns = new Map(); private taskHistory: TaskHistory | null = getTaskHistory(); + private flakyTasks: string[]; + + constructor() { + if (tasksHistoryLifeCycle) { + throw new Error( + 'TaskHistoryLifeCycle is a singleton and should not be instantiated multiple times' + ); + } + tasksHistoryLifeCycle = this; + } startTasks(tasks: Task[]): void { for (let task of tasks) { @@ -43,21 +74,25 @@ export class TaskHistoryLifeCycle implements LifeCycle { return; } await this.taskHistory.recordTaskRuns(entries.map(([_, v]) => v)); - const flakyTasks = await this.taskHistory.getFlakyTasks( + this.flakyTasks = await this.taskHistory.getFlakyTasks( entries.map(([hash]) => hash) ); // Do not directly print output when using the TUI if (isTuiEnabled()) { return; } - if (flakyTasks.length > 0) { + this.printFlakyTasksMessage(); + } + + printFlakyTasksMessage() { + if (this.flakyTasks.length > 0) { output.warn({ title: `Nx detected ${ - flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks' + this.flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks' }`, bodyLines: [ , - ...flakyTasks.map((hash) => { + ...this.flakyTasks.map((hash) => { const taskRun = this.taskRuns.get(hash); return ` ${serializeTarget( taskRun.target.project, 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 689f8d3008..8c82dc96ee 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 @@ -8,6 +8,7 @@ import { formatFlags, formatTargetsAndProjects } from './formatting-utils'; import { prettyTime } from './pretty-time'; import { viewLogsFooterRows } from './view-logs-utils'; import figures = require('figures'); +import { getTasksHistoryLifeCycle } from './task-history-life-cycle'; const LEFT_PAD = ` `; const SPACER = ` `; @@ -112,6 +113,7 @@ export function getTuiTerminalSummaryLifeCycle({ } else { printRunManySummary(); } + getTasksHistoryLifeCycle()?.printFlakyTasksMessage(); }; const printRunOneSummary = () => { diff --git a/packages/nx/src/tasks-runner/run-command.ts b/packages/nx/src/tasks-runner/run-command.ts index 40bb19fb97..97b0a18902 100644 --- a/packages/nx/src/tasks-runner/run-command.ts +++ b/packages/nx/src/tasks-runner/run-command.ts @@ -53,8 +53,7 @@ import { createRunOneDynamicOutputRenderer } from './life-cycles/dynamic-run-one import { StaticRunManyTerminalOutputLifeCycle } from './life-cycles/static-run-many-terminal-output-life-cycle'; import { StaticRunOneTerminalOutputLifeCycle } from './life-cycles/static-run-one-terminal-output-life-cycle'; import { StoreRunInformationLifeCycle } from './life-cycles/store-run-information-life-cycle'; -import { TaskHistoryLifeCycle } from './life-cycles/task-history-life-cycle'; -import { LegacyTaskHistoryLifeCycle } from './life-cycles/task-history-life-cycle-old'; +import { getTasksHistoryLifeCycle } from './life-cycles/task-history-life-cycle'; import { TaskProfilingLifeCycle } from './life-cycles/task-profiling-life-cycle'; import { TaskResultsLifeCycle } from './life-cycles/task-results-life-cycle'; import { TaskTimingsLifeCycle } from './life-cycles/task-timings-life-cycle'; @@ -961,12 +960,9 @@ export function constructLifeCycles(lifeCycle: LifeCycle): LifeCycle[] { if (process.env.NX_PROFILE) { lifeCycles.push(new TaskProfilingLifeCycle(process.env.NX_PROFILE)); } - if (!isNxCloudUsed(readNxJson())) { - lifeCycles.push( - process.env.NX_DISABLE_DB !== 'true' && !IS_WASM - ? new TaskHistoryLifeCycle() - : new LegacyTaskHistoryLifeCycle() - ); + const historyLifeCycle = getTasksHistoryLifeCycle(); + if (historyLifeCycle) { + lifeCycles.push(historyLifeCycle); } return lifeCycles; }