From dfa755957c4e3399d4da1db8d76d131498b297f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Wed, 4 Sep 2024 11:49:17 +0200 Subject: [PATCH] fix(core): improve sync error handling and messaging --- packages/nx/src/command-line/sync/sync.ts | 43 +++- packages/nx/src/daemon/client/client.ts | 9 +- ...le-flush-sync-generator-changes-to-disk.ts | 4 +- .../src/daemon/server/sync-generators.spec.ts | 4 +- .../nx/src/daemon/server/sync-generators.ts | 5 +- packages/nx/src/tasks-runner/run-command.ts | 198 +++++++++++------ packages/nx/src/utils/sync-generators.ts | 203 +++++++++++++----- 7 files changed, 318 insertions(+), 148 deletions(-) diff --git a/packages/nx/src/command-line/sync/sync.ts b/packages/nx/src/command-line/sync/sync.ts index 81f5bd8e18724..89b2179af22ea 100644 --- a/packages/nx/src/command-line/sync/sync.ts +++ b/packages/nx/src/command-line/sync/sync.ts @@ -7,6 +7,7 @@ import { collectAllRegisteredSyncGenerators, flushSyncGeneratorChanges, getFailedSyncGeneratorsFixMessageLines, + getFlushFailureMessageLines, getSyncGeneratorChanges, getSyncGeneratorSuccessResultsMessageLines, processSyncGeneratorResultErrors, @@ -65,8 +66,8 @@ export function syncHandler(options: SyncOptions): Promise { if (areAllResultsFailures) { // if all sync generators failed to run we can't say for sure if the workspace is out of sync // because they could have failed due to a bug, so we print a warning and exit with code 0 - output.warn({ - title: `The workspace might be out of sync because ${ + output.error({ + title: `The workspace is probably out of sync because ${ failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators' @@ -74,7 +75,7 @@ export function syncHandler(options: SyncOptions): Promise { bodyLines: failedSyncGeneratorsFixMessageLines, }); - return 0; + return 1; } const resultBodyLines = getSyncGeneratorSuccessResultsMessageLines(results); @@ -85,7 +86,7 @@ export function syncHandler(options: SyncOptions): Promise { }); if (anySyncGeneratorsFailed) { - output.warn({ + output.error({ title: failedGeneratorsCount === 1 ? 'A sync generator failed to run' @@ -106,7 +107,17 @@ export function syncHandler(options: SyncOptions): Promise { spinner.start(); try { - await flushSyncGeneratorChanges(results); + const flushResult = await flushSyncGeneratorChanges(results); + + if ('generatorFailures' in flushResult) { + spinner.fail(); + output.error({ + title: 'Failed to sync the workspace', + bodyLines: getFlushFailureMessageLines(flushResult, options.verbose), + }); + + return 1; + } } catch (e) { spinner.fail(); output.error({ @@ -115,26 +126,36 @@ export function syncHandler(options: SyncOptions): Promise { 'Syncing the workspace failed with the following error:', '', e.message, - ...(options.verbose ? [`\n${e.stack}`] : []), + ...(options.verbose && !!e.stack ? [`\n${e.stack}`] : []), + '', + 'Please make sure to run with `--verbose` and report the error at: https://github.com/nrwl/nx/issues/new/choose', ], }); return 1; } - spinner.succeed(`The workspace was synced successfully! - -Please make sure to commit the changes to your repository.`); + const successTitle = anySyncGeneratorsFailed + ? // the identified changes were synced successfully, but the workspace + // is still not up to date, which we'll mention next + 'The identified changes were synced successfully!' + : // the workspace is fully up to date + 'The workspace was synced successfully!'; + const successSubtitle = + 'Please make sure to commit the changes to your repository.'; + spinner.succeed(`${successTitle}\n\n${successSubtitle}`); if (anySyncGeneratorsFailed) { - output.warn({ - title: `The workspace might still be out of sync because ${ + output.error({ + title: `The workspace is probably still out of sync because ${ failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators' } failed to run`, bodyLines: failedSyncGeneratorsFixMessageLines, }); + + return 1; } return 0; diff --git a/packages/nx/src/daemon/client/client.ts b/packages/nx/src/daemon/client/client.ts index 4b45c229d3a2b..02116379fd944 100644 --- a/packages/nx/src/daemon/client/client.ts +++ b/packages/nx/src/daemon/client/client.ts @@ -54,7 +54,10 @@ import { GET_SYNC_GENERATOR_CHANGES, type HandleGetSyncGeneratorChangesMessage, } from '../message-types/get-sync-generator-changes'; -import type { SyncGeneratorRunResult } from '../../utils/sync-generators'; +import type { + FlushSyncGeneratorChangesResult, + SyncGeneratorRunResult, +} from '../../utils/sync-generators'; import { GET_REGISTERED_SYNC_GENERATORS, type HandleGetRegisteredSyncGeneratorsMessage, @@ -372,7 +375,9 @@ export class DaemonClient { return this.sendToDaemonViaQueue(message); } - flushSyncGeneratorChangesToDisk(generators: string[]): Promise { + flushSyncGeneratorChangesToDisk( + generators: string[] + ): Promise { const message: HandleFlushSyncGeneratorChangesToDiskMessage = { type: FLUSH_SYNC_GENERATOR_CHANGES_TO_DISK, generators, diff --git a/packages/nx/src/daemon/server/handle-flush-sync-generator-changes-to-disk.ts b/packages/nx/src/daemon/server/handle-flush-sync-generator-changes-to-disk.ts index f6367c390f51c..1a36ff4fd086d 100644 --- a/packages/nx/src/daemon/server/handle-flush-sync-generator-changes-to-disk.ts +++ b/packages/nx/src/daemon/server/handle-flush-sync-generator-changes-to-disk.ts @@ -4,10 +4,10 @@ import { flushSyncGeneratorChangesToDisk } from './sync-generators'; export async function handleFlushSyncGeneratorChangesToDisk( generators: string[] ): Promise { - await flushSyncGeneratorChangesToDisk(generators); + const result = await flushSyncGeneratorChangesToDisk(generators); return { - response: '{}', + response: JSON.stringify(result), description: 'handleFlushSyncGeneratorChangesToDisk', }; } diff --git a/packages/nx/src/daemon/server/sync-generators.spec.ts b/packages/nx/src/daemon/server/sync-generators.spec.ts index 639d46a8bb875..ba128a9a97ae4 100644 --- a/packages/nx/src/daemon/server/sync-generators.spec.ts +++ b/packages/nx/src/daemon/server/sync-generators.spec.ts @@ -1,9 +1,9 @@ -import type { SyncGeneratorChangesResult } from '../../utils/sync-generators'; +import type { SyncGeneratorRunResult } from '../../utils/sync-generators'; import { _getConflictingGeneratorGroups } from './sync-generators'; describe('_getConflictingGeneratorGroups', () => { it('should return grouped conflicting generators', () => { - const results: SyncGeneratorChangesResult[] = [ + const results: SyncGeneratorRunResult[] = [ { generatorName: 'a', changes: [ diff --git a/packages/nx/src/daemon/server/sync-generators.ts b/packages/nx/src/daemon/server/sync-generators.ts index 5755494a74df8..287f21688b3f4 100644 --- a/packages/nx/src/daemon/server/sync-generators.ts +++ b/packages/nx/src/daemon/server/sync-generators.ts @@ -9,6 +9,7 @@ import { collectRegisteredGlobalSyncGenerators, flushSyncGeneratorChanges, runSyncGenerator, + type FlushSyncGeneratorChangesResult, type SyncGeneratorRunResult, type SyncGeneratorRunSuccessResult, } from '../../utils/sync-generators'; @@ -71,7 +72,7 @@ export async function getCachedSyncGeneratorChanges( export async function flushSyncGeneratorChangesToDisk( generators: string[] -): Promise { +): Promise { log('flush sync generators changes', generators); const results = await getCachedSyncGeneratorChanges(generators); @@ -80,7 +81,7 @@ export async function flushSyncGeneratorChangesToDisk( syncGeneratorsCacheResultPromises.delete(generator); } - await flushSyncGeneratorChanges(results); + return await flushSyncGeneratorChanges(results); } export function collectAndScheduleSyncGenerators( diff --git a/packages/nx/src/tasks-runner/run-command.ts b/packages/nx/src/tasks-runner/run-command.ts index 6492170a408c9..550e7e2056d92 100644 --- a/packages/nx/src/tasks-runner/run-command.ts +++ b/packages/nx/src/tasks-runner/run-command.ts @@ -25,6 +25,7 @@ import { collectEnabledTaskSyncGeneratorsFromTaskGraph, flushSyncGeneratorChanges, getFailedSyncGeneratorsFixMessageLines, + getFlushFailureMessageLines, getSyncGeneratorChanges, getSyncGeneratorSuccessResultsMessageLines, processSyncGeneratorResultErrors, @@ -265,26 +266,8 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( } = processSyncGeneratorResultErrors(results); const failedSyncGeneratorsFixMessageLines = getFailedSyncGeneratorsFixMessageLines(results, nxArgs.verbose); - - if (areAllResultsFailures) { - output.warn({ - title: `The workspace might be out of sync because ${ - failedGeneratorsCount === 1 - ? 'a sync generator' - : 'some sync generators' - } failed to run`, - bodyLines: failedSyncGeneratorsFixMessageLines, - }); - - // if all sync generators failed to run there's nothing to sync, we just let the tasks run - return { projectGraph, taskGraph }; - } - const outOfSyncTitle = 'The workspace is out of sync'; - const resultBodyLines = [ - ...getSyncGeneratorSuccessResultsMessageLines(results), - '', - ]; + const resultBodyLines = getSyncGeneratorSuccessResultsMessageLines(results); const fixMessage = 'You can manually run `nx sync` to update your workspace with the identified changes or you can set `sync.applyChanges` to `true` in your `nx.json` to apply the changes automatically when running tasks in interactive environments.'; const willErrorOnCiMessage = @@ -293,19 +276,49 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( if (isCI() || !process.stdout.isTTY) { // If the user is running in CI or is running in a non-TTY environment we // throw an error to stop the execution of the tasks. - let errorMessage = `${outOfSyncTitle}\n${resultBodyLines.join( - '\n' - )}\n${fixMessage}`; + if (areAllResultsFailures) { + output.error({ + title: `The workspace is probably out of sync because ${ + failedGeneratorsCount === 1 + ? 'a sync generator' + : 'some sync generators' + } failed to run`, + bodyLines: failedSyncGeneratorsFixMessageLines, + }); + } else { + output.error({ + title: outOfSyncTitle, + bodyLines: [...resultBodyLines, '', fixMessage], + }); - if (anySyncGeneratorsFailed) { - errorMessage += `\n\nAdditionally, ${ + if (anySyncGeneratorsFailed) { + output.error({ + title: + failedGeneratorsCount === 1 + ? 'A sync generator failed to run' + : 'Some sync generators failed to run', + bodyLines: failedSyncGeneratorsFixMessageLines, + }); + } + } + + process.exit(1); + } + + if (areAllResultsFailures) { + output.warn({ + title: `The workspace is probably out of sync because ${ failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators' - } failed to run:\n\n${failedSyncGeneratorsFixMessageLines.join('\n')}`; - } + } failed to run`, + bodyLines: failedSyncGeneratorsFixMessageLines, + }); - throw new Error(errorMessage); + await confirmRunningTasksWithSyncFailures(); + + // if all sync generators failed to run there's nothing to sync, we just let the tasks run + return { projectGraph, taskGraph }; } if (nxJson.sync?.applyChanges === false) { @@ -317,7 +330,8 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( title: outOfSyncTitle, bodyLines: [ ...resultBodyLines, - 'Your workspace is set to not apply changes automatically (`sync.applyChanges` is set to `false` in your `nx.json`).', + '', + 'Your workspace is set to not apply the identified changes automatically (`sync.applyChanges` is set to `false` in your `nx.json`).', willErrorOnCiMessage, fixMessage, ], @@ -331,6 +345,8 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( : 'Some sync generators failed to run', bodyLines: failedSyncGeneratorsFixMessageLines, }); + + await confirmRunningTasksWithSyncFailures(); } return { projectGraph, taskGraph }; @@ -340,8 +356,9 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( title: outOfSyncTitle, bodyLines: [ ...resultBodyLines, + '', nxJson.sync?.applyChanges === true - ? 'Proceeding to sync the changes automatically (`sync.applyChanges` is set to `true` in your `nx.json`).' + ? 'Proceeding to sync the identified changes automatically (`sync.applyChanges` is set to `true` in your `nx.json`).' : willErrorOnCiMessage, ], }); @@ -354,22 +371,24 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( const spinner = ora('Syncing the workspace...'); spinner.start(); - try { - // Flush sync generator changes to disk - await flushSyncGeneratorChanges(results); - } catch (e) { + // Flush sync generator changes to disk + const flushResult = await flushSyncGeneratorChanges(results); + + if ('generatorFailures' in flushResult) { spinner.fail(); output.error({ title: 'Failed to sync the workspace', bodyLines: [ - 'Syncing the workspace failed with the following error:', - '', - e.message, - ...(nxArgs.verbose ? [`\n${e.stack}`] : []), + ...getFlushFailureMessageLines(flushResult, nxArgs.verbose), + ...(flushResult.generalFailure + ? [ + 'If needed, you can run the tasks with the `--skip-sync` flag to disable syncing.', + ] + : []), ], }); - return { projectGraph, taskGraph }; + await confirmRunningTasksWithSyncFailures(); } // Re-create project graph and task graph @@ -383,48 +402,52 @@ async function ensureWorkspaceIsInSyncAndGetGraphs( extraOptions ); - if (nxJson.sync?.applyChanges === true) { - spinner.succeed(`The workspace was synced successfully! - -Please make sure to commit the changes to your repository or this will error on CI.`); - } else { - // The user was prompted and we already logged a message about erroring on CI - // so here we just tell them to commit the changes. - spinner.succeed(`The workspace was synced successfully! - -Please make sure to commit the changes to your repository.`); - } + const successTitle = anySyncGeneratorsFailed + ? // the identified changes were synced successfully, but the workspace + // is still not up to date, which we'll mention next + 'The identified changes were synced successfully!' + : // the workspace is fully up to date + 'The workspace was synced successfully!'; + const successSubtitle = + nxJson.sync?.applyChanges === true + ? 'Please make sure to commit the changes to your repository or this will error on CI.' + : // The user was prompted and we already logged a message about erroring on CI + // so here we just tell them to commit the changes. + 'Please make sure to commit the changes to your repository.'; + spinner.succeed(`${successTitle}\n\n${successSubtitle}`); if (anySyncGeneratorsFailed) { output.warn({ - title: `The workspace might still be out of sync because ${ + title: `The workspace is probably still out of sync because ${ failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators' } failed to run`, bodyLines: failedSyncGeneratorsFixMessageLines, }); + + await confirmRunningTasksWithSyncFailures(); } } else { - output.warn({ - title: 'Syncing the workspace was skipped', - bodyLines: [ - 'This could lead to unexpected results or errors when running tasks.', - fixMessage, - ...(anySyncGeneratorsFailed - ? [ - '', - `Additionally, ${ - failedGeneratorsCount === 1 - ? 'a sync generator' - : 'some sync generators' - } failed to run:`, - '', - ...failedSyncGeneratorsFixMessageLines, - ] - : []), - ], - }); + if (anySyncGeneratorsFailed) { + output.warn({ + title: + failedGeneratorsCount === 1 + ? 'A sync generator failed to report the sync status' + : 'Some sync generators failed to report the sync status', + bodyLines: failedSyncGeneratorsFixMessageLines, + }); + + await confirmRunningTasksWithSyncFailures(); + } else { + output.warn({ + title: 'Syncing the workspace was skipped', + bodyLines: [ + 'This could lead to unexpected results or errors when running tasks.', + fixMessage, + ], + }); + } } return { projectGraph, taskGraph }; @@ -436,7 +459,7 @@ async function promptForApplyingSyncGeneratorChanges(): Promise { name: 'applyChanges', type: 'select', message: - 'Would you like to sync the changes to get your worskpace up to date?', + 'Would you like to sync the identified changes to get your worskpace up to date?', choices: [ { name: 'yes', @@ -461,6 +484,41 @@ async function promptForApplyingSyncGeneratorChanges(): Promise { } } +async function confirmRunningTasksWithSyncFailures(): Promise { + try { + const promptConfig = { + name: 'runTasks', + type: 'select', + message: + 'Would you like to ignore the sync failures and continue running the tasks?', + choices: [ + { + name: 'yes', + message: 'Yes, ignore the failures and run the tasks', + }, + { + name: 'no', + message: `No, don't run the tasks`, + }, + ], + footer: () => + chalk.dim( + `\nWhen running on CI and there are sync failures, the tasks won't run. Addressing the errors above is highly recommended to prevent failures in CI.` + ), + }; + + const runTasks = await prompt<{ runTasks: 'yes' | 'no' }>([ + promptConfig, + ]).then(({ runTasks }) => runTasks === 'yes'); + + if (!runTasks) { + process.exit(1); + } + } catch { + process.exit(1); + } +} + function setEnvVarsBasedOnArgs(nxArgs: NxArgs, loadDotEnvFiles: boolean) { if ( nxArgs.outputStyle == 'stream' || diff --git a/packages/nx/src/utils/sync-generators.ts b/packages/nx/src/utils/sync-generators.ts index f1a95ab6ab334..39efc393b5c52 100644 --- a/packages/nx/src/utils/sync-generators.ts +++ b/packages/nx/src/utils/sync-generators.ts @@ -38,15 +38,34 @@ export type SyncGeneratorRunSuccessResult = { outOfSyncMessage?: string; }; +// Error is not serializable, so we use a simple object instead +type SerializableSimpleError = { + message: string; + stack: string | undefined; +}; + export type SyncGeneratorRunErrorResult = { generatorName: string; - error: Error; + error: SerializableSimpleError; }; export type SyncGeneratorRunResult = | SyncGeneratorRunSuccessResult | SyncGeneratorRunErrorResult; +type FlushSyncGeneratorChangesSuccess = { success: true }; +type FlushSyncGeneratorFailure = { + generator: string; + error: SerializableSimpleError; +}; +type FlushSyncGeneratorChangesFailure = { + generatorFailures: FlushSyncGeneratorFailure[]; + generalFailure?: SerializableSimpleError; +}; +export type FlushSyncGeneratorChangesResult = + | FlushSyncGeneratorChangesSuccess + | FlushSyncGeneratorChangesFailure; + export async function getSyncGeneratorChanges( generators: string[] ): Promise { @@ -71,14 +90,14 @@ export async function getSyncGeneratorChanges( export async function flushSyncGeneratorChanges( results: SyncGeneratorRunResult[] -): Promise { +): Promise { if (isOnDaemon() || !daemonClient.enabled()) { - await flushSyncGeneratorChangesToDisk(results); - } else { - await daemonClient.flushSyncGeneratorChangesToDisk( - results.map((r) => r.generatorName) - ); + return await flushSyncGeneratorChangesToDisk(results); } + + return await daemonClient.flushSyncGeneratorChangesToDisk( + results.map((r) => r.generatorName) + ); } export async function collectAllRegisteredSyncGenerators( @@ -135,7 +154,7 @@ export async function runSyncGenerator( } catch (e) { return { generatorName: generatorSpecifier, - error: e, + error: { message: e.message, stack: e.stack }, }; } } @@ -253,36 +272,70 @@ export function getFailedSyncGeneratorsFixMessageLines( verbose: boolean ): string[] { const messageLines: string[] = []; - let errorCount = 0; + const generators: string[] = []; for (const result of results) { if ('error' in result) { messageLines.push( `The ${chalk.bold( result.generatorName - )} sync generator failed to run with the following error: + )} sync generator reported the following error: ${chalk.bold(result.error.message)}${ verbose && result.error.stack ? '\n' + result.error.stack : '' }` ); - errorCount++; + generators.push(result.generatorName); } } - if (errorCount === 1) { + messageLines.push( + ...getFailedSyncGeneratorsMessageLines(generators, verbose) + ); + + return messageLines; +} + +export function getFlushFailureMessageLines( + result: FlushSyncGeneratorChangesFailure, + verbose: boolean +): string[] { + const messageLines: string[] = []; + const generators: string[] = []; + for (const failure of result.generatorFailures) { messageLines.push( - '', - 'Please take the following actions to fix the failed sync generator:', - ' - Check if the error is caused by an issue in your source code (e.g. wrong configuration, missing dependencies/files, etc.).', - ' - Check if the error has already been reported in the repository owning the failing sync generator. If not, please file a new issue.', - ' - While the issue is addressed, you could disable the failing sync generator by adding it to the `sync.disabledTaskSyncGenerators` array in your `nx.json`.' + `The ${chalk.bold( + failure.generator + )} sync generator failed to apply its changes with the following error: +${chalk.bold(failure.error.message)}${ + verbose && failure.error.stack ? '\n' + failure.error.stack : '' + }` ); - } else if (errorCount > 1) { + generators.push(failure.generator); + } + + messageLines.push( + ...getFailedSyncGeneratorsMessageLines(generators, verbose) + ); + + if (result.generalFailure) { + if (messageLines.length > 0) { + messageLines.push(''); + messageLines.push('Additionally, an unexpected error occurred:'); + } else { + messageLines.push('An unexpected error occurred:'); + } + messageLines.push( - '', - 'Please take the following actions to fix the failed sync generators:', - ' - Check if the errors are caused by an issue in your source code (e.g. wrong configuration, missing dependencies/files, etc.).', - ' - Check if the errors have already been reported in the repositories owning the failing sync generators. If not, please file new issues for them.', - ' - While the issues are addressed, you could disable the failing sync generators by adding them to the `sync.disabledTaskSyncGenerators` array in your `nx.json`.' + ...[ + '', + result.generalFailure.message, + ...(verbose && !!result.generalFailure.stack + ? [`\n${result.generalFailure.stack}`] + : []), + '', + verbose + ? 'Please report the error at: https://github.com/nrwl/nx/issues/new/choose' + : 'Please run with `--verbose` and report the error at: https://github.com/nrwl/nx/issues/new/choose', + ] ); } @@ -326,53 +379,20 @@ async function runSyncGenerators( async function flushSyncGeneratorChangesToDisk( results: SyncGeneratorRunResult[] -): Promise { +): Promise { performance.mark('flush-sync-generator-changes-to-disk:start'); - const { changes, createdFiles, updatedFiles, deletedFiles, callbacks } = - processSyncGeneratorResultChanges(results); - // Write changes to disk - flushChanges(workspaceRoot, changes); - - // Run the callbacks - if (callbacks.length) { - for (const callback of callbacks) { - await callback(); - } - } - // Update the context files - await updateContextWithChangedFiles( - workspaceRoot, - createdFiles, - updatedFiles, - deletedFiles - ); - performance.mark('flush-sync-generator-changes-to-disk:end'); - performance.measure( - 'flush sync generator changes to disk', - 'flush-sync-generator-changes-to-disk:start', - 'flush-sync-generator-changes-to-disk:end' - ); -} - -function processSyncGeneratorResultChanges(results: SyncGeneratorRunResult[]) { - const changes: FileChange[] = []; const createdFiles: string[] = []; const updatedFiles: string[] = []; const deletedFiles: string[] = []; - const callbacks: GeneratorCallback[] = []; + const generatorFailures: FlushSyncGeneratorFailure[] = []; for (const result of results) { if ('error' in result) { continue; } - if (result.callback) { - callbacks.push(result.callback); - } - for (const change of result.changes) { - changes.push(change); if (change.type === 'CREATE') { createdFiles.push(change.path); } else if (change.type === 'UPDATE') { @@ -381,7 +401,72 @@ function processSyncGeneratorResultChanges(results: SyncGeneratorRunResult[]) { deletedFiles.push(change.path); } } + + try { + // Write changes to disk + flushChanges(workspaceRoot, result.changes); + // Run the callback + if (result.callback) { + await result.callback(); + } + } catch (e) { + generatorFailures.push({ + generator: result.generatorName, + error: { message: e.message, stack: e.stack }, + }); + } + } + + try { + // Update the context files + await updateContextWithChangedFiles( + workspaceRoot, + createdFiles, + updatedFiles, + deletedFiles + ); + performance.mark('flush-sync-generator-changes-to-disk:end'); + performance.measure( + 'flush sync generator changes to disk', + 'flush-sync-generator-changes-to-disk:start', + 'flush-sync-generator-changes-to-disk:end' + ); + } catch (e) { + return { + generatorFailures, + generalFailure: { message: e.message, stack: e.stack }, + }; } - return { changes, createdFiles, updatedFiles, deletedFiles, callbacks }; + return generatorFailures.length > 0 + ? { generatorFailures } + : { success: true }; +} + +function getFailedSyncGeneratorsMessageLines( + generators: string[], + verbose: boolean +): string[] { + const messageLines: string[] = []; + + if (generators.length === 1) { + messageLines.push( + '', + verbose + ? 'Please check the error above and address the issue.' + : 'Please check the error above and address the issue. You can provide the `--verbose` flag to get more details.', + `If needed, you can disable the failing sync generator by setting \`sync.disabledTaskSyncGenerators: ["${generators[0]}"]\` in your \`nx.json\`.` + ); + } else if (generators.length > 1) { + const generatorsString = generators.map((g) => `"${g}"`).join(', '); + messageLines.push( + '', + verbose + ? 'Please check the errors above and address the issues.' + : 'Please check the errors above and address the issues. You can provide the `--verbose` flag to get more details.', + `If needed, you can disable the failing sync generators by setting \`sync.disabledTaskSyncGenerators: [${generatorsString}]\` in your \`nx.json\`.` + ); + } + + return messageLines; }