Skip to content

Commit

Permalink
feat(core): show progress on ci if graph construction takes longer th…
Browse files Browse the repository at this point in the history
…an 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
  • Loading branch information
AgentEnder authored Dec 19, 2024
1 parent ba1641d commit 3c3d2e5
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 68 deletions.
22 changes: 8 additions & 14 deletions packages/nx/src/daemon/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
58 changes: 33 additions & 25 deletions packages/nx/src/project-graph/build-project-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) {
Expand Down
32 changes: 16 additions & 16 deletions packages/nx/src/project-graph/utils/project-configuration-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down Expand Up @@ -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<
Expand Down
57 changes: 44 additions & 13 deletions packages/nx/src/utils/delayed-spinner.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
);
}

Expand All @@ -32,23 +46,31 @@ 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;
}

/**
* 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;
}
Expand All @@ -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;
}

0 comments on commit 3c3d2e5

Please sign in to comment.