Skip to content

Commit 923758a

Browse files
Copilotdmichon-msfticlanton
authored
[rush-lib] Forward parameterNamesToIgnore to child processes via environment variable (#5494)
* Initial plan * Add IgnoredParametersPlugin to forward parameterNamesToIgnore to environment variable Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Refactor tests to use helper function for mock objects Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Address code review feedback - simplify PLUGIN_NAME constant Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Address feedback: export env var constant and simplify test mocks Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Use comma separator for environment variable to clarify enumeration Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Rename environment variable to RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Add change file for parameterNamesToIgnore environment variable feature Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> * Address feedback: use JSON.stringify for env var and update type annotations Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dmichon-msft <26827560+dmichon-msft@users.noreply.github.com> Co-authored-by: iclanton <5010588+iclanton@users.noreply.github.com>
1 parent dc482e6 commit 923758a

4 files changed

Lines changed: 227 additions & 0 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"comment": "Forward the `parameterNamesToIgnore` `<project>/config/rush-project.json` property to child processes via a `RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES` environment variable",
5+
"type": "none",
6+
"packageName": "@microsoft/rush"
7+
}
8+
],
9+
"packageName": "@microsoft/rush",
10+
"email": "198982749+Copilot@users.noreply.github.com"
11+
}

libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import { WeightedOperationPlugin } from '../../logic/operations/WeightedOperatio
5959
import { getVariantAsync, VARIANT_PARAMETER } from '../../api/Variants';
6060
import { Selection } from '../../logic/Selection';
6161
import { NodeDiagnosticDirPlugin } from '../../logic/operations/NodeDiagnosticDirPlugin';
62+
import { IgnoredParametersPlugin } from '../../logic/operations/IgnoredParametersPlugin';
6263
import { DebugHashesPlugin } from '../../logic/operations/DebugHashesPlugin';
6364
import { measureAsyncFn, measureFn } from '../../utilities/performance';
6465

@@ -409,6 +410,9 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
409410
new WeightedOperationPlugin().apply(hooks);
410411
new ValidateOperationsPlugin(terminal).apply(hooks);
411412

