Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Add support for remainder arguments with new `allowRemainderArguments` option in a global, bulk, and phased command configurations in `common/config/rush/command-line.json`",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/hashed-folder-copy-plugin",
"comment": "",
"type": "none"
}
],
"packageName": "@rushstack/hashed-folder-copy-plugin"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/ts-command-line",
"comment": "This change introduces enhanced support for remainder arguments in the CommandLineRemainder class. The -- separator used to delimit remainder arguments is now automatically excluded from the values array. The -- separator used to delimit remainder arguments is now automatically excluded from the values array. For example, my-tool --flag -- arg1 arg2 will result in values being [\"arg1\", \"arg2\"], not [\"--\", \"arg1\", \"arg2\"].",
"type": "patch"
}
],
"packageName": "@rushstack/ts-command-line"
}
1 change: 1 addition & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ export interface ICreateOperationsContext {
readonly projectConfigurations: ReadonlyMap<RushConfigurationProject, RushProjectConfiguration>;
readonly projectSelection: ReadonlySet<RushConfigurationProject>;
readonly projectsInUnknownState: ReadonlySet<RushConfigurationProject>;
readonly remainderArgs?: ReadonlyArray<string>;
readonly rushConfiguration: RushConfiguration;
}

Expand Down
3 changes: 3 additions & 0 deletions libraries/rush-lib/src/api/CommandLineJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface IBulkCommandJson extends IBaseCommandJson {
allowWarningsInSuccessfulBuild?: boolean;
watchForChanges?: boolean;
disableBuildCache?: boolean;
allowRemainderArguments?: boolean;
}

