Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"comment": "Add support for remainder arguments with new allowRemainderArguments option",
"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`,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing!

"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"
}
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
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,77 @@ 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();
});

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();
});

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

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

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();
});
});
});
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
16 changes: 14 additions & 2 deletions libraries/rush-lib/src/schemas/command-line.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
"title": "Disable build cache.",
"description": "Disable build cache for this action. This may be useful if this command affects state outside of projects' own folders. If the build cache is not configured, this also disables the legacy skip detection logic.",
"type": "boolean"
},
"allowRemainderArguments": {
"title": "Allow Remainder Arguments",
"description": "If true then this command will accept any additional command-line arguments after the defined parameters. The additional arguments will be passed through to the shell command (or npm script) as-is. This is useful for commands that need to forward arbitrary arguments to an external tool.",
"type": "boolean"
}
}
},
Expand All @@ -121,7 +126,8 @@
"incremental": { "$ref": "#/definitions/anything" },
"allowWarningsInSuccessfulBuild": { "$ref": "#/definitions/anything" },
"watchForChanges": { "$ref": "#/definitions/anything" },
"disableBuildCache": { "$ref": "#/definitions/anything" }
"disableBuildCache": { "$ref": "#/definitions/anything" },
"allowRemainderArguments": { "$ref": "#/definitions/anything" }
}
}
]
Expand Down Expand Up @@ -149,6 +155,11 @@
"title": "Autoinstaller Name",
"description": "If your \"shellCommand\" script depends on NPM packages, the recommended best practice is to make it into a regular Rush project that builds using your normal toolchain. In cases where the command needs to work without first having to run \"rush build\", the recommended practice is to publish the project to an NPM registry and use common/scripts/install-run.js to launch it.\n\nAutoinstallers offer another possibility: They are folders under \"common/autoinstallers\" with a package.json file and shrinkwrap file. Rush will automatically invoke the package manager to install these dependencies before an associated command is invoked. Autoinstallers have the advantage that they work even in a branch where \"rush install\" is broken, which makes them a good solution for Git hook scripts. But they have the disadvantages of not being buildable projects, and of increasing the overall installation footprint for your monorepo.\n\nThe \"autoinstallerName\" setting must not contain a path and must be a valid NPM package name.\n\nFor example, the name \"my-task\" would map to \"common/autoinstallers/my-task/package.json\", and the \"common/autoinstallers/my-task/node_modules/.bin\" folder would be added to the shell PATH when invoking the \"shellCommand\".",
"type": "string"
},
"allowRemainderArguments": {
"title": "Allow Remainder Arguments",
"description": "If true then this command will accept any additional command-line arguments after the defined parameters. The additional arguments will be passed through to the shell command as-is. This is useful for commands that need to forward arbitrary arguments to an external tool.",
"type": "boolean"
}
}
},
Expand All @@ -163,7 +174,8 @@
"safeForSimultaneousRushProcesses": { "$ref": "#/definitions/anything" },

"shellCommand": { "$ref": "#/definitions/anything" },
"autoinstallerName": { "$ref": "#/definitions/anything" }
"autoinstallerName": { "$ref": "#/definitions/anything" },
"allowRemainderArguments": { "$ref": "#/definitions/anything" }
}
}
]
Expand Down
33 changes: 33 additions & 0 deletions webpack/hashed-folder-copy-plugin/ambientTypes.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this. I'm guessing this was from an old fork.

* Describes a source folder from which assets should be copied.
*
* @public
*/
declare interface IRequireFolderSource {
/**
* The root under which glob patterns should be evaluated
*/
globsBase: string;

/**
* Glob patterns matching assets to be copied
*/
globPatterns: string[];
}

/**
* @public
*/
declare interface IRequireFolderOptions {
/**
* A set of sources to copy to the specified output folder name.
*/
sources: IRequireFolderSource[];

/**
* The name of the folder to which assets should be copied. May contain a "[hash]" token.
*/
outputFolder: string;
}

declare function requireFolder(options: IRequireFolderOptions): string;