413+
// Forward ignored parameters to child processes as an environment variable
414+
new IgnoredParametersPlugin().apply(hooks);
415+
412416
const showTimeline: boolean = this._timelineParameter?.value ?? false;
413417
if (showTimeline) {
414418
const { ConsoleTimelinePlugin } = await import(
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import type { IPhasedCommandPlugin, PhasedCommandHooks } from '../../pluginFramework/PhasedCommandHooks';
5+
import type { IEnvironment } from '../../utilities/Utilities';
6+
import type { IOperationExecutionResult } from './IOperationExecutionResult';
7+
8+
const PLUGIN_NAME: 'IgnoredParametersPlugin' = 'IgnoredParametersPlugin';
9+
10+
/**
11+
* Environment variable name for forwarding ignored parameters to child processes
12+
* @public
13+
*/
14+
export const RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES_ENV_VAR: 'RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES' =
15+
'RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES';
16+
17+
/**
18+
* Phased command plugin that forwards the value of the `parameterNamesToIgnore` operation setting
19+
* to child processes as the RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES environment variable.
20+
*/
21+
export class IgnoredParametersPlugin implements IPhasedCommandPlugin {
22+
public apply(hooks: PhasedCommandHooks): void {
23+
hooks.createEnvironmentForOperation.tap(
24+
PLUGIN_NAME,
25+
(env: IEnvironment, record: IOperationExecutionResult) => {
26+
const { settings } = record.operation;
27+
28+
// If there are parameter names to ignore, set the environment variable
29+
if (settings?.parameterNamesToIgnore && settings.parameterNamesToIgnore.length > 0) {
30+
env[RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES_ENV_VAR] = JSON.stringify(
31+
settings.parameterNamesToIgnore
32+
);
33+
}
34+
35+
return env;
36+
}
37+
);
38+
}
39+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import path from 'node:path';
5+
import { JsonFile } from '@rushstack/node-core-library';
6+
import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal';
7+
8+
import { RushConfiguration } from '../../../api/RushConfiguration';
9+
import { CommandLineConfiguration, type IPhasedCommandConfig } from '../../../api/CommandLineConfiguration';
10+
import type { Operation } from '../Operation';
11+
import type { ICommandLineJson } from '../../../api/CommandLineJson';
12+
import { PhasedOperationPlugin } from '../PhasedOperationPlugin';
13+
import { ShellOperationRunnerPlugin } from '../ShellOperationRunnerPlugin';
14+
import {
15+
IgnoredParametersPlugin,
16+
RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES_ENV_VAR
17+
} from '../IgnoredParametersPlugin';
18+
import {
19+
type ICreateOperationsContext,
20+
PhasedCommandHooks
21+
} from '../../../pluginFramework/PhasedCommandHooks';
22+
import { RushProjectConfiguration } from '../../../api/RushProjectConfiguration';
23+
import type { IEnvironment } from '../../../utilities/Utilities';
24+
import type { IOperationRunnerContext } from '../IOperationRunner';
25+
import type { IOperationExecutionResult } from '../IOperationExecutionResult';
26+
27+
/**
28+
* Helper function to create a minimal mock record for testing the createEnvironmentForOperation hook
29+
*/
30+
function createMockRecord(operation: Operation): IOperationRunnerContext & IOperationExecutionResult {
31+
return {
32+
operation,
33+
environment: undefined
34+
} as IOperationRunnerContext & IOperationExecutionResult;
35+
}
36+
37+
describe(IgnoredParametersPlugin.name, () => {
38+
it('should set RUSHSTACK_OPERATION_IGNORED_PARAMETERS environment variable', async () => {
39+
const rushJsonFile: string = path.resolve(__dirname, `../../test/parameterIgnoringRepo/rush.json`);
40+
const commandLineJsonFile: string = path.resolve(
41+
__dirname,
42+
`../../test/parameterIgnoringRepo/common/config/rush/command-line.json`
43+
);
44+
45+
const rushConfiguration = RushConfiguration.loadFromConfigurationFile(rushJsonFile);
46+
const commandLineJson: ICommandLineJson = JsonFile.load(commandLineJsonFile);
47+
48+
const commandLineConfiguration = new CommandLineConfiguration(commandLineJson);
49+
const buildCommand: IPhasedCommandConfig = commandLineConfiguration.commands.get(
50+
'build'
51+
)! as IPhasedCommandConfig;
52+
53+
// Load project configurations
54+
const terminalProvider: ConsoleTerminalProvider = new ConsoleTerminalProvider();
55+
const terminal: Terminal = new Terminal(terminalProvider);
56+
57+
const projectConfigurations = await RushProjectConfiguration.tryLoadForProjectsAsync(
58+
rushConfiguration.projects,
59+
terminal
60+
);
61+
62+
const fakeCreateOperationsContext: Pick<
63+
ICreateOperationsContext,
64+
| 'phaseOriginal'
65+
| 'phaseSelection'
66+
| 'projectSelection'
67+
| 'projectsInUnknownState'
68+
| 'projectConfigurations'
69+
| 'rushConfiguration'
70+
> = {
71+
phaseOriginal: buildCommand.phases,
72+
phaseSelection: buildCommand.phases,
73+
projectSelection: new Set(rushConfiguration.projects),
74+
projectsInUnknownState: new Set(rushConfiguration.projects),
75+
projectConfigurations,
76+
rushConfiguration
77+
};
78+
79+
const hooks: PhasedCommandHooks = new PhasedCommandHooks();
80+
81+
// Apply plugins
82+
new PhasedOperationPlugin().apply(hooks);
83+
new ShellOperationRunnerPlugin().apply(hooks);
84+
new IgnoredParametersPlugin().apply(hooks);
85+
86+
const operations: Set<Operation> = await hooks.createOperations.promise(
87+
new Set(),
88+
fakeCreateOperationsContext as ICreateOperationsContext
89+
);
90+
91+
// Test project 'a' which has parameterNamesToIgnore: ["--production"]
92+
const operationA = Array.from(operations).find((op) => op.name === 'a');
93+
expect(operationA).toBeDefined();
94+
95+
// Create a mock operation execution result with required fields
96+
const mockRecordA = createMockRecord(operationA!);
97+
98+
// Call the hook to get the environment
99+
const envA: IEnvironment = hooks.createEnvironmentForOperation.call({ ...process.env }, mockRecordA);
100+
101+
// Verify the environment variable is set correctly for project 'a'
102+
expect(envA[RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES_ENV_VAR]).toBe('["--production"]');
103+
104+
// Test project 'b' which has parameterNamesToIgnore: ["--verbose", "--config", "--mode", "--tags"]
105+
const operationB = Array.from(operations).find((op) => op.name === 'b');
106+
expect(operationB).toBeDefined();
107+
108+
const mockRecordB = createMockRecord(operationB!);
109+
110+
const envB: IEnvironment = hooks.createEnvironmentForOperation.call({ ...process.env }, mockRecordB);
111+
112+
// Verify the environment variable is set correctly for project 'b'
113+
expect(envB[RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES_ENV_VAR]).toBe(
114+
'["--verbose","--config","--mode","--tags"]'
115+
);
116+
});
117+
118+
it('should not set environment variable when parameterNamesToIgnore is not specified', async () => {
119+
const rushJsonFile: string = path.resolve(__dirname, `../../test/customShellCommandinBulkRepo/rush.json`);
120+
const commandLineJsonFile: string = path.resolve(
121+
__dirname,
122+
`../../test/customShellCommandinBulkRepo/common/config/rush/command-line.json`
123+
);
124+
125+
const rushConfiguration = RushConfiguration.loadFromConfigurationFile(rushJsonFile);
126+
const commandLineJson: ICommandLineJson = JsonFile.load(commandLineJsonFile);
127+
128+
const commandLineConfiguration = new CommandLineConfiguration(commandLineJson);
129+
const echoCommand: IPhasedCommandConfig = commandLineConfiguration.commands.get(
130+
'echo'
131+
)! as IPhasedCommandConfig;
132+
133+
const fakeCreateOperationsContext: Pick<
134+
ICreateOperationsContext,
135+
| 'phaseOriginal'
136+
| 'phaseSelection'
137+
| 'projectSelection'
138+
| 'projectsInUnknownState'
139+
| 'projectConfigurations'
140+
| 'rushConfiguration'
141+
> = {
142+
phaseOriginal: echoCommand.phases,
143+
phaseSelection: echoCommand.phases,
144+
projectSelection: new Set(rushConfiguration.projects),
145+
projectsInUnknownState: new Set(rushConfiguration.projects),
146+
projectConfigurations: new Map(),
147+
rushConfiguration
148+
};
149+
150+
const hooks: PhasedCommandHooks = new PhasedCommandHooks();
151+
152+
// Apply plugins
153+
new PhasedOperationPlugin().apply(hooks);
154+
new ShellOperationRunnerPlugin().apply(hooks);
155+
new IgnoredParametersPlugin().apply(hooks);
156+
157+
const operations: Set<Operation> = await hooks.createOperations.promise(
158+
new Set(),
159+
fakeCreateOperationsContext as ICreateOperationsContext
160+
);
161+
162+
// Get any operation
163+
const operation = Array.from(operations)[0];
164+
expect(operation).toBeDefined();
165+
166+
const mockRecord = createMockRecord(operation);
167+
168+
const env: IEnvironment = hooks.createEnvironmentForOperation.call({ ...process.env }, mockRecord);
169+
170+
// Verify the environment variable is not set
171+
expect(env[RUSHSTACK_CLI_IGNORED_PARAMETER_NAMES_ENV_VAR]).toBeUndefined();
172+
});
173+
});

0 commit comments

Comments
 (0)