feat(core): show progress on ci if graph construction takes longer than expected (#29392)

Progress spinners currently only show up when the terminal is a tty.
This updates it to show static text on CI, but at a longer duration
This commit is contained in:
Craigory Coppola 2024-12-19 11:34:51 -05:00 committed by GitHub
parent ba1641d7fd
commit 3c3d2e5f82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 101 additions and 68 deletions

View File

@ -77,10 +77,7 @@ import {
FLUSH_SYNC_GENERATOR_CHANGES_TO_DISK, FLUSH_SYNC_GENERATOR_CHANGES_TO_DISK,
type HandleFlushSyncGeneratorChangesToDiskMessage, type HandleFlushSyncGeneratorChangesToDiskMessage,
} from '../message-types/flush-sync-generator-changes-to-disk'; } from '../message-types/flush-sync-generator-changes-to-disk';
import { import { DelayedSpinner } from '../../utils/delayed-spinner';
DelayedSpinner,
SHOULD_SHOW_SPINNERS,
} from '../../utils/delayed-spinner';
const DAEMON_ENV_SETTINGS = { const DAEMON_ENV_SETTINGS = {
NX_PROJECT_GLOB_CACHE: 'false', NX_PROJECT_GLOB_CACHE: 'false',
@ -199,16 +196,13 @@ export class DaemonClient {
sourceMaps: ConfigurationSourceMaps; sourceMaps: ConfigurationSourceMaps;
}> { }> {
let spinner: DelayedSpinner; let spinner: DelayedSpinner;
if (SHOULD_SHOW_SPINNERS) { // If the graph takes a while to load, we want to show a spinner.
// If the graph takes a while to load, we want to show a spinner. spinner = new DelayedSpinner(
spinner = new DelayedSpinner( 'Calculating the project graph on the Nx Daemon'
'Calculating the project graph on the Nx Daemon', ).scheduleMessageUpdate(
500 'Calculating the project graph on the Nx Daemon is taking longer than expected. Re-run with NX_DAEMON=false to see more details.',
).scheduleMessageUpdate( { ciDelay: 60_000, delay: 30_000 }
'Calculating the project graph on the Nx Daemon is taking longer than expected. Re-run with NX_DAEMON=false to see more details.', );
30_000
);
}
try { try {
const response = await this.sendToDaemonViaQueue({ const response = await this.sendToDaemonViaQueue({
type: 'REQUEST_PROJECT_GRAPH', type: 'REQUEST_PROJECT_GRAPH',

View File

@ -44,7 +44,7 @@ import {
ConfigurationSourceMaps, ConfigurationSourceMaps,
mergeMetadata, mergeMetadata,
} from './utils/project-configuration-utils'; } from './utils/project-configuration-utils';
import { DelayedSpinner, SHOULD_SHOW_SPINNERS } from '../utils/delayed-spinner'; import { DelayedSpinner } from '../utils/delayed-spinner';
let storedFileMap: FileMap | null = null; let storedFileMap: FileMap | null = null;
let storedAllWorkspaceFiles: FileData[] | null = null; let storedAllWorkspaceFiles: FileData[] | null = null;
@ -323,24 +323,28 @@ async function updateProjectGraphWithPlugins(
return; return;
} }
if (inProgressPlugins.size === 1) { if (inProgressPlugins.size === 1) {
return `Creating project graph dependencies with ${ spinner.setMessage(
inProgressPlugins.keys()[0] `Creating project graph dependencies with ${
}`; inProgressPlugins.keys()[0]
}`
);
} else if (process.env.NX_VERBOSE_LOGGING === 'true') { } else if (process.env.NX_VERBOSE_LOGGING === 'true') {
return [ spinner.setMessage(
`Creating project graph dependencies with ${inProgressPlugins.size} plugins`, [
...Array.from(inProgressPlugins).map((p) => ` - ${p}`), `Creating project graph dependencies with ${inProgressPlugins.size} plugins`,
].join('\n'); ...Array.from(inProgressPlugins).map((p) => ` - ${p}`),
].join('\n')
);
} else { } else {
return `Creating project graph dependencies with ${inProgressPlugins.size} plugins`; spinner.setMessage(
`Creating project graph dependencies with ${inProgressPlugins.size} plugins`
);
} }
} }
if (SHOULD_SHOW_SPINNERS) { spinner = new DelayedSpinner(
spinner = new DelayedSpinner( `Creating project graph dependencies with ${plugins.length} plugins`
`Creating project graph dependencies with ${plugins.length} plugins` );
);
}
await Promise.all( await Promise.all(
createDependencyPlugins.map(async (plugin) => { createDependencyPlugins.map(async (plugin) => {
@ -439,22 +443,26 @@ export async function applyProjectMetadata(
return; return;
} }
if (inProgressPlugins.size === 1) { if (inProgressPlugins.size === 1) {
return `Creating project metadata with ${inProgressPlugins.keys()[0]}`; spinner.setMessage(
`Creating project metadata with ${inProgressPlugins.keys()[0]}`
);
} else if (process.env.NX_VERBOSE_LOGGING === 'true') { } else if (process.env.NX_VERBOSE_LOGGING === 'true') {
return [ spinner.setMessage(
`Creating project metadata with ${inProgressPlugins.size} plugins`, [
...Array.from(inProgressPlugins).map((p) => ` - ${p}`), `Creating project metadata with ${inProgressPlugins.size} plugins`,
].join('\n'); ...Array.from(inProgressPlugins).map((p) => ` - ${p}`),
].join('\n')
);
} else { } else {
return `Creating project metadata with ${inProgressPlugins.size} plugins`; spinner.setMessage(
`Creating project metadata with ${inProgressPlugins.size} plugins`
);
} }
} }
if (SHOULD_SHOW_SPINNERS) { spinner = new DelayedSpinner(
spinner = new DelayedSpinner( `Creating project metadata with ${plugins.length} plugins`
`Creating project metadata with ${plugins.length} plugins` );
);
}
const promises = plugins.map(async (plugin) => { const promises = plugins.map(async (plugin) => {
if (plugin.createMetadata) { if (plugin.createMetadata) {

View File

@ -31,11 +31,7 @@ import {
} from '../error-types'; } from '../error-types';
import { CreateNodesResult } from '../plugins/public-api'; import { CreateNodesResult } from '../plugins/public-api';
import { isGlobPattern } from '../../utils/globs'; import { isGlobPattern } from '../../utils/globs';
import { isOnDaemon } from '../../daemon/is-on-daemon'; import { DelayedSpinner } from '../../utils/delayed-spinner';
import {
DelayedSpinner,
SHOULD_SHOW_SPINNERS,
} from '../../utils/delayed-spinner';
export type SourceInformation = [file: string | null, plugin: string]; export type SourceInformation = [file: string | null, plugin: string];
export type ConfigurationSourceMaps = Record< export type ConfigurationSourceMaps = Record<
@ -339,22 +335,26 @@ export async function createProjectConfigurations(
} }
if (inProgressPlugins.size === 1) { if (inProgressPlugins.size === 1) {
return `Creating project graph nodes with ${inProgressPlugins.keys()[0]}`; spinner.setMessage(
`Creating project graph nodes with ${inProgressPlugins.keys()[0]}`
);
} else if (process.env.NX_VERBOSE_LOGGING === 'true') { } else if (process.env.NX_VERBOSE_LOGGING === 'true') {
return [ spinner.setMessage(
`Creating project graph nodes with ${inProgressPlugins.size} plugins`, [
...Array.from(inProgressPlugins).map((p) => ` - ${p}`), `Creating project graph nodes with ${inProgressPlugins.size} plugins`,
].join('\n'); ...Array.from(inProgressPlugins).map((p) => ` - ${p}`),
].join('\n')
);
} else { } else {
return `Creating project graph nodes with ${inProgressPlugins.size} plugins`; spinner.setMessage(
`Creating project graph nodes with ${inProgressPlugins.size} plugins`
);
} }
} }
if (SHOULD_SHOW_SPINNERS) { spinner = new DelayedSpinner(
spinner = new DelayedSpinner( `Creating project graph nodes with ${plugins.length} plugins`
`Creating project graph nodes with ${plugins.length} plugins` );
);
}
const results: Array<ReturnType<LoadedNxPlugin['createNodes'][1]>> = []; const results: Array<ReturnType<LoadedNxPlugin['createNodes'][1]>> = [];
const errors: Array< const errors: Array<

View File

@ -1,4 +1,10 @@
import * as ora from 'ora'; import * as ora from 'ora';
import { isCI } from './is-ci';
export type DelayedSpinnerOptions = {
delay?: number;
ciDelay?: number;
};
/** /**
* A class that allows to delay the creation of a spinner, as well * A class that allows to delay the creation of a spinner, as well
@ -10,18 +16,26 @@ export class DelayedSpinner {
spinner: ora.Ora; spinner: ora.Ora;
timeouts: NodeJS.Timeout[] = []; timeouts: NodeJS.Timeout[] = [];
initial: number = Date.now(); initial: number = Date.now();
lastMessage: string;
/** /**
* Constructs a new {@link DelayedSpinner} instance. * Constructs a new {@link DelayedSpinner} instance.
* *
* @param message The message to display in the spinner * @param opts The options for the spinner
* @param ms The number of milliseconds to wait before creating the spinner
*/ */
constructor(message: string, ms: number = 500) { constructor(message: string, opts?: DelayedSpinnerOptions) {
opts = normalizeDelayedSpinnerOpts(opts);
const delay = SHOULD_SHOW_SPINNERS ? opts.delay : opts.ciDelay;
this.timeouts.push( this.timeouts.push(
setTimeout(() => { setTimeout(() => {
this.spinner = ora(message); if (!SHOULD_SHOW_SPINNERS) {
}, ms).unref() console.warn(message);
} else {
this.spinner = ora(message);
}
this.lastMessage = message;
}, delay).unref()
); );
} }
@ -32,7 +46,12 @@ export class DelayedSpinner {
* @returns The {@link DelayedSpinner} instance * @returns The {@link DelayedSpinner} instance
*/ */
setMessage(message: string) { setMessage(message: string) {
this.spinner.text = message; if (this.spinner && SHOULD_SHOW_SPINNERS) {
this.spinner.text = message;
} else if (this.lastMessage && this.lastMessage !== message) {
console.warn(message);
this.lastMessage = message;
}
return this; return this;
} }
@ -40,15 +59,18 @@ export class DelayedSpinner {
* Schedules an update to the message of the spinner. Useful for * Schedules an update to the message of the spinner. Useful for
* changing the message after a certain amount of time has passed. * changing the message after a certain amount of time has passed.
* *
* @param message The message to display in the spinner * @param opts The options for the update
* @param delay How long to wait before updating the message
* @returns The {@link DelayedSpinner} instance * @returns The {@link DelayedSpinner} instance
*/ */
scheduleMessageUpdate(message: string, delay: number) { scheduleMessageUpdate(message: string, opts?: DelayedSpinnerOptions) {
opts = normalizeDelayedSpinnerOpts(opts);
this.timeouts.push( this.timeouts.push(
setTimeout(() => { setTimeout(
this.spinner.text = message; () => {
}, delay).unref() this.setMessage(message);
},
SHOULD_SHOW_SPINNERS ? opts.delay : opts.ciDelay
).unref()
); );
return this; return this;
} }
@ -62,4 +84,13 @@ export class DelayedSpinner {
} }
} }
export const SHOULD_SHOW_SPINNERS = process.stdout.isTTY; const SHOULD_SHOW_SPINNERS = process.stdout.isTTY && !isCI();
function normalizeDelayedSpinnerOpts(
opts: DelayedSpinnerOptions | null | undefined
) {
opts ??= {};
opts.delay ??= 500;
opts.ciDelay ??= 10_000;
return opts;
}