Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "Add \"--show-existing-failure-logs\" parameter to phased commands that skips execution and replays existing failure logs from previous runs. This is useful when performing sweeping changes (e.g., TypeScript upgrades) to quickly review which projects failed without re-running expensive builds.",
"type": "minor",
"packageName": "@microsoft/rush"
}
],
"packageName": "@microsoft/rush",
"email": "[email protected]"
}
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,14 @@ Object {
"required": false,
"shortName": undefined,
},
Object {
"description": "Skips execution of operations and instead displays any existing failure logs for the selected projects. This is useful for reviewing failures from a previous run without re-executing the operations. Operations without existing failure logs will be silenced.",
"environmentVariable": undefined,
"kind": "Flag",
"longName": "--show-existing-failure-logs",
"required": false,
"shortName": undefined,
},
Object {
"description": "Selects a single instead of the default locale (en-us) for non-ship builds or all locales for ship builds.",
"environmentVariable": undefined,
Expand Down Expand Up @@ -1480,6 +1488,14 @@ Object {
"required": false,
"shortName": undefined,
},
Object {
"description": "Skips execution of operations and instead displays any existing failure logs for the selected projects. This is useful for reviewing failures from a previous run without re-executing the operations. Operations without existing failure logs will be silenced.",
"environmentVariable": undefined,
"kind": "Flag",
"longName": "--show-existing-failure-logs",
"required": false,
"shortName": undefined,
},
Object {
"description": "Perform a production build, including minification and localization steps",
"environmentVariable": undefined,
Expand Down Expand Up @@ -1629,6 +1645,14 @@ Object {
"required": false,
"shortName": undefined,
},
Object {
"description": "Skips execution of operations and instead displays any existing failure logs for the selected projects. This is useful for reviewing failures from a previous run without re-executing the operations. Operations without existing failure logs will be silenced.",
"environmentVariable": undefined,
"kind": "Flag",
"longName": "--show-existing-failure-logs",
"required": false,
"shortName": undefined,
},
Object {
"description": "Perform a production build, including minification and localization steps",
"environmentVariable": undefined,
Expand Down
82 changes: 55 additions & 27 deletions libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
private readonly _nodeDiagnosticDirParameter: CommandLineStringParameter;
private readonly _debugBuildCacheIdsParameter: CommandLineFlagParameter;
private readonly _includePhaseDeps: CommandLineFlagParameter | undefined;
private readonly _showExistingFailureLogsParameter: CommandLineFlagParameter;

public constructor(options: IPhasedScriptActionOptions) {
super(options);
Expand Down Expand Up @@ -326,6 +327,14 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
'Logs information about the components of the build cache ids for individual operations. This is useful for debugging the incremental build logic.'
});

this._showExistingFailureLogsParameter = this.defineFlagParameter({
parameterLongName: '--show-existing-failure-logs',
description:
'Skips execution of operations and instead displays any existing failure logs for the selected projects. ' +
'This is useful for reviewing failures from a previous run without re-executing the operations. ' +
'Operations without existing failure logs will be silenced.'
});

this.defineScriptParameters();

// Associate parameters with their respective phases
Expand Down Expand Up @@ -485,43 +494,62 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
}

const isWatch: boolean = this._watchParameter?.value || this._alwaysWatch;
const showExistingFailureLogs: boolean = this._showExistingFailureLogsParameter.value;

// If showing existing failure logs, we don't want to enable watch mode
if (showExistingFailureLogs && isWatch) {
throw new Error('The --show-existing-failure-logs parameter cannot be used with --watch.');
}

await measureAsyncFn(`${PERF_PREFIX}:applySituationalPlugins`, async () => {
if (isWatch && this._noIPCParameter?.value === false) {
if (showExistingFailureLogs) {
// Apply the plugin that replays existing failure logs without executing operations
terminal.writeVerboseLine(`Mode: showing existing failure logs`);
const { ShowExistingFailureLogsPlugin } = await import(
/* webpackChunkName: 'ShowExistingFailureLogsPlugin' */
'../../logic/operations/ShowExistingFailureLogsPlugin'
);
new ShowExistingFailureLogsPlugin({
terminal
}).apply(this.hooks);
} else if (isWatch && this._noIPCParameter?.value === false) {
new (
await import(
/* webpackChunkName: 'IPCOperationRunnerPlugin' */ '../../logic/operations/IPCOperationRunnerPlugin'
)
).IPCOperationRunnerPlugin().apply(this.hooks);
}

if (buildCacheConfiguration?.buildCacheEnabled) {
terminal.writeVerboseLine(`Incremental strategy: cache restoration`);
new CacheableOperationPlugin({
allowWarningsInSuccessfulBuild:
!!this.rushConfiguration.experimentsConfiguration.configuration
.buildCacheWithAllowWarningsInSuccessfulBuild,
buildCacheConfiguration,
cobuildConfiguration,
terminal
}).apply(this.hooks);

if (this._debugBuildCacheIdsParameter.value) {
new DebugHashesPlugin(terminal).apply(this.hooks);
// Skip build cache and legacy skip plugins when showing existing failure logs
if (!showExistingFailureLogs) {
if (buildCacheConfiguration?.buildCacheEnabled) {
terminal.writeVerboseLine(`Incremental strategy: cache restoration`);
new CacheableOperationPlugin({
allowWarningsInSuccessfulBuild:
!!this.rushConfiguration.experimentsConfiguration.configuration
.buildCacheWithAllowWarningsInSuccessfulBuild,
buildCacheConfiguration,
cobuildConfiguration,
terminal
}).apply(this.hooks);

if (this._debugBuildCacheIdsParameter.value) {
new DebugHashesPlugin(terminal).apply(this.hooks);
}
} else if (!this._disableBuildCache) {
terminal.writeVerboseLine(`Incremental strategy: output preservation`);
// Explicitly disabling the build cache also disables legacy skip detection.
new LegacySkipPlugin({
allowWarningsInSuccessfulBuild:
this.rushConfiguration.experimentsConfiguration.configuration
.buildSkipWithAllowWarningsInSuccessfulBuild,
terminal,
changedProjectsOnly,
isIncrementalBuildAllowed: this._isIncrementalBuildAllowed
}).apply(this.hooks);
} else {
terminal.writeVerboseLine(`Incremental strategy: none (full rebuild)`);
}
} else if (!this._disableBuildCache) {
terminal.writeVerboseLine(`Incremental strategy: output preservation`);
// Explicitly disabling the build cache also disables legacy skip detection.
new LegacySkipPlugin({
allowWarningsInSuccessfulBuild:
this.rushConfiguration.experimentsConfiguration.configuration
.buildSkipWithAllowWarningsInSuccessfulBuild,
terminal,
changedProjectsOnly,
isIncrementalBuildAllowed: this._isIncrementalBuildAllowed
}).apply(this.hooks);
} else {
terminal.writeVerboseLine(`Incremental strategy: none (full rebuild)`);
}

const showBuildPlan: boolean = this._cobuildPlanParameter?.value ?? false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ exports[`CommandLineHelp prints the help for each action: build 1`] = `
[--from-version-policy VERSION_POLICY_NAME] [-v]
[--include-phase-deps] [-c] [--ignore-hooks]
[--node-diagnostic-dir DIRECTORY] [--debug-build-cache-ids]
[-s] [-m]
[--show-existing-failure-logs] [-s] [-m]


This command is similar to \\"rush rebuild\\", except that \\"rush build\\" performs
Expand Down Expand Up @@ -334,6 +334,12 @@ Optional arguments:
Logs information about the components of the build
cache ids for individual operations. This is useful
for debugging the incremental build logic.
--show-existing-failure-logs
Skips execution of operations and instead displays
any existing failure logs for the selected projects.
This is useful for reviewing failures from a previous
run without re-executing the operations. Operations
without existing failure logs will be silenced.
-s, --ship Perform a production build, including minification
and localization steps
-m, --minimal Perform a fast build, which disables certain tasks
Expand Down Expand Up @@ -488,6 +494,7 @@ exports[`CommandLineHelp prints the help for each action: import-strings 1`] = `
[--include-phase-deps] [--ignore-hooks]
[--node-diagnostic-dir DIRECTORY]
[--debug-build-cache-ids]
[--show-existing-failure-logs]
[--locale {en-us,fr-fr,es-es,zh-cn}]


Expand Down Expand Up @@ -612,6 +619,12 @@ Optional arguments:
Logs information about the components of the build
cache ids for individual operations. This is useful
for debugging the incremental build logic.
--show-existing-failure-logs
Skips execution of operations and instead displays
any existing failure logs for the selected projects.
This is useful for reviewing failures from a previous
run without re-executing the operations. Operations
without existing failure logs will be silenced.
--locale {en-us,fr-fr,es-es,zh-cn}
Selects a single instead of the default locale
(en-us) for non-ship builds or all locales for ship
Expand Down Expand Up @@ -1129,7 +1142,8 @@ exports[`CommandLineHelp prints the help for each action: rebuild 1`] = `
[--from-version-policy VERSION_POLICY_NAME] [-v]
[--include-phase-deps] [--ignore-hooks]
[--node-diagnostic-dir DIRECTORY]
[--debug-build-cache-ids] [-s] [-m]
[--debug-build-cache-ids] [--show-existing-failure-logs]
[-s] [-m]


This command assumes that the package.json file for each project contains a
Expand Down Expand Up @@ -1259,6 +1273,12 @@ Optional arguments:
Logs information about the components of the build
cache ids for individual operations. This is useful
for debugging the incremental build logic.
--show-existing-failure-logs
Skips execution of operations and instead displays
any existing failure logs for the selected projects.
This is useful for reviewing failures from a previous
run without re-executing the operations. Operations
without existing failure logs will be silenced.
-s, --ship Perform a production build, including minification
and localization steps
-m, --minimal Perform a fast build, which disables certain tasks
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { FileSystem } from '@rushstack/node-core-library';
import type { ITerminal } from '@rushstack/terminal';

import { OperationStatus } from './OperationStatus';
import type { IPhasedCommandPlugin, PhasedCommandHooks } from '../../pluginFramework/PhasedCommandHooks';
import type { IOperationRunnerContext } from './IOperationRunner';
import type { OperationExecutionRecord } from './OperationExecutionRecord';
import { getProjectLogFilePaths } from './ProjectLogWritable';

const PLUGIN_NAME: 'ShowExistingFailureLogsPlugin' = 'ShowExistingFailureLogsPlugin';

export interface IShowExistingFailureLogsPluginOptions {
terminal: ITerminal;
}

/**
* Plugin that replays existing failure logs without executing operations.
* This is useful for reviewing failures from a previous run.
*/
export class ShowExistingFailureLogsPlugin implements IPhasedCommandPlugin {
private readonly _options: IShowExistingFailureLogsPluginOptions;

public constructor(options: IShowExistingFailureLogsPluginOptions) {
this._options = options;
}

public apply(hooks: PhasedCommandHooks): void {
hooks.beforeExecuteOperation.tapPromise(
PLUGIN_NAME,
async (runnerContext: IOperationRunnerContext): Promise<OperationStatus | undefined> => {
const record: OperationExecutionRecord = runnerContext as OperationExecutionRecord;
const { operation, _operationMetadataManager: operationMetadataManager } = record;

const { associatedProject: project } = operation;

// Get the path to the error log file
const { error: errorLogPath } = getProjectLogFilePaths({
project,
logFilenameIdentifier: operation.logFilenameIdentifier
});

// Check if an error log exists from a previous run
const errorLogExists: boolean = await FileSystem.existsAsync(errorLogPath);

if (errorLogExists) {
// Replay the failure log
await runnerContext.runWithTerminalAsync(
async (taskTerminal, terminalProvider) => {
// Restore the operation logs
await operationMetadataManager?.tryRestoreAsync({
terminalProvider,
terminal: taskTerminal,
errorLogPath,
cobuildContextId: undefined,
cobuildRunnerId: undefined
});
},
{ createLogFile: false, logFileSuffix: '' }
);

// Return Failure status to indicate this operation had previously failed
return OperationStatus.Failure;
} else {
// No error log exists, so this operation either succeeded or wasn't run
// Return Skipped to silence it
return OperationStatus.Skipped;
}
}
);
}
}
Loading
Loading