Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "Add project-level parameter ignoring to prevent unnecessary cache invalidation. Projects can now use \"parameterNamesToIgnore\" in \"rush-project.json\" to exclude custom command-line parameters that don't affect their operations.",
"type": "minor",
"packageName": "@microsoft/rush"
}
],
"packageName": "@microsoft/rush",
"email": "198982749+Copilot@users.noreply.github.com"
}
1 change: 1 addition & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ export interface IOperationSettings {
ignoreChangedProjectsOnlyFlag?: boolean;
operationName: string;
outputFolderNames?: string[];
parameterNamesToIgnore?: string[];
sharding?: IRushPhaseSharding;
weight?: number;
}
Expand Down
8 changes: 8 additions & 0 deletions libraries/rush-lib/src/api/RushProjectConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ export interface IOperationSettings {
* If true, this operation will never be skipped by the `--changed-projects-only` flag.
*/
ignoreChangedProjectsOnlyFlag?: boolean;

/**
* An optional list of custom command-line parameter names (their `parameterLongName` values from
* command-line.json) that should be ignored when invoking the command for this operation.
* This allows a project to opt out of parameters that don't affect its operation, preventing
* unnecessary cache invalidation for this operation and its consumers.
*/
parameterNamesToIgnore?: string[];
}

interface IOldRushProjectJson {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import { InternalError } from '@rushstack/node-core-library';
import type { CommandLineParameter } from '@rushstack/ts-command-line';

import type { IParameterJson, IPhase } from '../../api/CommandLineConfiguration';

/**
* Associates command line parameters with their associated phases.
* This helper is used to populate the `associatedParameters` set on each phase
* based on the `associatedPhases` property of each parameter.
*
* @param customParameters - Map of parameter definitions to their CommandLineParameter instances
* @param knownPhases - Map of phase names to IPhase objects
*/
export function associateParametersByPhase(
customParameters: ReadonlyMap<IParameterJson, CommandLineParameter>,
knownPhases: ReadonlyMap<string, IPhase>
): void {
for (const [parameterJson, tsCommandLineParameter] of customParameters) {
if (parameterJson.associatedPhases) {
for (const phaseName of parameterJson.associatedPhases) {
const phase: IPhase | undefined = knownPhases.get(phaseName);
if (!phase) {
throw new InternalError(`Could not find a phase matching ${phaseName}.`);
}
phase.associatedParameters.add(tsCommandLineParameter);
}
}
}
}
100 changes: 100 additions & 0 deletions libraries/rush-lib/src/cli/parsing/defineCustomParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { CommandLineAction, CommandLineParameter } from '@rushstack/ts-command-line';

import type { IParameterJson } from '../../api/CommandLineConfiguration';
import { RushConstants } from '../../logic/RushConstants';
import type { ParameterJson } from '../../api/CommandLineJson';

/**
* Helper function to create CommandLineParameter instances from parameter definitions.
* This centralizes the logic for defining parameters based on their kind.
*
* @param action - The CommandLineAction to define the parameters on
* @param associatedParameters - The set of parameter definitions
* @param targetMap - The map to populate with parameter definitions to CommandLineParameter instances
*/
export function defineCustomParameters(
action: CommandLineAction,
associatedParameters: Iterable<IParameterJson>,
targetMap: Map<IParameterJson, CommandLineParameter>
): void {
for (const parameter of associatedParameters) {
let tsCommandLineParameter: CommandLineParameter | undefined;

switch (parameter.parameterKind) {
case 'flag':
tsCommandLineParameter = action.defineFlagParameter({
parameterShortName: parameter.shortName,
parameterLongName: parameter.longName,
description: parameter.description,
required: parameter.required
});
break;
case 'choice':
tsCommandLineParameter = action.defineChoiceParameter({
parameterShortName: parameter.shortName,
parameterLongName: parameter.longName,
description: parameter.description,
required: parameter.required,
alternatives: parameter.alternatives.map((x) => x.name),
defaultValue: parameter.defaultValue
});
break;
case 'string':
tsCommandLineParameter = action.defineStringParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'integer':
tsCommandLineParameter = action.defineIntegerParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'stringList':
tsCommandLineParameter = action.defineStringListParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'integerList':
tsCommandLineParameter = action.defineIntegerListParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'choiceList':
tsCommandLineParameter = action.defineChoiceListParameter({
parameterShortName: parameter.shortName,
parameterLongName: parameter.longName,
description: parameter.description,
required: parameter.required,
alternatives: parameter.alternatives.map((x) => x.name)
});
break;
default:
throw new Error(
`${RushConstants.commandLineFilename} defines a parameter "${
(parameter as ParameterJson).longName
}" using an unsupported parameter kind "${(parameter as ParameterJson).parameterKind}"`
);
}

targetMap.set(parameter, tsCommandLineParameter);
}
}
83 changes: 3 additions & 80 deletions libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import type { CommandLineParameter } from '@rushstack/ts-command-line';

import { BaseRushAction, type IBaseRushActionOptions } from '../actions/BaseRushAction';
import type { Command, CommandLineConfiguration, IParameterJson } from '../../api/CommandLineConfiguration';
import { RushConstants } from '../../logic/RushConstants';
import type { ParameterJson } from '../../api/CommandLineJson';
import { defineCustomParameters } from '../parsing/defineCustomParameters';

/**
* Constructor parameters for BaseScriptAction
Expand Down Expand Up @@ -42,83 +41,7 @@ export abstract class BaseScriptAction<TCommand extends Command> extends BaseRus
return;
}

// Find any parameters that are associated with this command
for (const parameter of this.command.associatedParameters) {
let tsCommandLineParameter: CommandLineParameter | undefined;

switch (parameter.parameterKind) {
case 'flag':
tsCommandLineParameter = this.defineFlagParameter({
parameterShortName: parameter.shortName,
parameterLongName: parameter.longName,
description: parameter.description,
required: parameter.required
});
break;
case 'choice':
tsCommandLineParameter = this.defineChoiceParameter({
parameterShortName: parameter.shortName,
parameterLongName: parameter.longName,
description: parameter.description,
required: parameter.required,
alternatives: parameter.alternatives.map((x) => x.name),
defaultValue: parameter.defaultValue
});
break;
case 'string':
tsCommandLineParameter = this.defineStringParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'integer':
tsCommandLineParameter = this.defineIntegerParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'stringList':
tsCommandLineParameter = this.defineStringListParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'integerList':
tsCommandLineParameter = this.defineIntegerListParameter({
parameterLongName: parameter.longName,
parameterShortName: parameter.shortName,
description: parameter.description,
required: parameter.required,
argumentName: parameter.argumentName
});
break;
case 'choiceList':
tsCommandLineParameter = this.defineChoiceListParameter({
parameterShortName: parameter.shortName,
parameterLongName: parameter.longName,
description: parameter.description,
required: parameter.required,
alternatives: parameter.alternatives.map((x) => x.name)
});
break;
default:
throw new Error(
`${RushConstants.commandLineFilename} defines a parameter "${
(parameter as ParameterJson).longName
}" using an unsupported parameter kind "${(parameter as ParameterJson).parameterKind}"`
);
}

this.customParameters.set(parameter, tsCommandLineParameter);
}
// Use the centralized helper to create CommandLineParameter instances
defineCustomParameters(this, this.command.associatedParameters, this.customParameters);
}
}
16 changes: 4 additions & 12 deletions libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { AsyncSeriesHook } from 'tapable';

import { AlreadyReportedError, InternalError } from '@rushstack/node-core-library';
import { AlreadyReportedError } from '@rushstack/node-core-library';
import { type ITerminal, Terminal, Colorize } from '@rushstack/terminal';
import type {
CommandLineFlagParameter,
Expand Down Expand Up @@ -33,6 +33,7 @@ import { SelectionParameterSet } from '../parsing/SelectionParameterSet';
import type { IPhase, IPhasedCommandConfig } from '../../api/CommandLineConfiguration';
import type { Operation } from '../../logic/operations/Operation';
import type { OperationExecutionRecord } from '../../logic/operations/OperationExecutionRecord';
import { associateParametersByPhase } from '../parsing/associateParametersByPhase';
import { PhasedOperationPlugin } from '../../logic/operations/PhasedOperationPlugin';
import { ShellOperationRunnerPlugin } from '../../logic/operations/ShellOperationRunnerPlugin';
import { Event } from '../../api/EventHooks';
Expand Down Expand Up @@ -327,17 +328,8 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i

this.defineScriptParameters();

for (const [{ associatedPhases }, tsCommandLineParameter] of this.customParameters) {
if (associatedPhases) {
for (const phaseName of associatedPhases) {
const phase: IPhase | undefined = this._knownPhases.get(phaseName);
if (!phase) {
throw new InternalError(`Could not find a phase matching ${phaseName}.`);
}
phase.associatedParameters.add(tsCommandLineParameter);
}
}
}
// Associate parameters with their respective phases
associateParametersByPhase(this.customParameters, this._knownPhases);
}

public async runAsync(): Promise<void> {
Expand Down
10 changes: 10 additions & 0 deletions libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface IIPCOperationRunnerOptions {
commandForHash: string;
persist: boolean;
requestRun: OperationRequestRunCallback;
ignoredParameterValues: ReadonlyArray<string>;
}

function isAfterExecuteEventMessage(message: unknown): message is IAfterExecuteEventMessage {
Expand Down Expand Up @@ -59,6 +60,7 @@ export class IPCOperationRunner implements IOperationRunner {
private readonly _commandForHash: string;
private readonly _persist: boolean;
private readonly _requestRun: OperationRequestRunCallback;
private readonly _ignoredParameterValues: ReadonlyArray<string>;

private _ipcProcess: ChildProcess | undefined;
private _processReadyPromise: Promise<void> | undefined;
Expand All @@ -75,13 +77,21 @@ export class IPCOperationRunner implements IOperationRunner {

this._persist = options.persist;
this._requestRun = options.requestRun;
this._ignoredParameterValues = options.ignoredParameterValues;
}

public async executeAsync(context: IOperationRunnerContext): Promise<OperationStatus> {
return await context.runWithTerminalAsync(
async (terminal: ITerminal, terminalProvider: ITerminalProvider): Promise<OperationStatus> => {
let isConnected: boolean = false;
if (!this._ipcProcess || typeof this._ipcProcess.exitCode === 'number') {
// Log any ignored parameters
if (this._ignoredParameterValues.length > 0) {
terminal.writeLine(
`These parameters were ignored for this operation by project-level configuration: ${this._ignoredParameterValues.join(' ')}`
);
}

// Run the operation
terminal.writeLine('Invoking: ' + this._commandToRun);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { IPhase } from '../../api/CommandLineConfiguration';
import type {
ICreateOperationsContext,
IPhasedCommandPlugin,
Expand All @@ -14,7 +13,8 @@ import { OperationStatus } from './OperationStatus';
import {
PLUGIN_NAME as ShellOperationPluginName,
formatCommand,
getCustomParameterValuesByPhase,
getCustomParameterValuesByOperation,
type ICustomParameterValuesForOperation,
getDisplayName
} from './ShellOperationRunnerPlugin';

Expand Down Expand Up @@ -45,8 +45,8 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {

currentContext = context;

const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
getCustomParameterValuesByPhase();
const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation =
getCustomParameterValuesByOperation();

for (const operation of operations) {
const { associatedPhase: phase, associatedProject: project, runner } = operation;
Expand All @@ -73,7 +73,8 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
// for this operation (or downstream operations) to be restored from the build cache.
const commandForHash: string | undefined = phase.shellCommand ?? scripts?.[phaseName];

const customParameterValues: ReadonlyArray<string> = getCustomParameterValuesForPhase(phase);
const { parameterValues: customParameterValues, ignoredParameterValues } =
getCustomParameterValues(operation);
const commandToRun: string = formatCommand(rawScript, customParameterValues);

const operationName: string = getDisplayName(phase, project);
Expand All @@ -86,6 +87,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
commandToRun,
commandForHash,
persist: true,
ignoredParameterValues,
requestRun: (requestor: string, detail?: string) => {
const operationState: IOperationExecutionResult | undefined =
operationStatesByRunner.get(ipcOperationRunner);
Expand Down
Loading
Loading