/**
Expand All @@ -41,6 +42,7 @@ export interface IPhasedCommandWithoutPhasesJson extends IBaseCommandJson {
enableParallelism: boolean;
allowOversubscription?: boolean;
incremental?: boolean;
allowRemainderArguments?: boolean;
}

/**
Expand All @@ -64,6 +66,7 @@ export interface IPhasedCommandJson extends IPhasedCommandWithoutPhasesJson {
export interface IGlobalCommandJson extends IBaseCommandJson {
commandKind: 'global';
shellCommand: string;
allowRemainderArguments?: boolean;
}

export type CommandJson = IBulkCommandJson | IGlobalCommandJson | IPhasedCommandJson;
Expand Down
125 changes: 125 additions & 0 deletions libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,129 @@ describe(CommandLineConfiguration.name, () => {
expect(phase.shellCommand).toEqual('echo');
});
});

describe('allowRemainderArguments configuration', () => {
it('should accept allowRemainderArguments for bulk commands', () => {
const commandLineConfiguration: CommandLineConfiguration = new CommandLineConfiguration({
commands: [
{
commandKind: 'bulk',
name: 'test-remainder-bulk',
summary: 'Test bulk command with remainder arguments',
enableParallelism: true,
safeForSimultaneousRushProcesses: false,
allowRemainderArguments: true
}
]
});

const command = commandLineConfiguration.commands.get('test-remainder-bulk');
expect(command).toBeDefined();
expect(command?.allowRemainderArguments).toBe(true);
});

it('should accept allowRemainderArguments for global commands', () => {
const commandLineConfiguration: CommandLineConfiguration = new CommandLineConfiguration({
commands: [
{
commandKind: 'global',
name: 'test-remainder-global',
summary: 'Test global command with remainder arguments',
shellCommand: 'echo',
safeForSimultaneousRushProcesses: false,
allowRemainderArguments: true
}
]
});

const command = commandLineConfiguration.commands.get('test-remainder-global');
expect(command).toBeDefined();
expect(command?.allowRemainderArguments).toBe(true);
});

it('should accept allowRemainderArguments for phased commands', () => {
const commandLineConfiguration: CommandLineConfiguration = new CommandLineConfiguration({
commands: [
{
commandKind: 'phased',
name: 'test-remainder-phased',
summary: 'Test phased command with remainder arguments',
enableParallelism: true,
safeForSimultaneousRushProcesses: false,
phases: ['_phase:test'],
allowRemainderArguments: true
}
],
phases: [
{
name: '_phase:test'
}
]
});

const command = commandLineConfiguration.commands.get('test-remainder-phased');
expect(command).toBeDefined();
expect(command?.allowRemainderArguments).toBe(true);
});

it('should default allowRemainderArguments to false when not specified', () => {
const commandLineConfiguration: CommandLineConfiguration = new CommandLineConfiguration({
commands: [
{
commandKind: 'global',
name: 'test-no-remainder',
summary: 'Test command without remainder arguments',
shellCommand: 'echo',
safeForSimultaneousRushProcesses: false
}
]
});

const command = commandLineConfiguration.commands.get('test-no-remainder');
expect(command).toBeDefined();
expect(command?.allowRemainderArguments).toBeUndefined();
});

it('should work with both custom parameters and remainder arguments', () => {
const commandLineConfiguration: CommandLineConfiguration = new CommandLineConfiguration({
commands: [
{
commandKind: 'global',
name: 'test-mixed-params',
summary: 'Test command with both custom parameters and remainder arguments',
shellCommand: 'echo',
safeForSimultaneousRushProcesses: false,
allowRemainderArguments: true
}
],
parameters: [
{
parameterKind: 'flag',
longName: '--verbose',
associatedCommands: ['test-mixed-params'],
description: 'Enable verbose logging'
},
{
parameterKind: 'string',
longName: '--output',
argumentName: 'PATH',
associatedCommands: ['test-mixed-params'],
description: 'Output file path'
},
{
parameterKind: 'integer',
longName: '--count',
argumentName: 'NUM',
associatedCommands: ['test-mixed-params'],
description: 'Number of iterations'
}
]
});

const command = commandLineConfiguration.commands.get('test-mixed-params');
expect(command).toBeDefined();
expect(command?.allowRemainderArguments).toBe(true);
expect(command?.associatedParameters.size).toBe(3);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ export abstract class BaseScriptAction<TCommand extends Command> extends BaseRus
return;
}

// Define remainder parameter if the command allows it
if (this.command.allowRemainderArguments) {
this.defineCommandLineRemainder({
description:
'Additional command-line arguments to be passed through to the shell command or npm script'
});
}

// Find any parameters that are associated with this command
for (const parameter of this.command.associatedParameters) {
let tsCommandLineParameter: CommandLineParameter | undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ export class GlobalScriptAction extends BaseScriptAction<IGlobalCommandConfig> {
tsCommandLineParameter.appendToArgList(customParameterValues);
}

// Add remainder arguments if they exist
if (this.remainder) {
this.remainder.appendToArgList(customParameterValues);
}

for (let i: number = 0; i < customParameterValues.length; i++) {
let customParameterValue: string = customParameterValues[i];
customParameterValue = customParameterValue.replace(/"/g, '\\"');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> i
changedProjectsOnly,
cobuildConfiguration,
customParameters: customParametersByName,
remainderArgs: this.remainder?.values,
isIncrementalBuildAllowed: this._isIncrementalBuildAllowed,
isInitial: true,
isWatch,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin {
before: ShellOperationPluginName
},
async (operations: Set<Operation>, context: ICreateOperationsContext) => {
const { isWatch, isInitial } = context;
const { isWatch, isInitial, remainderArgs } = context;
if (!isWatch) {
return operations;
}

currentContext = context;

const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
getCustomParameterValuesByPhase();
getCustomParameterValuesByPhase(remainderArgs);

for (const operation of operations) {
const { associatedPhase: phase, associatedProject: project, runner } = operation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export class ShardedPhasedOperationPlugin implements IPhasedCommandPlugin {
}

function spliceShards(existingOperations: Set<Operation>, context: ICreateOperationsContext): Set<Operation> {
const { rushConfiguration, projectConfigurations } = context;
const { rushConfiguration, projectConfigurations, remainderArgs } = context;

const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
getCustomParameterValuesByPhase();
getCustomParameterValuesByPhase(remainderArgs);

for (const operation of existingOperations) {
const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin {
operations: Set<Operation>,
context: ICreateOperationsContext
): Set<Operation> {
const { rushConfiguration, isInitial } = context;
const { rushConfiguration, isInitial, remainderArgs } = context;

const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray<string> =
getCustomParameterValuesByPhase();
getCustomParameterValuesByPhase(remainderArgs);
for (const operation of operations) {
const { associatedPhase: phase, associatedProject: project } = operation;

Expand Down Expand Up @@ -120,7 +120,9 @@ export function initializeShellOperationRunner(options: {
* 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<string> {
export function getCustomParameterValuesByPhase(
remainderArgs?: ReadonlyArray<string>
): (phase: IPhase) => ReadonlyArray<string> {
const customParametersByPhase: Map<IPhase, string[]> = new Map();

function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray<string> {
Expand All @@ -131,6 +133,11 @@ export function getCustomParameterValuesByPhase(): (phase: IPhase) => ReadonlyAr
tsCommandLineParameter.appendToArgList(customParameterValues);
}

// Add remainder arguments if they exist
if (remainderArgs && remainderArgs.length > 0) {
customParameterValues.push(...remainderArgs);
}

customParametersByPhase.set(phase, customParameterValues);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,63 @@ describe(ShellOperationRunnerPlugin.name, () => {
// All projects
expect(Array.from(operations, serializeOperation)).toMatchSnapshot();
});

it('should handle remainderArgs when provided in context', async () => {
const rushJsonFile: string = path.resolve(__dirname, `../../test/customShellCommandinBulkRepo/rush.json`);
const commandLineJsonFile: string = path.resolve(
__dirname,
`../../test/customShellCommandinBulkRepo/common/config/rush/command-line.json`
);

const rushConfiguration = RushConfiguration.loadFromConfigurationFile(rushJsonFile);
const commandLineJson: ICommandLineJson = JsonFile.load(commandLineJsonFile);

const commandLineConfiguration = new CommandLineConfiguration(commandLineJson);

const echoCommand: IPhasedCommandConfig = commandLineConfiguration.commands.get(
'echo'
)! as IPhasedCommandConfig;

// Create context with remainder arguments
const fakeCreateOperationsContext: Pick<
ICreateOperationsContext,
| 'phaseOriginal'
| 'phaseSelection'
| 'projectSelection'
| 'projectsInUnknownState'
| 'projectConfigurations'
| 'remainderArgs'
> = {
phaseOriginal: echoCommand.phases,
phaseSelection: echoCommand.phases,
projectSelection: new Set(rushConfiguration.projects),
projectsInUnknownState: new Set(rushConfiguration.projects),
projectConfigurations: new Map(),
remainderArgs: ['--verbose', '--output', 'file.log']
};

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<Operation> = await hooks.createOperations.promise(
new Set(),
fakeCreateOperationsContext as ICreateOperationsContext
);

// Verify that operations were created and include remainder args in config hash
expect(operations.size).toBeGreaterThan(0);

// Get the first operation and check that remainder args affect the command configuration
const operation = Array.from(operations)[0];
const configHash = operation.runner!.getConfigHash();

// The config hash should include the remainder arguments
expect(configHash).toContain('--verbose');
expect(configHash).toContain('--output');
expect(configHash).toContain('file.log');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export interface ICreateOperationsContext {
* Maps from the `longName` field in command-line.json to the parser configuration in ts-command-line.
*/
readonly customParameters: ReadonlyMap<string, CommandLineParameter>;
/**
* The remainder arguments from the command line, if any.
* These are additional arguments that were not recognized as regular parameters.
*/
readonly remainderArgs?: ReadonlyArray<string>;
/**
* If true, projects may read their output from cache or be skipped if already up to date.
* If false, neither of the above may occur, e.g. "rush rebuild"
Expand Down
Loading
Loading