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

View File

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

View File

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

View File

@ -1,4 +1,10 @@
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
@ -10,18 +16,26 @@ export class DelayedSpinner {
spinner: ora.Ora;
timeouts: NodeJS.Timeout[] = [];
initial: number = Date.now();
lastMessage: string;
/**
* Constructs a new {@link DelayedSpinner} instance.
*
* @param message The message to display in the spinner
* @param ms The number of milliseconds to wait before creating the spinner
* @param opts The options for 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(
setTimeout(() => {
this.spinner = ora(message);
}, ms).unref()
if (!SHOULD_SHOW_SPINNERS) {
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
*/
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;
}
@ -40,15 +59,18 @@ export class DelayedSpinner {
* Schedules an update to the message of the spinner. Useful for
* changing the message after a certain amount of time has passed.
*
* @param message The message to display in the spinner
* @param delay How long to wait before updating the message
* @param opts The options for the update
* @returns The {@link DelayedSpinner} instance
*/
scheduleMessageUpdate(message: string, delay: number) {
scheduleMessageUpdate(message: string, opts?: DelayedSpinnerOptions) {
opts = normalizeDelayedSpinnerOpts(opts);
this.timeouts.push(
setTimeout(() => {
this.spinner.text = message;
}, delay).unref()
setTimeout(
() => {
this.setMessage(message);
},
SHOULD_SHOW_SPINNERS ? opts.delay : opts.ciDelay
).unref()
);
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;
}