From 7860b20dda1be0f534a353f9d38f3544a3a7de3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:45:31 +0000 Subject: [PATCH 01/14] Initial plan From 2c3b5af17eca016c2b7a162cfecc0b2ad3d7aa24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:02:25 +0000 Subject: [PATCH 02/14] Add parameter ignoring feature to rush-lib Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- common/reviews/api/rush-lib.api.md | 1 + .../src/api/RushProjectConfiguration.ts | 8 ++ .../logic/operations/IPCOperationRunner.ts | 8 ++ .../operations/IPCOperationRunnerPlugin.ts | 12 +- .../operations/ShardedPhaseOperationPlugin.ts | 6 +- .../logic/operations/ShellOperationRunner.ts | 9 ++ .../operations/ShellOperationRunnerPlugin.ts | 107 ++++++++++++++++-- .../src/schemas/rush-project.schema.json | 8 ++ 8 files changed, 141 insertions(+), 18 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index d6804daab7e..cf0b56b9abb 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -670,6 +670,7 @@ export interface IOperationSettings { ignoreChangedProjectsOnlyFlag?: boolean; operationName: string; outputFolderNames?: string[]; + parameterNamesToIgnore?: string[]; sharding?: IRushPhaseSharding; weight?: number; } diff --git a/libraries/rush-lib/src/api/RushProjectConfiguration.ts b/libraries/rush-lib/src/api/RushProjectConfiguration.ts index 75b7216f47b..6ee20c8e91a 100644 --- a/libraries/rush-lib/src/api/RushProjectConfiguration.ts +++ b/libraries/rush-lib/src/api/RushProjectConfiguration.ts @@ -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 { diff --git a/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts b/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts index b72151fdb11..c8a46df662b 100644 --- a/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts @@ -30,6 +30,7 @@ export interface IIPCOperationRunnerOptions { commandForHash: string; persist: boolean; requestRun: OperationRequestRunCallback; + ignoredParameterNames: ReadonlyArray; } function isAfterExecuteEventMessage(message: unknown): message is IAfterExecuteEventMessage { @@ -59,6 +60,7 @@ export class IPCOperationRunner implements IOperationRunner { private readonly _commandForHash: string; private readonly _persist: boolean; private readonly _requestRun: OperationRequestRunCallback; + private readonly _ignoredParameterNames: ReadonlyArray; private _ipcProcess: ChildProcess | undefined; private _processReadyPromise: Promise | undefined; @@ -75,6 +77,7 @@ export class IPCOperationRunner implements IOperationRunner { this._persist = options.persist; this._requestRun = options.requestRun; + this._ignoredParameterNames = options.ignoredParameterNames; } public async executeAsync(context: IOperationRunnerContext): Promise { @@ -85,6 +88,11 @@ export class IPCOperationRunner implements IOperationRunner { // Run the operation terminal.writeLine('Invoking: ' + this._commandToRun); + // Log any ignored parameters in verbose mode + if (this._ignoredParameterNames.length > 0) { + terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`); + } + const { rushConfiguration, projectFolder } = this._rushProject; const { environment: initialEnvironment } = context; diff --git a/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts index 550cc726889..c522ee61957 100644 --- a/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts @@ -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, @@ -14,7 +13,8 @@ import { OperationStatus } from './OperationStatus'; import { PLUGIN_NAME as ShellOperationPluginName, formatCommand, - getCustomParameterValuesByPhase, + getCustomParameterValuesForOperation, + type ICustomParameterValuesForOperation, getDisplayName } from './ShellOperationRunnerPlugin'; @@ -45,8 +45,8 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin { currentContext = context; - const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray = - getCustomParameterValuesByPhase(); + const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation = + getCustomParameterValuesForOperation(); for (const operation of operations) { const { associatedPhase: phase, associatedProject: project, runner } = operation; @@ -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 = getCustomParameterValuesForPhase(phase); + const { parameterValues: customParameterValues, ignoredParameterNames } = + getCustomParameterValues(operation); const commandToRun: string = formatCommand(rawScript, customParameterValues); const operationName: string = getDisplayName(phase, project); @@ -86,6 +87,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin { commandToRun, commandForHash, persist: true, + ignoredParameterNames, requestRun: (requestor: string, detail?: string) => { const operationState: IOperationExecutionResult | undefined = operationStatesByRunner.get(ipcOperationRunner); diff --git a/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts b/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts index 20a0ce44dbd..63ed0e3589b 100644 --- a/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts @@ -137,7 +137,8 @@ function spliceShards(existingOperations: Set, context: ICreateOperat displayName: collatorDisplayName, rushConfiguration, commandToRun, - customParameterValues: collatorParameters + customParameterValues: collatorParameters, + ignoredParameterNames: [] }); const shardOperationName: string = `${phase.name}:shard`; @@ -207,7 +208,8 @@ function spliceShards(existingOperations: Set, context: ICreateOperat commandToRun: baseCommand, customParameterValues: shardedParameters, displayName: shardDisplayName, - rushConfiguration + rushConfiguration, + ignoredParameterNames: [] }); shardOperation.addDependency(preShardOperation); diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts index 5e3200c1bdf..fe835d37736 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts @@ -20,6 +20,7 @@ export interface IShellOperationRunnerOptions { displayName: string; commandToRun: string; commandForHash: string; + ignoredParameterNames: ReadonlyArray; } /** @@ -44,6 +45,8 @@ export class ShellOperationRunner implements IOperationRunner { private readonly _rushProject: RushConfigurationProject; + private readonly _ignoredParameterNames: ReadonlyArray; + public constructor(options: IShellOperationRunnerOptions) { const { phase } = options; @@ -53,6 +56,7 @@ export class ShellOperationRunner implements IOperationRunner { this._rushProject = options.rushProject; this.commandToRun = options.commandToRun; this._commandForHash = options.commandForHash; + this._ignoredParameterNames = options.ignoredParameterNames; } public async executeAsync(context: IOperationRunnerContext): Promise { @@ -75,6 +79,11 @@ export class ShellOperationRunner implements IOperationRunner { // Run the operation terminal.writeLine(`Invoking: ${this.commandToRun}`); + // Log any ignored parameters in verbose mode + if (this._ignoredParameterNames.length > 0) { + terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`); + } + const { rushConfiguration, projectFolder } = this._rushProject; const { environment: initialEnvironment } = context; diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts index 96186d9e0d8..33dbfbca78b 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts @@ -31,15 +31,17 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin { ): Set { const { rushConfiguration, isInitial } = context; - const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray = - getCustomParameterValuesByPhase(); + const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation = + getCustomParameterValuesForOperation(); + for (const operation of operations) { const { associatedPhase: phase, associatedProject: project } = operation; if (!operation.runner) { // This is a shell command. In the future, may consider having a property on the initial operation // to specify a runner type requested in rush-project.json - const customParameterValues: ReadonlyArray = getCustomParameterValuesForPhase(phase); + const { parameterValues: customParameterValues, ignoredParameterNames } = + getCustomParameterValues(operation); const displayName: string = getDisplayName(phase, project); const { name: phaseName, shellCommand } = phase; @@ -63,6 +65,7 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin { commandForHash, commandToRun, customParameterValues, + ignoredParameterNames, rushConfiguration }); } @@ -82,8 +85,9 @@ export function initializeShellOperationRunner(options: { commandToRun: string | undefined; commandForHash?: string; customParameterValues: ReadonlyArray; + ignoredParameterNames: ReadonlyArray; }): IOperationRunner { - const { phase, project, commandToRun: rawCommandToRun, displayName } = options; + const { phase, project, commandToRun: rawCommandToRun, displayName, ignoredParameterNames } = options; if (typeof rawCommandToRun !== 'string' && phase.missingScriptBehavior === 'error') { throw new Error( @@ -104,7 +108,8 @@ export function initializeShellOperationRunner(options: { commandForHash, displayName, phase, - rushProject: project + rushProject: project, + ignoredParameterNames }); } else { // Empty build script indicates a no-op, so use a no-op runner @@ -116,30 +121,110 @@ export function initializeShellOperationRunner(options: { } } +/** + * Result of filtering custom parameters for an operation + */ +export interface ICustomParameterValuesForOperation { + /** + * The serialized custom parameter values that should be included in the command + */ + parameterValues: ReadonlyArray; + /** + * The names of parameters that were ignored for this operation + */ + ignoredParameterNames: ReadonlyArray; +} + /** * Memoizer for custom parameter values by phase * @returns A function that returns the custom parameter values for a given phase */ export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyArray { - const customParametersByPhase: Map = new Map(); + const customParametersByPhase: Map> = new Map(); function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray { - let customParameterValues: string[] | undefined = customParametersByPhase.get(phase); - if (!customParameterValues) { - customParameterValues = []; + let customParameterSet: Set | undefined = customParametersByPhase.get(phase); + if (!customParameterSet) { + const customParameterValues: string[] = []; for (const tsCommandLineParameter of phase.associatedParameters) { tsCommandLineParameter.appendToArgList(customParameterValues); } - customParametersByPhase.set(phase, customParameterValues); + customParameterSet = new Set(customParameterValues); + customParametersByPhase.set(phase, customParameterSet); } - return customParameterValues; + return Array.from(customParameterSet); } return getCustomParameterValuesForPhase; } +/** + * Gets custom parameter values for an operation, filtering out any parameters that should be ignored + * based on the operation's settings. + * @returns A function that returns the filtered custom parameter values and ignored parameter names for a given operation + */ +export function getCustomParameterValuesForOperation(): ( + operation: Operation +) => ICustomParameterValuesForOperation { + const customParametersByPhase: Map> = new Map(); + + function getCustomParameterValuesForOp(operation: Operation): ICustomParameterValuesForOperation { + const { associatedPhase: phase, settings } = operation; + + // Get or compute the set of all custom parameters for this phase + let customParameterSet: Set | undefined = customParametersByPhase.get(phase); + if (!customParameterSet) { + customParameterSet = new Set(); + for (const tsCommandLineParameter of phase.associatedParameters) { + const tempArgs: string[] = []; + tsCommandLineParameter.appendToArgList(tempArgs); + for (const arg of tempArgs) { + customParameterSet.add(arg); + } + } + + customParametersByPhase.set(phase, customParameterSet); + } + + // If there are no parameters to ignore, return early with all parameters + const parameterNamesToIgnore: string[] | undefined = settings?.parameterNamesToIgnore; + if (!parameterNamesToIgnore || parameterNamesToIgnore.length === 0) { + return { + parameterValues: Array.from(customParameterSet), + ignoredParameterNames: [] + }; + } + + // Create a set of parameter long names to ignore for fast lookup + const ignoreSet: Set = new Set(parameterNamesToIgnore); + + // Filter out ignored parameters and track which ones were ignored + const filteredParameterValues: string[] = []; + const ignoredParameterNames: string[] = []; + + for (const tsCommandLineParameter of phase.associatedParameters) { + const parameterLongName: string = tsCommandLineParameter.longName; + + if (ignoreSet.has(parameterLongName)) { + // This parameter should be ignored for this operation + ignoredParameterNames.push(parameterLongName); + } else { + // Include this parameter in the command + tsCommandLineParameter.appendToArgList(filteredParameterValues); + } + } + + return { + parameterValues: filteredParameterValues, + ignoredParameterNames + }; + } + + return getCustomParameterValuesForOp; +} + export function formatCommand(rawCommand: string, customParameterValues: ReadonlyArray): string { if (!rawCommand) { return ''; diff --git a/libraries/rush-lib/src/schemas/rush-project.schema.json b/libraries/rush-lib/src/schemas/rush-project.schema.json index 52883eaa6c1..deafa5a0db4 100644 --- a/libraries/rush-lib/src/schemas/rush-project.schema.json +++ b/libraries/rush-lib/src/schemas/rush-project.schema.json @@ -110,6 +110,14 @@ "ignoreChangedProjectsOnlyFlag": { "type": "boolean", "description": "If true, this operation never be skipped by the `--changed-projects-only` flag. This is useful for projects that bundle code from other packages." + }, + "parameterNamesToIgnore": { + "type": "array", + "description": "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.", + "items": { + "type": "string" + }, + "uniqueItems": true } } } From d684dcaf83c1ee8385376f0082af931423dcd29f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:16:02 +0000 Subject: [PATCH 03/14] Add tests for parameter ignoring feature Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../test/ShellOperationRunnerPlugin.test.ts | 72 +++++++++++++++++++ .../ShellOperationRunnerPlugin.test.ts.snap | 13 ++++ .../a/config/rush-project.json | 9 +++ .../test/parameterIgnoringRepo/a/package.json | 7 ++ .../test/parameterIgnoringRepo/b/package.json | 7 ++ .../common/config/rush/command-line.json | 18 +++++ .../test/parameterIgnoringRepo/rush.json | 16 +++++ 7 files changed, 142 insertions(+) create mode 100644 libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/config/rush-project.json create mode 100644 libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/package.json create mode 100644 libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/package.json create mode 100644 libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json create mode 100644 libraries/rush-lib/src/logic/test/parameterIgnoringRepo/rush.json diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 912531e3eef..3011a7a4c9c 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -3,6 +3,7 @@ import path from 'node:path'; import { JsonFile } from '@rushstack/node-core-library'; +import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal'; import { RushConfiguration } from '../../../api/RushConfiguration'; import { CommandLineConfiguration, type IPhasedCommandConfig } from '../../../api/CommandLineConfiguration'; @@ -14,6 +15,7 @@ import { type ICreateOperationsContext, PhasedCommandHooks } from '../../../pluginFramework/PhasedCommandHooks'; +import { RushProjectConfiguration } from '../../../api/RushProjectConfiguration'; interface ISerializedOperation { name: string; @@ -121,4 +123,74 @@ describe(ShellOperationRunnerPlugin.name, () => { // All projects expect(Array.from(operations, serializeOperation)).toMatchSnapshot(); }); + + it('parameters should be filtered when parameterNamesToIgnore is specified', async () => { + const rushJsonFile: string = path.resolve(__dirname, `../../test/parameterIgnoringRepo/rush.json`); + const commandLineJsonFile: string = path.resolve( + __dirname, + `../../test/parameterIgnoringRepo/common/config/rush/command-line.json` + ); + + const rushConfiguration = RushConfiguration.loadFromConfigurationFile(rushJsonFile); + const commandLineJson: ICommandLineJson = JsonFile.load(commandLineJsonFile); + + const commandLineConfiguration = new CommandLineConfiguration(commandLineJson); + const buildCommand: IPhasedCommandConfig = commandLineConfiguration.commands.get( + 'build' + )! as IPhasedCommandConfig; + + // Load project configurations + const terminalProvider: ConsoleTerminalProvider = new ConsoleTerminalProvider(); + const terminal: Terminal = new Terminal(terminalProvider); + + const projectConfigurations = await RushProjectConfiguration.tryLoadForProjectsAsync( + rushConfiguration.projects, + terminal + ); + + const fakeCreateOperationsContext: Pick< + ICreateOperationsContext, + | 'phaseOriginal' + | 'phaseSelection' + | 'projectSelection' + | 'projectsInUnknownState' + | 'projectConfigurations' + | 'rushConfiguration' + > = { + phaseOriginal: buildCommand.phases, + phaseSelection: buildCommand.phases, + projectSelection: new Set(rushConfiguration.projects), + projectsInUnknownState: new Set(rushConfiguration.projects), + projectConfigurations, + rushConfiguration + }; + + const hooks: PhasedCommandHooks = new PhasedCommandHooks(); + + // Generates the default operation graph + new PhasedOperationPlugin().apply(hooks); + // Applies the Shell Operation Runner to selected operations + new ShellOperationRunnerPlugin().apply(hooks); + + const operations: Set = await hooks.createOperations.promise( + new Set(), + fakeCreateOperationsContext as ICreateOperationsContext + ); + + // Verify that project 'a' has the --production parameter filtered out + const operationA = Array.from(operations).find((op) => op.name === 'a'); + expect(operationA).toBeDefined(); + const commandHashA = operationA!.runner!.getConfigHash(); + // Should not contain --production but should contain other parameters + expect(commandHashA).not.toContain('--production'); + + // Verify that project 'b' has all parameters (no filtering) + const operationB = Array.from(operations).find((op) => op.name === 'b'); + expect(operationB).toBeDefined(); + // Should contain all parameters since no filtering is configured + // Note: Parameters only appear if they are provided, so we can't test for them without setting them + + // All projects snapshot + expect(Array.from(operations, serializeOperation)).toMatchSnapshot(); + }); }); diff --git a/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap b/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap index f5990af4d43..ac9c783a923 100644 --- a/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap +++ b/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap @@ -1,5 +1,18 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`ShellOperationRunnerPlugin parameters should be filtered when parameterNamesToIgnore is specified 1`] = ` +Array [ + Object { + "commandToRun": "echo building a ", + "name": "a", + }, + Object { + "commandToRun": "echo building b ", + "name": "b", + }, +] +`; + exports[`ShellOperationRunnerPlugin shellCommand "echo custom shellCommand" should be set to commandToRun 1`] = ` Array [ Object { diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/config/rush-project.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/config/rush-project.json new file mode 100644 index 00000000000..103f06da2e0 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/config/rush-project.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-project.schema.json", + "operationSettings": [ + { + "operationName": "build", + "parameterNamesToIgnore": ["--production"] + } + ] +} diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/package.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/package.json new file mode 100644 index 00000000000..d77de36cc7b --- /dev/null +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/a/package.json @@ -0,0 +1,7 @@ +{ + "name": "a", + "version": "1.0.0", + "scripts": { + "build": "echo building a" + } +} diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/package.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/package.json new file mode 100644 index 00000000000..41a4b66e358 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/package.json @@ -0,0 +1,7 @@ +{ + "name": "b", + "version": "1.0.0", + "scripts": { + "build": "echo building b" + } +} diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json new file mode 100644 index 00000000000..40c68fab52a --- /dev/null +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", + "commands": [], + "parameters": [ + { + "longName": "--production", + "description": "A production flag", + "parameterKind": "flag", + "associatedCommands": ["build"] + }, + { + "longName": "--verbose", + "description": "A verbose flag", + "parameterKind": "flag", + "associatedCommands": ["build"] + } + ] +} diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/rush.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/rush.json new file mode 100644 index 00000000000..529024b893c --- /dev/null +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/rush.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", + "rushVersion": "5.162.0", + "pnpmVersion": "8.15.9", + "nodeSupportedVersionRange": ">=18.0.0", + "projects": [ + { + "packageName": "a", + "projectFolder": "a" + }, + { + "packageName": "b", + "projectFolder": "b" + } + ] +} From 065ef10f09fe6f978bc37efbb4d9e5f1b4a4b541 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:20:00 +0000 Subject: [PATCH 04/14] Refactor to eliminate code duplication in parameter collection Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../operations/ShellOperationRunnerPlugin.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts index 33dbfbca78b..f2dfc313d28 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts @@ -135,6 +135,21 @@ export interface ICustomParameterValuesForOperation { ignoredParameterNames: ReadonlyArray; } +/** + * Helper function to collect all parameter arguments for a phase + */ +function collectPhaseParameterArguments(phase: IPhase): Set { + const customParameterSet: Set = new Set(); + for (const tsCommandLineParameter of phase.associatedParameters) { + const tempArgs: string[] = []; + tsCommandLineParameter.appendToArgList(tempArgs); + for (const arg of tempArgs) { + customParameterSet.add(arg); + } + } + return customParameterSet; +} + /** * Memoizer for custom parameter values by phase * @returns A function that returns the custom parameter values for a given phase @@ -145,12 +160,7 @@ export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyAr function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray { let customParameterSet: Set | undefined = customParametersByPhase.get(phase); if (!customParameterSet) { - const customParameterValues: string[] = []; - for (const tsCommandLineParameter of phase.associatedParameters) { - tsCommandLineParameter.appendToArgList(customParameterValues); - } - - customParameterSet = new Set(customParameterValues); + customParameterSet = collectPhaseParameterArguments(phase); customParametersByPhase.set(phase, customParameterSet); } @@ -176,15 +186,7 @@ export function getCustomParameterValuesForOperation(): ( // Get or compute the set of all custom parameters for this phase let customParameterSet: Set | undefined = customParametersByPhase.get(phase); if (!customParameterSet) { - customParameterSet = new Set(); - for (const tsCommandLineParameter of phase.associatedParameters) { - const tempArgs: string[] = []; - tsCommandLineParameter.appendToArgList(tempArgs); - for (const arg of tempArgs) { - customParameterSet.add(arg); - } - } - + customParameterSet = collectPhaseParameterArguments(phase); customParametersByPhase.set(phase, customParameterSet); } From 873d74e06e55acadfb4315fa488cdbbb0452f36a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 20:23:21 +0000 Subject: [PATCH 05/14] Improve documentation and add clarifying comments Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../operations/ShellOperationRunnerPlugin.ts | 22 +++++++++---------- .../src/schemas/rush-project.schema.json | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts index f2dfc313d28..dbffb536709 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts @@ -183,26 +183,26 @@ export function getCustomParameterValuesForOperation(): ( function getCustomParameterValuesForOp(operation: Operation): ICustomParameterValuesForOperation { const { associatedPhase: phase, settings } = operation; - // Get or compute the set of all custom parameters for this phase - let customParameterSet: Set | undefined = customParametersByPhase.get(phase); - if (!customParameterSet) { - customParameterSet = collectPhaseParameterArguments(phase); - customParametersByPhase.set(phase, customParameterSet); - } - - // If there are no parameters to ignore, return early with all parameters + // Check if there are any parameters to ignore const parameterNamesToIgnore: string[] | undefined = settings?.parameterNamesToIgnore; if (!parameterNamesToIgnore || parameterNamesToIgnore.length === 0) { + // No filtering needed - use the cached parameter set for efficiency + let customParameterSet: Set | undefined = customParametersByPhase.get(phase); + if (!customParameterSet) { + customParameterSet = collectPhaseParameterArguments(phase); + customParametersByPhase.set(phase, customParameterSet); + } + return { parameterValues: Array.from(customParameterSet), ignoredParameterNames: [] }; } - // Create a set of parameter long names to ignore for fast lookup + // Filtering is needed - we must iterate through parameter objects to check longName + // Note: We cannot use the cached parameter set here because we need access to + // the parameter objects to get their longName property for filtering const ignoreSet: Set = new Set(parameterNamesToIgnore); - - // Filter out ignored parameters and track which ones were ignored const filteredParameterValues: string[] = []; const ignoredParameterNames: string[] = []; diff --git a/libraries/rush-lib/src/schemas/rush-project.schema.json b/libraries/rush-lib/src/schemas/rush-project.schema.json index deafa5a0db4..acf1b20e5ae 100644 --- a/libraries/rush-lib/src/schemas/rush-project.schema.json +++ b/libraries/rush-lib/src/schemas/rush-project.schema.json @@ -113,7 +113,7 @@ }, "parameterNamesToIgnore": { "type": "array", - "description": "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.", + "description": "An optional list of custom command-line parameter names that should be ignored when invoking the command for this operation. The parameter names should match the exact longName field values from the command-line.json parameters array (e.g., '--production', '--verbose'). 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.", "items": { "type": "string" }, From df43c85ef2ecbf3539e8a296a5f98f3cfc9cd490 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:39:44 +0000 Subject: [PATCH 06/14] Address PR feedback: use ignoredParameterValues and improve filtering Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../logic/operations/IPCOperationRunner.ts | 10 +++--- .../operations/IPCOperationRunnerPlugin.ts | 8 ++--- .../operations/ShardedPhaseOperationPlugin.ts | 20 ++++++----- .../logic/operations/ShellOperationRunner.ts | 10 +++--- .../operations/ShellOperationRunnerPlugin.ts | 36 +++++++++---------- .../test/ShellOperationRunnerPlugin.test.ts | 4 ++- .../common/config/rush/command-line.json | 30 ++++++++++++++++ 7 files changed, 74 insertions(+), 44 deletions(-) diff --git a/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts b/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts index c8a46df662b..33ec8c82bb9 100644 --- a/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts @@ -30,7 +30,7 @@ export interface IIPCOperationRunnerOptions { commandForHash: string; persist: boolean; requestRun: OperationRequestRunCallback; - ignoredParameterNames: ReadonlyArray; + ignoredParameterValues: ReadonlyArray; } function isAfterExecuteEventMessage(message: unknown): message is IAfterExecuteEventMessage { @@ -60,7 +60,7 @@ export class IPCOperationRunner implements IOperationRunner { private readonly _commandForHash: string; private readonly _persist: boolean; private readonly _requestRun: OperationRequestRunCallback; - private readonly _ignoredParameterNames: ReadonlyArray; + private readonly _ignoredParameterValues: ReadonlyArray; private _ipcProcess: ChildProcess | undefined; private _processReadyPromise: Promise | undefined; @@ -77,7 +77,7 @@ export class IPCOperationRunner implements IOperationRunner { this._persist = options.persist; this._requestRun = options.requestRun; - this._ignoredParameterNames = options.ignoredParameterNames; + this._ignoredParameterValues = options.ignoredParameterValues; } public async executeAsync(context: IOperationRunnerContext): Promise { @@ -89,8 +89,8 @@ export class IPCOperationRunner implements IOperationRunner { terminal.writeLine('Invoking: ' + this._commandToRun); // Log any ignored parameters in verbose mode - if (this._ignoredParameterNames.length > 0) { - terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`); + if (this._ignoredParameterValues.length > 0) { + terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterValues.join(' ')}`); } const { rushConfiguration, projectFolder } = this._rushProject; diff --git a/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts index c522ee61957..f87dcb1685a 100644 --- a/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts @@ -13,7 +13,7 @@ import { OperationStatus } from './OperationStatus'; import { PLUGIN_NAME as ShellOperationPluginName, formatCommand, - getCustomParameterValuesForOperation, + getCustomParameterValuesByOperation, type ICustomParameterValuesForOperation, getDisplayName } from './ShellOperationRunnerPlugin'; @@ -46,7 +46,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin { currentContext = context; const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation = - getCustomParameterValuesForOperation(); + getCustomParameterValuesByOperation(); for (const operation of operations) { const { associatedPhase: phase, associatedProject: project, runner } = operation; @@ -73,7 +73,7 @@ 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 { parameterValues: customParameterValues, ignoredParameterNames } = + const { parameterValues: customParameterValues, ignoredParameterValues } = getCustomParameterValues(operation); const commandToRun: string = formatCommand(rawScript, customParameterValues); @@ -87,7 +87,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin { commandToRun, commandForHash, persist: true, - ignoredParameterNames, + ignoredParameterValues, requestRun: (requestor: string, detail?: string) => { const operationState: IOperationExecutionResult | undefined = operationStatesByRunner.get(ipcOperationRunner); diff --git a/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts b/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts index 63ed0e3589b..b4f017dfe3f 100644 --- a/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts @@ -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 { IOperationSettings, RushProjectConfiguration } from '../../api/RushProjectConfiguration'; import type { ICreateOperationsContext, @@ -13,7 +12,8 @@ import { NullOperationRunner } from './NullOperationRunner'; import { Operation } from './Operation'; import { OperationStatus } from './OperationStatus'; import { - getCustomParameterValuesByPhase, + getCustomParameterValuesByOperation, + type ICustomParameterValuesForOperation, getDisplayName, initializeShellOperationRunner } from './ShellOperationRunnerPlugin'; @@ -46,8 +46,8 @@ export class ShardedPhasedOperationPlugin implements IPhasedCommandPlugin { function spliceShards(existingOperations: Set, context: ICreateOperationsContext): Set { const { rushConfiguration, projectConfigurations } = context; - const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray = - getCustomParameterValuesByPhase(); + const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation = + getCustomParameterValuesByOperation(); for (const operation of existingOperations) { const { @@ -119,10 +119,12 @@ function spliceShards(existingOperations: Set, context: ICreateOperat const collatorDisplayName: string = `${getDisplayName(phase, project)} - collate`; - const customParameters: readonly string[] = getCustomParameterValuesForPhase(phase); + // Get the custom parameter values for the collator, filtered according to the operation settings + const { parameterValues: customParameterValues, ignoredParameterValues } = + getCustomParameterValues(operation); const collatorParameters: string[] = [ - ...customParameters, + ...customParameterValues, `--shard-parent-folder="${parentFolder}"`, `--shard-count="${shards}"` ]; @@ -138,7 +140,7 @@ function spliceShards(existingOperations: Set, context: ICreateOperat rushConfiguration, commandToRun, customParameterValues: collatorParameters, - ignoredParameterNames: [] + ignoredParameterValues }); const shardOperationName: string = `${phase.name}:shard`; @@ -195,7 +197,7 @@ function spliceShards(existingOperations: Set, context: ICreateOperat ); const shardedParameters: string[] = [ - ...customParameters, + ...customParameterValues, shardArgument, outputDirectoryArgumentWithShard ]; @@ -209,7 +211,7 @@ function spliceShards(existingOperations: Set, context: ICreateOperat customParameterValues: shardedParameters, displayName: shardDisplayName, rushConfiguration, - ignoredParameterNames: [] + ignoredParameterValues }); shardOperation.addDependency(preShardOperation); diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts index fe835d37736..f275aa5d546 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts @@ -20,7 +20,7 @@ export interface IShellOperationRunnerOptions { displayName: string; commandToRun: string; commandForHash: string; - ignoredParameterNames: ReadonlyArray; + ignoredParameterValues: ReadonlyArray; } /** @@ -45,7 +45,7 @@ export class ShellOperationRunner implements IOperationRunner { private readonly _rushProject: RushConfigurationProject; - private readonly _ignoredParameterNames: ReadonlyArray; + private readonly _ignoredParameterValues: ReadonlyArray; public constructor(options: IShellOperationRunnerOptions) { const { phase } = options; @@ -56,7 +56,7 @@ export class ShellOperationRunner implements IOperationRunner { this._rushProject = options.rushProject; this.commandToRun = options.commandToRun; this._commandForHash = options.commandForHash; - this._ignoredParameterNames = options.ignoredParameterNames; + this._ignoredParameterValues = options.ignoredParameterValues; } public async executeAsync(context: IOperationRunnerContext): Promise { @@ -80,8 +80,8 @@ export class ShellOperationRunner implements IOperationRunner { terminal.writeLine(`Invoking: ${this.commandToRun}`); // Log any ignored parameters in verbose mode - if (this._ignoredParameterNames.length > 0) { - terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterNames.join(', ')}`); + if (this._ignoredParameterValues.length > 0) { + terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterValues.join(' ')}`); } const { rushConfiguration, projectFolder } = this._rushProject; diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts index dbffb536709..fc573283fce 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts @@ -32,7 +32,7 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin { const { rushConfiguration, isInitial } = context; const getCustomParameterValues: (operation: Operation) => ICustomParameterValuesForOperation = - getCustomParameterValuesForOperation(); + getCustomParameterValuesByOperation(); for (const operation of operations) { const { associatedPhase: phase, associatedProject: project } = operation; @@ -40,7 +40,7 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin { if (!operation.runner) { // This is a shell command. In the future, may consider having a property on the initial operation // to specify a runner type requested in rush-project.json - const { parameterValues: customParameterValues, ignoredParameterNames } = + const { parameterValues: customParameterValues, ignoredParameterValues } = getCustomParameterValues(operation); const displayName: string = getDisplayName(phase, project); @@ -65,7 +65,7 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin { commandForHash, commandToRun, customParameterValues, - ignoredParameterNames, + ignoredParameterValues, rushConfiguration }); } @@ -85,9 +85,9 @@ export function initializeShellOperationRunner(options: { commandToRun: string | undefined; commandForHash?: string; customParameterValues: ReadonlyArray; - ignoredParameterNames: ReadonlyArray; + ignoredParameterValues: ReadonlyArray; }): IOperationRunner { - const { phase, project, commandToRun: rawCommandToRun, displayName, ignoredParameterNames } = options; + const { phase, project, commandToRun: rawCommandToRun, displayName, ignoredParameterValues } = options; if (typeof rawCommandToRun !== 'string' && phase.missingScriptBehavior === 'error') { throw new Error( @@ -109,7 +109,7 @@ export function initializeShellOperationRunner(options: { displayName, phase, rushProject: project, - ignoredParameterNames + ignoredParameterValues }); } else { // Empty build script indicates a no-op, so use a no-op runner @@ -130,9 +130,9 @@ export interface ICustomParameterValuesForOperation { */ parameterValues: ReadonlyArray; /** - * The names of parameters that were ignored for this operation + * The serialized custom parameter values that were ignored for this operation */ - ignoredParameterNames: ReadonlyArray; + ignoredParameterValues: ReadonlyArray; } /** @@ -173,9 +173,9 @@ export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyAr /** * Gets custom parameter values for an operation, filtering out any parameters that should be ignored * based on the operation's settings. - * @returns A function that returns the filtered custom parameter values and ignored parameter names for a given operation + * @returns A function that returns the filtered custom parameter values and ignored parameter values for a given operation */ -export function getCustomParameterValuesForOperation(): ( +export function getCustomParameterValuesByOperation(): ( operation: Operation ) => ICustomParameterValuesForOperation { const customParametersByPhase: Map> = new Map(); @@ -195,7 +195,7 @@ export function getCustomParameterValuesForOperation(): ( return { parameterValues: Array.from(customParameterSet), - ignoredParameterNames: [] + ignoredParameterValues: [] }; } @@ -204,23 +204,19 @@ export function getCustomParameterValuesForOperation(): ( // the parameter objects to get their longName property for filtering const ignoreSet: Set = new Set(parameterNamesToIgnore); const filteredParameterValues: string[] = []; - const ignoredParameterNames: string[] = []; + const ignoredParameterValues: string[] = []; for (const tsCommandLineParameter of phase.associatedParameters) { const parameterLongName: string = tsCommandLineParameter.longName; - if (ignoreSet.has(parameterLongName)) { - // This parameter should be ignored for this operation - ignoredParameterNames.push(parameterLongName); - } else { - // Include this parameter in the command - tsCommandLineParameter.appendToArgList(filteredParameterValues); - } + tsCommandLineParameter.appendToArgList( + ignoreSet.has(parameterLongName) ? ignoredParameterValues : filteredParameterValues + ); } return { parameterValues: filteredParameterValues, - ignoredParameterNames + ignoredParameterValues }; } diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 3011a7a4c9c..1726a1b7ef0 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -156,13 +156,15 @@ describe(ShellOperationRunnerPlugin.name, () => { | 'projectsInUnknownState' | 'projectConfigurations' | 'rushConfiguration' + | 'customParameters' > = { phaseOriginal: buildCommand.phases, phaseSelection: buildCommand.phases, projectSelection: new Set(rushConfiguration.projects), projectsInUnknownState: new Set(rushConfiguration.projects), projectConfigurations, - rushConfiguration + rushConfiguration, + customParameters: new Map() }; const hooks: PhasedCommandHooks = new PhasedCommandHooks(); diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json index 40c68fab52a..0f2df3fd189 100644 --- a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/common/config/rush/command-line.json @@ -13,6 +13,36 @@ "description": "A verbose flag", "parameterKind": "flag", "associatedCommands": ["build"] + }, + { + "longName": "--config", + "description": "Config file path", + "parameterKind": "string", + "argumentName": "PATH", + "associatedCommands": ["build"] + }, + { + "longName": "--mode", + "description": "Build mode", + "parameterKind": "choice", + "alternatives": [ + { + "name": "dev", + "description": "Development mode" + }, + { + "name": "prod", + "description": "Production mode" + } + ], + "associatedCommands": ["build"] + }, + { + "longName": "--tags", + "description": "Build tags", + "parameterKind": "stringList", + "argumentName": "TAG", + "associatedCommands": ["build"] } ] } From bda9a91c85b9df2d1301c890830deaa3915a4bc2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:56:30 +0000 Subject: [PATCH 07/14] Address final PR feedback: improve logging and test coverage Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../logic/operations/IPCOperationRunner.ts | 12 +- .../logic/operations/ShellOperationRunner.ts | 12 +- .../operations/ShellOperationRunnerPlugin.ts | 40 ++-- .../test/ShellOperationRunnerPlugin.test.ts | 182 +++++++++++++++++- .../ShellOperationRunnerPlugin.test.ts.snap | 4 +- 5 files changed, 212 insertions(+), 38 deletions(-) diff --git a/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts b/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts index 33ec8c82bb9..6fbc924d87e 100644 --- a/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/IPCOperationRunner.ts @@ -85,14 +85,16 @@ export class IPCOperationRunner implements IOperationRunner { async (terminal: ITerminal, terminalProvider: ITerminalProvider): Promise => { let isConnected: boolean = false; if (!this._ipcProcess || typeof this._ipcProcess.exitCode === 'number') { - // Run the operation - terminal.writeLine('Invoking: ' + this._commandToRun); - - // Log any ignored parameters in verbose mode + // Log any ignored parameters if (this._ignoredParameterValues.length > 0) { - terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterValues.join(' ')}`); + terminal.writeLine( + `These parameters were ignored for this operation by project-level configuration: ${this._ignoredParameterValues.join(' ')}` + ); } + // Run the operation + terminal.writeLine('Invoking: ' + this._commandToRun); + const { rushConfiguration, projectFolder } = this._rushProject; const { environment: initialEnvironment } = context; diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts index f275aa5d546..3f9f5dc6ddb 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunner.ts @@ -76,14 +76,16 @@ export class ShellOperationRunner implements IOperationRunner { async (terminal: ITerminal, terminalProvider: ITerminalProvider) => { let hasWarningOrError: boolean = false; - // Run the operation - terminal.writeLine(`Invoking: ${this.commandToRun}`); - - // Log any ignored parameters in verbose mode + // Log any ignored parameters if (this._ignoredParameterValues.length > 0) { - terminal.writeVerboseLine(`Ignored parameters: ${this._ignoredParameterValues.join(' ')}`); + terminal.writeLine( + `These parameters were ignored for this operation by project-level configuration: ${this._ignoredParameterValues.join(' ')}` + ); } + // Run the operation + terminal.writeLine(`Invoking: ${this.commandToRun}`); + const { rushConfiguration, projectFolder } = this._rushProject; const { environment: initialEnvironment } = context; diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts index fc573283fce..e48a52482f6 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts @@ -138,16 +138,12 @@ export interface ICustomParameterValuesForOperation { /** * Helper function to collect all parameter arguments for a phase */ -function collectPhaseParameterArguments(phase: IPhase): Set { - const customParameterSet: Set = new Set(); +function collectPhaseParameterArguments(phase: IPhase): string[] { + const customParameterList: string[] = []; for (const tsCommandLineParameter of phase.associatedParameters) { - const tempArgs: string[] = []; - tsCommandLineParameter.appendToArgList(tempArgs); - for (const arg of tempArgs) { - customParameterSet.add(arg); - } + tsCommandLineParameter.appendToArgList(customParameterList); } - return customParameterSet; + return customParameterList; } /** @@ -155,16 +151,16 @@ function collectPhaseParameterArguments(phase: IPhase): Set { * @returns A function that returns the custom parameter values for a given phase */ export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyArray { - const customParametersByPhase: Map> = new Map(); + const customParametersByPhase: Map = new Map(); function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray { - let customParameterSet: Set | undefined = customParametersByPhase.get(phase); - if (!customParameterSet) { - customParameterSet = collectPhaseParameterArguments(phase); - customParametersByPhase.set(phase, customParameterSet); + let customParameterList: string[] | undefined = customParametersByPhase.get(phase); + if (!customParameterList) { + customParameterList = collectPhaseParameterArguments(phase); + customParametersByPhase.set(phase, customParameterList); } - return Array.from(customParameterSet); + return customParameterList; } return getCustomParameterValuesForPhase; @@ -178,7 +174,7 @@ export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyAr export function getCustomParameterValuesByOperation(): ( operation: Operation ) => ICustomParameterValuesForOperation { - const customParametersByPhase: Map> = new Map(); + const customParametersByPhase: Map = new Map(); function getCustomParameterValuesForOp(operation: Operation): ICustomParameterValuesForOperation { const { associatedPhase: phase, settings } = operation; @@ -186,21 +182,21 @@ export function getCustomParameterValuesByOperation(): ( // Check if there are any parameters to ignore const parameterNamesToIgnore: string[] | undefined = settings?.parameterNamesToIgnore; if (!parameterNamesToIgnore || parameterNamesToIgnore.length === 0) { - // No filtering needed - use the cached parameter set for efficiency - let customParameterSet: Set | undefined = customParametersByPhase.get(phase); - if (!customParameterSet) { - customParameterSet = collectPhaseParameterArguments(phase); - customParametersByPhase.set(phase, customParameterSet); + // No filtering needed - use the cached parameter list for efficiency + let customParameterList: string[] | undefined = customParametersByPhase.get(phase); + if (!customParameterList) { + customParameterList = collectPhaseParameterArguments(phase); + customParametersByPhase.set(phase, customParameterList); } return { - parameterValues: Array.from(customParameterSet), + parameterValues: customParameterList, ignoredParameterValues: [] }; } // Filtering is needed - we must iterate through parameter objects to check longName - // Note: We cannot use the cached parameter set here because we need access to + // Note: We cannot use the cached parameter list here because we need access to // the parameter objects to get their longName property for filtering const ignoreSet: Set = new Set(parameterNamesToIgnore); const filteredParameterValues: string[] = []; diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 1726a1b7ef0..4b3bd913f87 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -4,11 +4,23 @@ import path from 'node:path'; import { JsonFile } from '@rushstack/node-core-library'; import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal'; +import { + CommandLineAction, + type CommandLineParameter, + type CommandLineFlagParameter, + type CommandLineStringParameter, + type CommandLineChoiceParameter, + type CommandLineStringListParameter +} from '@rushstack/ts-command-line'; import { RushConfiguration } from '../../../api/RushConfiguration'; -import { CommandLineConfiguration, type IPhasedCommandConfig } from '../../../api/CommandLineConfiguration'; +import { + CommandLineConfiguration, + type IPhasedCommandConfig, + type IParameterJson +} from '../../../api/CommandLineConfiguration'; import type { Operation } from '../Operation'; -import type { ICommandLineJson } from '../../../api/CommandLineJson'; +import type { ICommandLineJson, ParameterJson } from '../../../api/CommandLineJson'; import { PhasedOperationPlugin } from '../PhasedOperationPlugin'; import { ShellOperationRunnerPlugin } from '../ShellOperationRunnerPlugin'; import { @@ -16,6 +28,7 @@ import { PhasedCommandHooks } from '../../../pluginFramework/PhasedCommandHooks'; import { RushProjectConfiguration } from '../../../api/RushProjectConfiguration'; +import { RushConstants } from '../../RushConstants'; interface ISerializedOperation { name: string; @@ -29,6 +42,106 @@ function serializeOperation(operation: Operation): ISerializedOperation { }; } +/** + * Helper function to create CommandLineParameter instances from parameter definitions. + * Extracted logic from BaseScriptAction.defineScriptParameters() + */ +function createCommandLineParameters( + action: CommandLineAction, + associatedParameters: Set +): Map { + const customParameters: Map = new Map(); + + 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}"` + ); + } + + customParameters.set(parameter.longName, tsCommandLineParameter); + } + + return customParameters; +} + +/** + * Test implementation of CommandLineAction for testing parameter handling + */ +class TestCommandLineAction extends CommandLineAction { + protected async onExecuteAsync(): Promise { + // No-op for testing + } +} + describe(ShellOperationRunnerPlugin.name, () => { it('shellCommand "echo custom shellCommand" should be set to commandToRun', async () => { const rushJsonFile: string = path.resolve(__dirname, `../../test/customShellCommandinBulkRepo/rush.json`); @@ -148,6 +261,58 @@ describe(ShellOperationRunnerPlugin.name, () => { terminal ); + // Create a dummy CommandLineAction to host the parameters + const action: TestCommandLineAction = new TestCommandLineAction({ + actionName: 'build', + summary: 'Test build action', + documentation: 'Test' + }); + + // Create CommandLineParameter instances from the parameter definitions + const customParametersMap: Map = createCommandLineParameters( + action, + buildCommand.associatedParameters + ); + + // Set values on the parameters to test filtering + // Set --production flag + const productionParam = customParametersMap.get('--production') as CommandLineFlagParameter | undefined; + if (productionParam) { + (productionParam as unknown as { _setValue(value: boolean): void })._setValue(true); + } + + // Set --verbose flag + const verboseParam = customParametersMap.get('--verbose') as CommandLineFlagParameter | undefined; + if (verboseParam) { + (verboseParam as unknown as { _setValue(value: boolean): void })._setValue(true); + } + + // Set --config parameter + const configParam = customParametersMap.get('--config') as CommandLineStringParameter | undefined; + if (configParam) { + (configParam as unknown as { _setValue(value: string): void })._setValue('/path/to/config.json'); + } + + // Set --mode parameter + const modeParam = customParametersMap.get('--mode') as CommandLineChoiceParameter | undefined; + if (modeParam) { + (modeParam as unknown as { _setValue(value: string): void })._setValue('prod'); + } + + // Set --tags parameter + const tagsParam = customParametersMap.get('--tags') as CommandLineStringListParameter | undefined; + if (tagsParam) { + (tagsParam as unknown as { _setValue(value: string[]): void })._setValue(['tag1', 'tag2']); + } + + // Update the phase's associatedParameters to use our created CommandLineParameters + for (const phase of buildCommand.phases) { + phase.associatedParameters.clear(); + for (const param of customParametersMap.values()) { + phase.associatedParameters.add(param); + } + } + const fakeCreateOperationsContext: Pick< ICreateOperationsContext, | 'phaseOriginal' @@ -164,7 +329,7 @@ describe(ShellOperationRunnerPlugin.name, () => { projectsInUnknownState: new Set(rushConfiguration.projects), projectConfigurations, rushConfiguration, - customParameters: new Map() + customParameters: customParametersMap }; const hooks: PhasedCommandHooks = new PhasedCommandHooks(); @@ -185,12 +350,21 @@ describe(ShellOperationRunnerPlugin.name, () => { const commandHashA = operationA!.runner!.getConfigHash(); // Should not contain --production but should contain other parameters expect(commandHashA).not.toContain('--production'); + expect(commandHashA).toContain('--verbose'); + expect(commandHashA).toContain('--config'); + expect(commandHashA).toContain('--mode'); + expect(commandHashA).toContain('--tags'); // Verify that project 'b' has all parameters (no filtering) const operationB = Array.from(operations).find((op) => op.name === 'b'); expect(operationB).toBeDefined(); + const commandHashB = operationB!.runner!.getConfigHash(); // Should contain all parameters since no filtering is configured - // Note: Parameters only appear if they are provided, so we can't test for them without setting them + expect(commandHashB).toContain('--production'); + expect(commandHashB).toContain('--verbose'); + expect(commandHashB).toContain('--config'); + expect(commandHashB).toContain('--mode'); + expect(commandHashB).toContain('--tags'); // All projects snapshot expect(Array.from(operations, serializeOperation)).toMatchSnapshot(); diff --git a/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap b/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap index ac9c783a923..4d2befa1132 100644 --- a/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap +++ b/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap @@ -3,11 +3,11 @@ exports[`ShellOperationRunnerPlugin parameters should be filtered when parameterNamesToIgnore is specified 1`] = ` Array [ Object { - "commandToRun": "echo building a ", + "commandToRun": "echo building a --verbose --config /path/to/config.json --mode prod --tags tag1 --tags tag2", "name": "a", }, Object { - "commandToRun": "echo building b ", + "commandToRun": "echo building b --production --verbose --config /path/to/config.json --mode prod --tags tag1 --tags tag2", "name": "b", }, ] From 87461396501256a01ec96fc6be773176ab6d2358 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 01:32:25 +0000 Subject: [PATCH 08/14] Centralize parameter creation helper and share with BaseScriptAction Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../src/cli/scriptActions/BaseScriptAction.ts | 90 +++------------ .../test/ShellOperationRunnerPlugin.test.ts | 101 +---------------- .../utilities/CommandLineParameterHelpers.ts | 103 ++++++++++++++++++ 3 files changed, 119 insertions(+), 175 deletions(-) create mode 100644 libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts diff --git a/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts index 2da245a5b7a..f2e9f1ea1e6 100644 --- a/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts @@ -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 { createCommandLineParameters } from '../../utilities/CommandLineParameterHelpers'; /** * Constructor parameters for BaseScriptAction @@ -42,83 +41,20 @@ export abstract class BaseScriptAction extends BaseRus return; } - // Find any parameters that are associated with this command - for (const parameter of this.command.associatedParameters) { - let tsCommandLineParameter: CommandLineParameter | undefined; + // Use the centralized helper to create CommandLineParameter instances + const parametersByLongName: Map = createCommandLineParameters( + this, + this.command.associatedParameters + ); - 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}"` - ); + // Map them by IParameterJson for internal use + for (const parameter of this.command.associatedParameters) { + const tsCommandLineParameter: CommandLineParameter | undefined = parametersByLongName.get( + parameter.longName + ); + if (tsCommandLineParameter) { + this.customParameters.set(parameter, tsCommandLineParameter); } - - this.customParameters.set(parameter, tsCommandLineParameter); } } } diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 4b3bd913f87..d0995bf0ac7 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -14,13 +14,9 @@ import { } from '@rushstack/ts-command-line'; import { RushConfiguration } from '../../../api/RushConfiguration'; -import { - CommandLineConfiguration, - type IPhasedCommandConfig, - type IParameterJson -} from '../../../api/CommandLineConfiguration'; +import { CommandLineConfiguration, type IPhasedCommandConfig } from '../../../api/CommandLineConfiguration'; import type { Operation } from '../Operation'; -import type { ICommandLineJson, ParameterJson } from '../../../api/CommandLineJson'; +import type { ICommandLineJson } from '../../../api/CommandLineJson'; import { PhasedOperationPlugin } from '../PhasedOperationPlugin'; import { ShellOperationRunnerPlugin } from '../ShellOperationRunnerPlugin'; import { @@ -28,7 +24,7 @@ import { PhasedCommandHooks } from '../../../pluginFramework/PhasedCommandHooks'; import { RushProjectConfiguration } from '../../../api/RushProjectConfiguration'; -import { RushConstants } from '../../RushConstants'; +import { createCommandLineParameters } from '../../../utilities/CommandLineParameterHelpers'; interface ISerializedOperation { name: string; @@ -42,97 +38,6 @@ function serializeOperation(operation: Operation): ISerializedOperation { }; } -/** - * Helper function to create CommandLineParameter instances from parameter definitions. - * Extracted logic from BaseScriptAction.defineScriptParameters() - */ -function createCommandLineParameters( - action: CommandLineAction, - associatedParameters: Set -): Map { - const customParameters: Map = new Map(); - - 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}"` - ); - } - - customParameters.set(parameter.longName, tsCommandLineParameter); - } - - return customParameters; -} - /** * Test implementation of CommandLineAction for testing parameter handling */ diff --git a/libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts b/libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts new file mode 100644 index 00000000000..496ed75b1f5 --- /dev/null +++ b/libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts @@ -0,0 +1,103 @@ +// 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 + * @returns A map from parameter longName to the created CommandLineParameter instance + */ +export function createCommandLineParameters( + action: CommandLineAction, + associatedParameters: Iterable +): Map { + const customParameters: Map = new Map(); + + 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}"` + ); + } + + customParameters.set(parameter.longName, tsCommandLineParameter); + } + + return customParameters; +} From 9d7ca3aaf545ce07916232bd7b6cb5f24fe1dac9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 8 Nov 2025 01:46:00 +0000 Subject: [PATCH 09/14] Ensure all parameter types are tested for filtering Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../test/ShellOperationRunnerPlugin.test.ts | 12 ++++++------ .../ShellOperationRunnerPlugin.test.ts.snap | 2 +- .../parameterIgnoringRepo/b/config/rush-project.json | 9 +++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/config/rush-project.json diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index d0995bf0ac7..94090be22b7 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -260,16 +260,16 @@ describe(ShellOperationRunnerPlugin.name, () => { expect(commandHashA).toContain('--mode'); expect(commandHashA).toContain('--tags'); - // Verify that project 'b' has all parameters (no filtering) + // Verify that project 'b' has --verbose, --config, --mode, and --tags filtered out const operationB = Array.from(operations).find((op) => op.name === 'b'); expect(operationB).toBeDefined(); const commandHashB = operationB!.runner!.getConfigHash(); - // Should contain all parameters since no filtering is configured + // Should contain --production but not the other parameters since they are filtered expect(commandHashB).toContain('--production'); - expect(commandHashB).toContain('--verbose'); - expect(commandHashB).toContain('--config'); - expect(commandHashB).toContain('--mode'); - expect(commandHashB).toContain('--tags'); + expect(commandHashB).not.toContain('--verbose'); + expect(commandHashB).not.toContain('--config'); + expect(commandHashB).not.toContain('--mode'); + expect(commandHashB).not.toContain('--tags'); // All projects snapshot expect(Array.from(operations, serializeOperation)).toMatchSnapshot(); diff --git a/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap b/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap index 4d2befa1132..346ce7e87b4 100644 --- a/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap +++ b/libraries/rush-lib/src/logic/operations/test/__snapshots__/ShellOperationRunnerPlugin.test.ts.snap @@ -7,7 +7,7 @@ Array [ "name": "a", }, Object { - "commandToRun": "echo building b --production --verbose --config /path/to/config.json --mode prod --tags tag1 --tags tag2", + "commandToRun": "echo building b --production", "name": "b", }, ] diff --git a/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/config/rush-project.json b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/config/rush-project.json new file mode 100644 index 00000000000..e6f27ab1857 --- /dev/null +++ b/libraries/rush-lib/src/logic/test/parameterIgnoringRepo/b/config/rush-project.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush-project.schema.json", + "operationSettings": [ + { + "operationName": "build", + "parameterNamesToIgnore": ["--verbose", "--config", "--mode", "--tags"] + } + ] +} From 7cdea4337f687e2e6c2eac8fe75beef9e382bee6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 01:36:19 +0000 Subject: [PATCH 10/14] Refactor parameter helpers into cli/parsing directory Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../cli/parsing/associateParametersByPhase.ts | 32 ++++++++ .../parsing/defineCustomParameters.ts} | 21 +++-- .../src/cli/scriptActions/BaseScriptAction.ts | 17 +--- .../cli/scriptActions/PhasedScriptAction.ts | 16 +--- .../test/ShellOperationRunnerPlugin.test.ts | 79 +++++++++++-------- 5 files changed, 91 insertions(+), 74 deletions(-) create mode 100644 libraries/rush-lib/src/cli/parsing/associateParametersByPhase.ts rename libraries/rush-lib/src/{utilities/CommandLineParameterHelpers.ts => cli/parsing/defineCustomParameters.ts} (85%) diff --git a/libraries/rush-lib/src/cli/parsing/associateParametersByPhase.ts b/libraries/rush-lib/src/cli/parsing/associateParametersByPhase.ts new file mode 100644 index 00000000000..408b1f27910 --- /dev/null +++ b/libraries/rush-lib/src/cli/parsing/associateParametersByPhase.ts @@ -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, + knownPhases: ReadonlyMap +): 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); + } + } + } +} diff --git a/libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts b/libraries/rush-lib/src/cli/parsing/defineCustomParameters.ts similarity index 85% rename from libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts rename to libraries/rush-lib/src/cli/parsing/defineCustomParameters.ts index 496ed75b1f5..bd5b80758dc 100644 --- a/libraries/rush-lib/src/utilities/CommandLineParameterHelpers.ts +++ b/libraries/rush-lib/src/cli/parsing/defineCustomParameters.ts @@ -3,9 +3,9 @@ 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'; +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. @@ -13,14 +13,13 @@ import type { ParameterJson } from '../api/CommandLineJson'; * * @param action - The CommandLineAction to define the parameters on * @param associatedParameters - The set of parameter definitions - * @returns A map from parameter longName to the created CommandLineParameter instance + * @param targetMap - The map to populate with parameter definitions to CommandLineParameter instances */ -export function createCommandLineParameters( +export function defineCustomParameters( action: CommandLineAction, - associatedParameters: Iterable -): Map { - const customParameters: Map = new Map(); - + associatedParameters: Iterable, + targetMap: Map +): void { for (const parameter of associatedParameters) { let tsCommandLineParameter: CommandLineParameter | undefined; @@ -96,8 +95,6 @@ export function createCommandLineParameters( ); } - customParameters.set(parameter.longName, tsCommandLineParameter); + targetMap.set(parameter, tsCommandLineParameter); } - - return customParameters; } diff --git a/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts index f2e9f1ea1e6..3d22215e517 100644 --- a/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts @@ -5,7 +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 { createCommandLineParameters } from '../../utilities/CommandLineParameterHelpers'; +import { defineCustomParameters } from '../parsing/defineCustomParameters'; /** * Constructor parameters for BaseScriptAction @@ -42,19 +42,6 @@ export abstract class BaseScriptAction extends BaseRus } // Use the centralized helper to create CommandLineParameter instances - const parametersByLongName: Map = createCommandLineParameters( - this, - this.command.associatedParameters - ); - - // Map them by IParameterJson for internal use - for (const parameter of this.command.associatedParameters) { - const tsCommandLineParameter: CommandLineParameter | undefined = parametersByLongName.get( - parameter.longName - ); - if (tsCommandLineParameter) { - this.customParameters.set(parameter, tsCommandLineParameter); - } - } + defineCustomParameters(this, this.command.associatedParameters, this.customParameters); } } diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index 906a3ca89ea..c5f19629af5 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -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, @@ -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'; @@ -327,17 +328,8 @@ export class PhasedScriptAction extends BaseScriptAction 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 { diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 94090be22b7..6177bd8f53c 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -4,17 +4,15 @@ import path from 'node:path'; import { JsonFile } from '@rushstack/node-core-library'; import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal'; -import { - CommandLineAction, - type CommandLineParameter, - type CommandLineFlagParameter, - type CommandLineStringParameter, - type CommandLineChoiceParameter, - type CommandLineStringListParameter -} from '@rushstack/ts-command-line'; +import { CommandLineAction, type CommandLineParameter } from '@rushstack/ts-command-line'; import { RushConfiguration } from '../../../api/RushConfiguration'; -import { CommandLineConfiguration, type IPhasedCommandConfig } from '../../../api/CommandLineConfiguration'; +import { + CommandLineConfiguration, + type IPhasedCommandConfig, + type IParameterJson, + type IPhase +} from '../../../api/CommandLineConfiguration'; import type { Operation } from '../Operation'; import type { ICommandLineJson } from '../../../api/CommandLineJson'; import { PhasedOperationPlugin } from '../PhasedOperationPlugin'; @@ -24,7 +22,8 @@ import { PhasedCommandHooks } from '../../../pluginFramework/PhasedCommandHooks'; import { RushProjectConfiguration } from '../../../api/RushProjectConfiguration'; -import { createCommandLineParameters } from '../../../utilities/CommandLineParameterHelpers'; +import { defineCustomParameters } from '../../../cli/parsing/defineCustomParameters'; +import { associateParametersByPhase } from '../../../cli/parsing/associateParametersByPhase'; interface ISerializedOperation { name: string; @@ -174,48 +173,58 @@ describe(ShellOperationRunnerPlugin.name, () => { }); // Create CommandLineParameter instances from the parameter definitions - const customParametersMap: Map = createCommandLineParameters( - action, - buildCommand.associatedParameters - ); + const customParametersMap: Map = new Map(); + defineCustomParameters(action, buildCommand.associatedParameters, customParametersMap); // Set values on the parameters to test filtering + // Create a map by longName for easier lookup + const paramsByLongName: Map = new Map(); + for (const [param, cli] of customParametersMap) { + paramsByLongName.set(param.longName, { param, cli }); + } + // Set --production flag - const productionParam = customParametersMap.get('--production') as CommandLineFlagParameter | undefined; - if (productionParam) { - (productionParam as unknown as { _setValue(value: boolean): void })._setValue(true); + const production = paramsByLongName.get('--production'); + if (production) { + (production.cli as unknown as { _setValue(value: boolean): void })._setValue(true); } // Set --verbose flag - const verboseParam = customParametersMap.get('--verbose') as CommandLineFlagParameter | undefined; - if (verboseParam) { - (verboseParam as unknown as { _setValue(value: boolean): void })._setValue(true); + const verbose = paramsByLongName.get('--verbose'); + if (verbose) { + (verbose.cli as unknown as { _setValue(value: boolean): void })._setValue(true); } // Set --config parameter - const configParam = customParametersMap.get('--config') as CommandLineStringParameter | undefined; - if (configParam) { - (configParam as unknown as { _setValue(value: string): void })._setValue('/path/to/config.json'); + const config = paramsByLongName.get('--config'); + if (config) { + (config.cli as unknown as { _setValue(value: string): void })._setValue('/path/to/config.json'); } // Set --mode parameter - const modeParam = customParametersMap.get('--mode') as CommandLineChoiceParameter | undefined; - if (modeParam) { - (modeParam as unknown as { _setValue(value: string): void })._setValue('prod'); + const mode = paramsByLongName.get('--mode'); + if (mode) { + (mode.cli as unknown as { _setValue(value: string): void })._setValue('prod'); } // Set --tags parameter - const tagsParam = customParametersMap.get('--tags') as CommandLineStringListParameter | undefined; - if (tagsParam) { - (tagsParam as unknown as { _setValue(value: string[]): void })._setValue(['tag1', 'tag2']); + const tags = paramsByLongName.get('--tags'); + if (tags) { + (tags.cli as unknown as { _setValue(value: string[]): void })._setValue(['tag1', 'tag2']); } - // Update the phase's associatedParameters to use our created CommandLineParameters + // Associate parameters with phases using the helper + // Create a map of phase names to phases for the helper + const phasesMap: Map = new Map(); for (const phase of buildCommand.phases) { - phase.associatedParameters.clear(); - for (const param of customParametersMap.values()) { - phase.associatedParameters.add(param); - } + phasesMap.set(phase.name, phase); + } + associateParametersByPhase(customParametersMap, phasesMap); + + // Create customParameters map for ICreateOperationsContext (keyed by longName) + const customParametersForContext: Map = new Map(); + for (const [param, cli] of customParametersMap) { + customParametersForContext.set(param.longName, cli); } const fakeCreateOperationsContext: Pick< @@ -234,7 +243,7 @@ describe(ShellOperationRunnerPlugin.name, () => { projectsInUnknownState: new Set(rushConfiguration.projects), projectConfigurations, rushConfiguration, - customParameters: customParametersMap + customParameters: customParametersForContext }; const hooks: PhasedCommandHooks = new PhasedCommandHooks(); From 777ef33835f15b8c1dbf4bc58fcee1e8f1088f6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 01:48:42 +0000 Subject: [PATCH 11/14] Use CommandLineParser to parse parameter values in test Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../test/ShellOperationRunnerPlugin.test.ts | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 6177bd8f53c..05d0c197a8b 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import { JsonFile } from '@rushstack/node-core-library'; import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal'; -import { CommandLineAction, type CommandLineParameter } from '@rushstack/ts-command-line'; +import { CommandLineAction, CommandLineParser, type CommandLineParameter } from '@rushstack/ts-command-line'; import { RushConfiguration } from '../../../api/RushConfiguration'; import { @@ -46,6 +46,18 @@ class TestCommandLineAction extends CommandLineAction { } } +/** + * Test implementation of CommandLineParser for testing parameter handling + */ +class TestCommandLineParser extends CommandLineParser { + public constructor() { + super({ + toolFilename: 'test-tool', + toolDescription: 'Test tool for parameter parsing' + }); + } +} + describe(ShellOperationRunnerPlugin.name, () => { it('shellCommand "echo custom shellCommand" should be set to commandToRun', async () => { const rushJsonFile: string = path.resolve(__dirname, `../../test/customShellCommandinBulkRepo/rush.json`); @@ -165,53 +177,36 @@ describe(ShellOperationRunnerPlugin.name, () => { terminal ); - // Create a dummy CommandLineAction to host the parameters + // Create CommandLineParser and action to parse parameter values + const parser: TestCommandLineParser = new TestCommandLineParser(); const action: TestCommandLineAction = new TestCommandLineAction({ actionName: 'build', summary: 'Test build action', documentation: 'Test' }); + parser.addAction(action); // Create CommandLineParameter instances from the parameter definitions const customParametersMap: Map = new Map(); defineCustomParameters(action, buildCommand.associatedParameters, customParametersMap); - // Set values on the parameters to test filtering - // Create a map by longName for easier lookup - const paramsByLongName: Map = new Map(); - for (const [param, cli] of customParametersMap) { - paramsByLongName.set(param.longName, { param, cli }); - } - - // Set --production flag - const production = paramsByLongName.get('--production'); - if (production) { - (production.cli as unknown as { _setValue(value: boolean): void })._setValue(true); - } - - // Set --verbose flag - const verbose = paramsByLongName.get('--verbose'); - if (verbose) { - (verbose.cli as unknown as { _setValue(value: boolean): void })._setValue(true); - } - - // Set --config parameter - const config = paramsByLongName.get('--config'); - if (config) { - (config.cli as unknown as { _setValue(value: string): void })._setValue('/path/to/config.json'); - } - - // Set --mode parameter - const mode = paramsByLongName.get('--mode'); - if (mode) { - (mode.cli as unknown as { _setValue(value: string): void })._setValue('prod'); - } - - // Set --tags parameter - const tags = paramsByLongName.get('--tags'); - if (tags) { - (tags.cli as unknown as { _setValue(value: string[]): void })._setValue(['tag1', 'tag2']); - } + // Parse parameter values using the parser + ( + parser as unknown as { _registerDefinedParameters(state: { parentParameterNames: Set }): void } + )._registerDefinedParameters({ parentParameterNames: new Set() }); + await parser.executeAsync([ + 'build', + '--production', + '--verbose', + '--config', + '/path/to/config.json', + '--mode', + 'prod', + '--tags', + 'tag1', + '--tags', + 'tag2' + ]); // Associate parameters with phases using the helper // Create a map of phase names to phases for the helper From e2ea7d6c7030a70a82e03cd7e2272489ec6d5fae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:20:51 +0000 Subject: [PATCH 12/14] Use executeWithoutErrorHandlingAsync instead of executeAsync Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> --- .../logic/operations/test/ShellOperationRunnerPlugin.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts index 05d0c197a8b..818144e85df 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -191,10 +191,7 @@ describe(ShellOperationRunnerPlugin.name, () => { defineCustomParameters(action, buildCommand.associatedParameters, customParametersMap); // Parse parameter values using the parser - ( - parser as unknown as { _registerDefinedParameters(state: { parentParameterNames: Set }): void } - )._registerDefinedParameters({ parentParameterNames: new Set() }); - await parser.executeAsync([ + await parser.executeWithoutErrorHandlingAsync([ 'build', '--production', '--verbose', From 4063f98f3a0ce4ff6b3d323168fdefb972ccdd14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 00:17:57 +0000 Subject: [PATCH 13/14] Add rush change file for project-level parameter ignoring feature Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --- ...ect-level-parameter-ignoring_2025-11-13-00-17.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json diff --git a/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json b/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json new file mode 100644 index 00000000000..a0efdc5da5b --- /dev/null +++ b/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json @@ -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" +} \ No newline at end of file From 10cc6ab59d384237e2d402501cd76e53163cf098 Mon Sep 17 00:00:00 2001 From: David Michon Date: Wed, 12 Nov 2025 17:57:22 -0800 Subject: [PATCH 14/14] Adjust formatting of change file --- ...t-add-project-level-parameter-ignoring_2025-11-13-00-17.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json b/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json index a0efdc5da5b..977b38c66c7 100644 --- a/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json +++ b/common/changes/@microsoft/rush/copilot-add-project-level-parameter-ignoring_2025-11-13-00-17.json @@ -1,7 +1,7 @@ { "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.", + "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" }