Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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;
}
Loading