Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "",
"type": "none",
"packageName": "@microsoft/rush"
}
],
"packageName": "@microsoft/rush",
"email": "198982749+Copilot@users.noreply.github.com"
}
29 changes: 29 additions & 0 deletions libraries/rush-lib/src/api/RushProjectConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,35 @@ export class RushProjectConfiguration {
}
}
}

// Validate that parameter names to ignore actually exist for this operation
if (operationSettings.parameterNamesToIgnore) {
// Build a set of valid parameter names for this phase
const validParameterNames: Set<string> = new Set<string>();
for (const parameter of phase.associatedParameters) {
validParameterNames.add(parameter.longName);
}

// Collect all invalid parameter names
const invalidParameterNames: string[] = [];
for (const parameterName of operationSettings.parameterNamesToIgnore) {
if (!validParameterNames.has(parameterName)) {
invalidParameterNames.push(parameterName);
}
}

// Report all invalid parameters in a single message
if (invalidParameterNames.length > 0) {
terminal.writeErrorLine(
`The project "${project.packageName}" has a ` +
`"${RUSH_PROJECT_CONFIGURATION_FILE.projectRelativeFilePath}" configuration that specifies ` +
`invalid parameter(s) in "parameterNamesToIgnore" for operation "${operationName}": ` +
`${invalidParameterNames.join(', ')}. ` +
`Valid parameters for this operation are: ${Array.from(validParameterNames).sort().join(', ') || '(none)'}.`
);
hasErrors = true;
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See LICENSE in the project root for license information.

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

import type { IPhase } from '../CommandLineConfiguration';
import type { RushConfigurationProject } from '../RushConfigurationProject';
Expand Down Expand Up @@ -57,7 +58,36 @@ function validateConfiguration(rushProjectConfiguration: RushProjectConfiguratio
try {
rushProjectConfiguration.validatePhaseConfiguration(
Array.from(rushProjectConfiguration.operationSettingsByOperationName.keys()).map(
(phaseName) => ({ name: phaseName }) as IPhase
(phaseName) => ({ name: phaseName, associatedParameters: new Set() }) as IPhase
),
terminal
);
} finally {
expect(terminalProvider.getOutput()).toMatchSnapshot('validation: terminal output');
expect(terminalProvider.getErrorOutput()).toMatchSnapshot('validation: terminal error');
expect(terminalProvider.getWarningOutput()).toMatchSnapshot('validation: terminal warning');
expect(terminalProvider.getVerboseOutput()).toMatchSnapshot('validation: terminal verbose');
}
}
}

function validateConfigurationWithParameters(
rushProjectConfiguration: RushProjectConfiguration | undefined,
parameterNames: string[]
): void {
const terminalProvider: StringBufferTerminalProvider = new StringBufferTerminalProvider();
const terminal: Terminal = new Terminal(terminalProvider);

if (rushProjectConfiguration) {
try {
// Create mock parameters with the specified names
const mockParameters = new Set<CommandLineParameter>(
parameterNames.map((name) => ({ longName: name }) as CommandLineParameter)
);

rushProjectConfiguration.validatePhaseConfiguration(
Array.from(rushProjectConfiguration.operationSettingsByOperationName.keys(),
(phaseName) => ({ name: phaseName, associatedParameters: mockParameters }) as IPhase
),
terminal
);
Expand Down Expand Up @@ -100,6 +130,33 @@ describe(RushProjectConfiguration.name, () => {

expect(() => validateConfiguration(rushProjectConfiguration)).toThrowError();
});

it('validates that parameters in parameterNamesToIgnore exist for the operation', async () => {
const rushProjectConfiguration: RushProjectConfiguration | undefined =
await loadProjectConfigurationAsync('test-project-e');

expect(() => validateConfiguration(rushProjectConfiguration)).toThrowError();
});

it('validates nonexistent parameters when operation has valid parameters', async () => {
const rushProjectConfiguration: RushProjectConfiguration | undefined =
await loadProjectConfigurationAsync('test-project-f');

// Provide some valid parameters for the operation
expect(() =>
validateConfigurationWithParameters(rushProjectConfiguration, ['--production', '--verbose'])
).toThrowError();
});

it('validates mix of existent and nonexistent parameters', async () => {
const rushProjectConfiguration: RushProjectConfiguration | undefined =
await loadProjectConfigurationAsync('test-project-g');

// Provide some valid parameters, test-project-g references both valid and invalid ones
expect(() =>
validateConfigurationWithParameters(rushProjectConfiguration, ['--production', '--verbose'])
).toThrowError();
});
});

describe(RushProjectConfiguration.prototype.getCacheDisabledReason.name, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,27 @@ exports[`RushProjectConfiguration operationSettingsByOperationName loads a rush-
exports[`RushProjectConfiguration operationSettingsByOperationName loads a rush-project.json config that extends another config file: validation: terminal warning 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName throws an error when loading a rush-project.json config that lists an operation twice 1`] = `"The operation \\"_phase:a\\" occurs multiple times in the \\"operationSettings\\" array in \\"<testFolder>/config/rush-project.json\\"."`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates mix of existent and nonexistent parameters: validation: terminal error 1`] = `"The project \\"test-project-g\\" has a \\"config/rush-project.json\\" configuration that specifies invalid parameter(s) in \\"parameterNamesToIgnore\\" for operation \\"_phase:build\\": --nonexistent-param. Valid parameters for this operation are: --production, --verbose.[n]"`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates mix of existent and nonexistent parameters: validation: terminal output 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates mix of existent and nonexistent parameters: validation: terminal verbose 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates mix of existent and nonexistent parameters: validation: terminal warning 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates nonexistent parameters when operation has valid parameters: validation: terminal error 1`] = `"The project \\"test-project-f\\" has a \\"config/rush-project.json\\" configuration that specifies invalid parameter(s) in \\"parameterNamesToIgnore\\" for operation \\"_phase:build\\": --nonexistent-param, --another-nonexistent. Valid parameters for this operation are: --production, --verbose.[n]"`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates nonexistent parameters when operation has valid parameters: validation: terminal output 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates nonexistent parameters when operation has valid parameters: validation: terminal verbose 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates nonexistent parameters when operation has valid parameters: validation: terminal warning 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates that parameters in parameterNamesToIgnore exist for the operation: validation: terminal error 1`] = `"The project \\"test-project-e\\" has a \\"config/rush-project.json\\" configuration that specifies invalid parameter(s) in \\"parameterNamesToIgnore\\" for operation \\"_phase:build\\": --invalid-parameter, --another-invalid, -malformed-parameter. Valid parameters for this operation are: (none).[n]"`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates that parameters in parameterNamesToIgnore exist for the operation: validation: terminal output 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates that parameters in parameterNamesToIgnore exist for the operation: validation: terminal verbose 1`] = `""`;

exports[`RushProjectConfiguration operationSettingsByOperationName validates that parameters in parameterNamesToIgnore exist for the operation: validation: terminal warning 1`] = `""`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"operationSettings": [
{
"operationName": "_phase:build",
"outputFolderNames": ["lib"],
"parameterNamesToIgnore": ["--invalid-parameter", "--another-invalid", "-malformed-parameter"]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"operationSettings": [
{
"operationName": "_phase:build",
"outputFolderNames": ["lib"],
"parameterNamesToIgnore": ["--nonexistent-param", "--another-nonexistent"]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"operationSettings": [
{
"operationName": "_phase:build",
"outputFolderNames": ["lib"],
"parameterNamesToIgnore": ["--production", "--nonexistent-param", "--verbose"]
}
]
}
Loading