From 4774d08c475c47c639afb3b8bb63ac8db848766a Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Wed, 8 Oct 2025 17:46:13 -0600 Subject: [PATCH 01/12] first attempt --- common/reviews/api/rush-lib.api.md | 1 + libraries/rush-lib/src/api/CommandLineJson.ts | 3 ++ .../src/cli/scriptActions/BaseScriptAction.ts | 8 +++++ .../cli/scriptActions/GlobalScriptAction.ts | 5 +++ .../cli/scriptActions/PhasedScriptAction.ts | 1 + .../operations/IPCOperationRunnerPlugin.ts | 4 +-- .../operations/ShardedPhaseOperationPlugin.ts | 4 +-- .../operations/ShellOperationRunnerPlugin.ts | 13 ++++++-- .../src/pluginFramework/PhasedCommandHooks.ts | 5 +++ .../src/schemas/command-line.schema.json | 16 +++++++-- .../ambientTypes.d.ts | 33 +++++++++++++++++++ 11 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 webpack/hashed-folder-copy-plugin/ambientTypes.d.ts diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 3fa9d1b178b..f8be6666e51 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -430,6 +430,7 @@ export interface ICreateOperationsContext { readonly projectConfigurations: ReadonlyMap; readonly projectSelection: ReadonlySet; readonly projectsInUnknownState: ReadonlySet; + readonly remainderArgs?: ReadonlyArray; readonly rushConfiguration: RushConfiguration; } diff --git a/libraries/rush-lib/src/api/CommandLineJson.ts b/libraries/rush-lib/src/api/CommandLineJson.ts index e6507e49633..69b28c79b26 100644 --- a/libraries/rush-lib/src/api/CommandLineJson.ts +++ b/libraries/rush-lib/src/api/CommandLineJson.ts @@ -30,6 +30,7 @@ export interface IBulkCommandJson extends IBaseCommandJson { allowWarningsInSuccessfulBuild?: boolean; watchForChanges?: boolean; disableBuildCache?: boolean; + allowRemainderArguments?: boolean; } /** @@ -41,6 +42,7 @@ export interface IPhasedCommandWithoutPhasesJson extends IBaseCommandJson { enableParallelism: boolean; allowOversubscription?: boolean; incremental?: boolean; + allowRemainderArguments?: boolean; } /** @@ -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; diff --git a/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts index 2da245a5b7a..3097b2bd64b 100644 --- a/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/BaseScriptAction.ts @@ -42,6 +42,14 @@ export abstract class BaseScriptAction 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; diff --git a/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts index 9b35156f188..7cd6996759c 100644 --- a/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts @@ -135,6 +135,11 @@ export class GlobalScriptAction extends BaseScriptAction { 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, '\\"'); diff --git a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts index 906a3ca89ea..e7277ae7d5c 100644 --- a/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts +++ b/libraries/rush-lib/src/cli/scriptActions/PhasedScriptAction.ts @@ -566,6 +566,7 @@ export class PhasedScriptAction extends BaseScriptAction i changedProjectsOnly, cobuildConfiguration, customParameters: customParametersByName, + remainderArgs: this.remainder?.values, isIncrementalBuildAllowed: this._isIncrementalBuildAllowed, isInitial: true, isWatch, diff --git a/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts index 550cc726889..b980c6406ce 100644 --- a/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/IPCOperationRunnerPlugin.ts @@ -38,7 +38,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin { before: ShellOperationPluginName }, async (operations: Set, context: ICreateOperationsContext) => { - const { isWatch, isInitial } = context; + const { isWatch, isInitial, remainderArgs } = context; if (!isWatch) { return operations; } @@ -46,7 +46,7 @@ export class IPCOperationRunnerPlugin implements IPhasedCommandPlugin { currentContext = context; const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray = - getCustomParameterValuesByPhase(); + getCustomParameterValuesByPhase(remainderArgs); for (const operation of operations) { const { associatedPhase: phase, associatedProject: project, runner } = operation; diff --git a/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts b/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts index 20a0ce44dbd..6f82c803d8f 100644 --- a/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShardedPhaseOperationPlugin.ts @@ -44,10 +44,10 @@ export class ShardedPhasedOperationPlugin implements IPhasedCommandPlugin { } function spliceShards(existingOperations: Set, context: ICreateOperationsContext): Set { - const { rushConfiguration, projectConfigurations } = context; + const { rushConfiguration, projectConfigurations, remainderArgs } = context; const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray = - getCustomParameterValuesByPhase(); + getCustomParameterValuesByPhase(remainderArgs); for (const operation of existingOperations) { const { diff --git a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts index 96186d9e0d8..c0d4a7ef44b 100644 --- a/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts +++ b/libraries/rush-lib/src/logic/operations/ShellOperationRunnerPlugin.ts @@ -29,10 +29,10 @@ export class ShellOperationRunnerPlugin implements IPhasedCommandPlugin { operations: Set, context: ICreateOperationsContext ): Set { - const { rushConfiguration, isInitial } = context; + const { rushConfiguration, isInitial, remainderArgs } = context; const getCustomParameterValuesForPhase: (phase: IPhase) => ReadonlyArray = - getCustomParameterValuesByPhase(); + getCustomParameterValuesByPhase(remainderArgs); for (const operation of operations) { const { associatedPhase: phase, associatedProject: project } = operation; @@ -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 { +export function getCustomParameterValuesByPhase( + remainderArgs?: ReadonlyArray +): (phase: IPhase) => ReadonlyArray { const customParametersByPhase: Map = new Map(); function getCustomParameterValuesForPhase(phase: IPhase): ReadonlyArray { @@ -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); } diff --git a/libraries/rush-lib/src/pluginFramework/PhasedCommandHooks.ts b/libraries/rush-lib/src/pluginFramework/PhasedCommandHooks.ts index 97ccdb064aa..a29c7d4685d 100644 --- a/libraries/rush-lib/src/pluginFramework/PhasedCommandHooks.ts +++ b/libraries/rush-lib/src/pluginFramework/PhasedCommandHooks.ts @@ -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; + /** + * The remainder arguments from the command line, if any. + * These are additional arguments that were not recognized as regular parameters. + */ + readonly remainderArgs?: ReadonlyArray; /** * 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" diff --git a/libraries/rush-lib/src/schemas/command-line.schema.json b/libraries/rush-lib/src/schemas/command-line.schema.json index 0091e7bb7ae..866a4c4b0c0 100644 --- a/libraries/rush-lib/src/schemas/command-line.schema.json +++ b/libraries/rush-lib/src/schemas/command-line.schema.json @@ -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" } } }, @@ -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" } } } ] @@ -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" } } }, @@ -163,7 +174,8 @@ "safeForSimultaneousRushProcesses": { "$ref": "#/definitions/anything" }, "shellCommand": { "$ref": "#/definitions/anything" }, - "autoinstallerName": { "$ref": "#/definitions/anything" } + "autoinstallerName": { "$ref": "#/definitions/anything" }, + "allowRemainderArguments": { "$ref": "#/definitions/anything" } } } ] diff --git a/webpack/hashed-folder-copy-plugin/ambientTypes.d.ts b/webpack/hashed-folder-copy-plugin/ambientTypes.d.ts new file mode 100644 index 00000000000..4499449c005 --- /dev/null +++ b/webpack/hashed-folder-copy-plugin/ambientTypes.d.ts @@ -0,0 +1,33 @@ +/** + * 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; From 2f014c0df8e656214fa934bfd8a3973f0c2c6962 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Wed, 8 Oct 2025 17:54:30 -0600 Subject: [PATCH 02/12] docs --- .../feature-add-remainder-args_2025-10-08-23-54.json | 10 ++++++++++ .../feature-add-remainder-args_2025-10-08-23-54.json | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json create mode 100644 common/changes/@rushstack/hashed-folder-copy-plugin/feature-add-remainder-args_2025-10-08-23-54.json diff --git a/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json b/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json new file mode 100644 index 00000000000..0941a7940a9 --- /dev/null +++ b/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add support for remainder arguments with new allowRemainderArguments option", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/hashed-folder-copy-plugin/feature-add-remainder-args_2025-10-08-23-54.json b/common/changes/@rushstack/hashed-folder-copy-plugin/feature-add-remainder-args_2025-10-08-23-54.json new file mode 100644 index 00000000000..e8c42a34e96 --- /dev/null +++ b/common/changes/@rushstack/hashed-folder-copy-plugin/feature-add-remainder-args_2025-10-08-23-54.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/hashed-folder-copy-plugin", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/hashed-folder-copy-plugin" +} \ No newline at end of file From 44e4f52014bdc1040f8e13ea89bb5f8d5023fece Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Thu, 9 Oct 2025 10:00:29 -0600 Subject: [PATCH 03/12] add tests --- .../api/test/CommandLineConfiguration.test.ts | 73 +++++++++++++++++++ .../test/ShellOperationRunnerPlugin.test.ts | 59 +++++++++++++++ 2 files changed, 132 insertions(+) diff --git a/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts b/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts index 6cd9360ed1b..25bdfd3c3bc 100644 --- a/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts +++ b/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts @@ -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(); + }); + }); }); 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..e38b387f627 100644 --- a/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/ShellOperationRunnerPlugin.test.ts @@ -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 = 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'); + }); }); From 0a042cc9ec94d30fbab6b80dfbf499dae5bb2f4e Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Wed, 15 Oct 2025 08:08:18 -0600 Subject: [PATCH 04/12] remove acceidental ambientTypes commit --- .../ambientTypes.d.ts | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 webpack/hashed-folder-copy-plugin/ambientTypes.d.ts diff --git a/webpack/hashed-folder-copy-plugin/ambientTypes.d.ts b/webpack/hashed-folder-copy-plugin/ambientTypes.d.ts deleted file mode 100644 index 4499449c005..00000000000 --- a/webpack/hashed-folder-copy-plugin/ambientTypes.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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; From 7e552d15cc771c4dd2b7f686fb68993f87aa1d46 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Wed, 15 Oct 2025 08:10:18 -0600 Subject: [PATCH 05/12] update feature comment --- .../rush/feature-add-remainder-args_2025-10-08-23-54.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json b/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json index 0941a7940a9..b84438cf259 100644 --- a/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json +++ b/common/changes/@microsoft/rush/feature-add-remainder-args_2025-10-08-23-54.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/rush", - "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`", "type": "none" } ], From 3dfa236bc459a236ea772eeb2c9ed2fc3645d129 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Wed, 15 Oct 2025 08:19:32 -0600 Subject: [PATCH 06/12] update tests and improve remainder argument documentation --- .../api/test/CommandLineConfiguration.test.ts | 62 +++++++++++++++++-- .../src/schemas/command-line.schema.json | 12 +++- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts b/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts index 25bdfd3c3bc..b9e80f32460 100644 --- a/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts +++ b/libraries/rush-lib/src/api/test/CommandLineConfiguration.test.ts @@ -312,6 +312,7 @@ describe(CommandLineConfiguration.name, () => { const command = commandLineConfiguration.commands.get('test-remainder-bulk'); expect(command).toBeDefined(); + expect(command?.allowRemainderArguments).toBe(true); }); it('should accept allowRemainderArguments for global commands', () => { @@ -330,24 +331,32 @@ describe(CommandLineConfiguration.name, () => { const command = commandLineConfiguration.commands.get('test-remainder-global'); expect(command).toBeDefined(); + expect(command?.allowRemainderArguments).toBe(true); }); - it('should accept allowRemainderArguments for phased commands with bulk command', () => { + it('should accept allowRemainderArguments for phased commands', () => { const commandLineConfiguration: CommandLineConfiguration = new CommandLineConfiguration({ commands: [ { - commandKind: 'bulk', - name: 'test-remainder-bulk-command', - summary: 'Test bulk command with remainder arguments', + 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-bulk-command'); + 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', () => { @@ -365,6 +374,49 @@ describe(CommandLineConfiguration.name, () => { 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); }); }); }); diff --git a/libraries/rush-lib/src/schemas/command-line.schema.json b/libraries/rush-lib/src/schemas/command-line.schema.json index 866a4c4b0c0..84b7da939bc 100644 --- a/libraries/rush-lib/src/schemas/command-line.schema.json +++ b/libraries/rush-lib/src/schemas/command-line.schema.json @@ -103,7 +103,7 @@ }, "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.", + "description": "If true, this command will accept additional command-line arguments that appear after the \"--\" separator. Everything after \"--\" (not including the \"--\" itself) will be passed through to the shell command or npm script. Any unrecognized arguments before \"--\" will still cause an error. Example: \"rush my-command --known-flag value -- --arbitrary --args here\" will pass \"--arbitrary --args here\" to the script, but \"rush my-command --unknown-flag\" will fail with an error about the unrecognized parameter.", "type": "boolean" } } @@ -158,7 +158,7 @@ }, "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.", + "description": "If true, this command will accept additional command-line arguments that appear after the \"--\" separator. Everything after \"--\" (not including the \"--\" itself) will be passed through to the shell command. Any unrecognized arguments before \"--\" will still cause an error. Example: \"rush my-command --known-flag value -- --arbitrary --args here\" will pass \"--arbitrary --args here\" to the script, but \"rush my-command --unknown-flag\" will fail with an error about the unrecognized parameter.", "type": "boolean" } } @@ -262,6 +262,11 @@ "type": "boolean" } } + }, + "allowRemainderArguments": { + "title": "Allow Remainder Arguments", + "description": "If true, this command will accept additional command-line arguments that appear after the \"--\" separator. Everything after \"--\" (not including the \"--\" itself) will be passed through to the phase scripts. Any unrecognized arguments before \"--\" will still cause an error. Example: \"rush my-command --known-flag value -- --arbitrary --args here\" will pass \"--arbitrary --args here\" to the phase scripts, but \"rush my-command --unknown-flag\" will fail with an error about the unrecognized parameter.", + "type": "boolean" } } }, @@ -280,7 +285,8 @@ "incremental": { "$ref": "#/definitions/anything" }, "phases": { "$ref": "#/definitions/anything" }, "watchOptions": { "$ref": "#/definitions/anything" }, - "installOptions": { "$ref": "#/definitions/anything" } + "installOptions": { "$ref": "#/definitions/anything" }, + "allowRemainderArguments": { "$ref": "#/definitions/anything" } } } ] From efbc6482e9b96ca9451e70c0957d853f5c54a13b Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Thu, 16 Oct 2025 07:37:03 -0600 Subject: [PATCH 07/12] update ts-command-line --- .../src/parameters/CommandLineRemainder.ts | 9 +++++++- .../src/providers/ScopedCommandLineAction.ts | 21 +++---------------- .../CommandLineRemainder.test.ts.snap | 1 - .../ScopedCommandLineAction.test.ts.snap | 4 +--- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts b/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts index 06e1106632a..395fa344529 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts @@ -24,6 +24,10 @@ export class CommandLineRemainder { * * @remarks * The array will be empty if the command-line has not been parsed yet. + * + * When the `--` separator is used to delimit remainder arguments, it is automatically + * excluded from this array. For example, `my-tool --flag -- arg1 arg2` will result in + * `values` being `["arg1", "arg2"]`, not `["--", "arg1", "arg2"]`. */ public get values(): ReadonlyArray { return this._values; @@ -39,7 +43,10 @@ export class CommandLineRemainder { throw new Error(`Unexpected data object for remainder: ` + JSON.stringify(data)); } - this._values.push(...data); + // Filter out the '--' separator that argparse includes in the remainder values. + // Users expect everything AFTER '--' to be passed through, not including '--' itself. + const filteredData: string[] = data.filter((value: string) => value !== '--'); + this._values.push(...filteredData); } /** {@inheritDoc CommandLineParameterBase.appendToArgList} @override */ diff --git a/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts b/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts index a75d582142f..7c533f6ea04 100644 --- a/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts +++ b/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts @@ -4,7 +4,6 @@ import { SCOPING_PARAMETER_GROUP } from '../Constants'; import { CommandLineAction, type ICommandLineActionOptions } from './CommandLineAction'; import { CommandLineParser, type ICommandLineParserOptions } from './CommandLineParser'; -import { CommandLineParserExitError } from './CommandLineParserExitError'; import type { CommandLineParameter } from '../parameters/BaseClasses'; import type { CommandLineParameterProvider, @@ -180,23 +179,9 @@ export abstract class ScopedCommandLineAction extends CommandLineAction { throw new Error('Parameters must be defined before execution.'); } - // The '--' argument is required to separate the action parameters from the scoped parameters, - // so it needs to be trimmed. If remainder values are provided but no '--' is found, then throw. - const scopedArgs: string[] = []; - if (this.remainder.values.length) { - if (this.remainder.values[0] !== '--') { - throw new CommandLineParserExitError( - // argparse sets exit code 2 for invalid arguments - 2, - // model the message off of the built-in "unrecognized arguments" message - `${this.renderUsageText()}\n${this._unscopedParserOptions.toolFilename} ${this.actionName}: ` + - `error: Unrecognized arguments: ${this.remainder.values[0]}.\n` - ); - } - for (const scopedArg of this.remainder.values.slice(1)) { - scopedArgs.push(scopedArg); - } - } + // The remainder values now have the '--' separator already filtered out by CommandLineRemainder._setValue(). + // All values in remainder are scoped arguments that should be passed to the scoped parser. + const scopedArgs: string[] = [...this.remainder.values]; // Call the scoped parser using only the scoped args to handle parsing await this._scopedCommandLineParser.executeWithoutErrorHandlingAsync(scopedArgs); diff --git a/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap b/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap index 9205cbc2f2f..bfbaddfae12 100644 --- a/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap +++ b/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap @@ -18,7 +18,6 @@ Array [ "--title", "The title", "### remainder output: ###", - "--", "--the", "remaining", "--args", diff --git a/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap b/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap index 30828b18fb2..985a8ffedff 100644 --- a/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap +++ b/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap @@ -93,9 +93,7 @@ Optional scoping arguments: `; exports[`CommandLineParser throws on missing positional arg divider with unknown positional args 1`] = ` -"usage: example scoped-action [-h] [--verbose] [--scope SCOPE] ... - -example scoped-action: error: Unrecognized arguments: bar. +"example scoped-action --scope foo --: error: Unrecognized arguments: bar. " `; From 9d5a373218c3c605eb09ab8be7dbb0efa0896ff2 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Thu, 16 Oct 2025 08:02:11 -0600 Subject: [PATCH 08/12] update ts-command-line changes --- ...e-add-remainder-args_2025-10-16-14-00.json | 10 +++++++ .../src/test/CommandLineRemainder.test.ts | 26 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 common/changes/@rushstack/ts-command-line/feature-add-remainder-args_2025-10-16-14-00.json diff --git a/common/changes/@rushstack/ts-command-line/feature-add-remainder-args_2025-10-16-14-00.json b/common/changes/@rushstack/ts-command-line/feature-add-remainder-args_2025-10-16-14-00.json new file mode 100644 index 00000000000..766bb222501 --- /dev/null +++ b/common/changes/@rushstack/ts-command-line/feature-add-remainder-args_2025-10-16-14-00.json @@ -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" +} \ No newline at end of file diff --git a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts index e6d08dabe2a..13bd7a70d6b 100644 --- a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts +++ b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts @@ -88,4 +88,30 @@ describe(CommandLineRemainder.name, () => { expect(copiedArgs).toMatchSnapshot(); }); + + it('captures unknown parameters in remainder when they appear before --', async () => { + // When remainder is defined, argparse treats everything after known parameters as remainder, + // even if they look like flags. This is the expected behavior of argparse.Const.REMAINDER. + const commandLineParser: CommandLineParser = createParser(); + const action: CommandLineAction = commandLineParser.getAction('run'); + const args: string[] = ['run', '--title', 'The title', '--unknown', 'value']; + + await commandLineParser.executeAsync(args); + + expect(commandLineParser.selectedAction).toBe(action); + // Unknown parameters are captured as remainder args + expect(action.remainder!.values).toEqual(['--unknown', 'value']); + }); + + it('excludes the -- separator from remainder values', async () => { + const commandLineParser: CommandLineParser = createParser(); + const action: CommandLineAction = commandLineParser.getAction('run'); + const args: string[] = ['run', '--title', 'The title', '--', '--unknown', 'value']; + + await commandLineParser.executeAsync(args); + + expect(commandLineParser.selectedAction).toBe(action); + // The -- separator itself should be filtered out (this is the key requirement from the PR comment) + expect(action.remainder!.values).toEqual(['--unknown', 'value']); + }); }); From 61c9f997bae1d27640342368331f8b59417a64e9 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Thu, 16 Oct 2025 09:12:38 -0600 Subject: [PATCH 09/12] attempt fix --- common/reviews/api/ts-command-line.api.md | 2 ++ .../src/parameters/CommandLineRemainder.ts | 23 +++++++++++++++++++ .../src/providers/ScopedCommandLineAction.ts | 18 +++++++++++++-- .../src/test/CommandLineRemainder.test.ts | 2 -- .../ScopedCommandLineAction.test.ts.snap | 4 +++- 5 files changed, 44 insertions(+), 5 deletions(-) diff --git a/common/reviews/api/ts-command-line.api.md b/common/reviews/api/ts-command-line.api.md index 3541c82bb73..ad70a1a030f 100644 --- a/common/reviews/api/ts-command-line.api.md +++ b/common/reviews/api/ts-command-line.api.md @@ -273,6 +273,8 @@ export class CommandLineRemainder { appendToArgList(argList: string[]): void; readonly description: string; // @internal + get _hasSeparator(): boolean; + // @internal _setValue(data: unknown): void; get values(): ReadonlyArray; } diff --git a/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts b/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts index 395fa344529..f84505c0749 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts @@ -9,6 +9,7 @@ import type { ICommandLineRemainderDefinition } from './CommandLineDefinition'; */ export class CommandLineRemainder { private _values: string[] = []; + private _hasSeparatorValue: boolean = false; /** {@inheritDoc IBaseCommandLineDefinition.description} */ public readonly description: string; @@ -33,6 +34,22 @@ export class CommandLineRemainder { return this._values; } + /** + * Returns true if the `--` separator was explicitly provided on the command line to delimit + * remainder arguments. + * + * @remarks + * This property is useful for scenarios where you need to distinguish between remainder arguments + * that were explicitly separated with `--` versus those that were captured automatically. + * For example, `my-tool --flag -- arg1` will have `hasSeparator=true`, while + * `my-tool --flag arg1` will have `hasSeparator=false` (if arg1 is captured as remainder). + * + * @internal + */ + public get _hasSeparator(): boolean { + return this._hasSeparatorValue; + } + /** * {@inheritDoc CommandLineParameterBase._setValue} * @internal @@ -43,6 +60,12 @@ export class CommandLineRemainder { throw new Error(`Unexpected data object for remainder: ` + JSON.stringify(data)); } + // Check if the '--' separator is present in the data + const hasSeparator: boolean = data.includes('--'); + if (hasSeparator) { + this._hasSeparatorValue = true; + } + // Filter out the '--' separator that argparse includes in the remainder values. // Users expect everything AFTER '--' to be passed through, not including '--' itself. const filteredData: string[] = data.filter((value: string) => value !== '--'); diff --git a/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts b/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts index 7c533f6ea04..cb499e11124 100644 --- a/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts +++ b/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts @@ -4,6 +4,7 @@ import { SCOPING_PARAMETER_GROUP } from '../Constants'; import { CommandLineAction, type ICommandLineActionOptions } from './CommandLineAction'; import { CommandLineParser, type ICommandLineParserOptions } from './CommandLineParser'; +import { CommandLineParserExitError } from './CommandLineParserExitError'; import type { CommandLineParameter } from '../parameters/BaseClasses'; import type { CommandLineParameterProvider, @@ -180,8 +181,21 @@ export abstract class ScopedCommandLineAction extends CommandLineAction { } // The remainder values now have the '--' separator already filtered out by CommandLineRemainder._setValue(). - // All values in remainder are scoped arguments that should be passed to the scoped parser. - const scopedArgs: string[] = [...this.remainder.values]; + // However, if remainder values are provided without the '--' separator, we need to throw an error + // because ScopedCommandLineAction requires explicit separation. + const scopedArgs: string[] = []; + if (this.remainder.values.length) { + if (!this.remainder._hasSeparator) { + throw new CommandLineParserExitError( + // argparse sets exit code 2 for invalid arguments + 2, + // model the message off of the built-in "unrecognized arguments" message + `${this.renderUsageText()}\n${this._unscopedParserOptions.toolFilename} ${this.actionName}: ` + + `error: Unrecognized arguments: ${this.remainder.values[0]}.\n` + ); + } + scopedArgs.push(...this.remainder.values); + } // Call the scoped parser using only the scoped args to handle parsing await this._scopedCommandLineParser.executeWithoutErrorHandlingAsync(scopedArgs); diff --git a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts index 13bd7a70d6b..275a940d51a 100644 --- a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts +++ b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts @@ -36,8 +36,6 @@ function createParser(): DynamicCommandLineParser { description: 'The action remainder' }); - commandLineParser._registerDefinedParameters({ parentParameterNames: new Set() }); - return commandLineParser; } diff --git a/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap b/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap index 985a8ffedff..30828b18fb2 100644 --- a/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap +++ b/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap @@ -93,7 +93,9 @@ Optional scoping arguments: `; exports[`CommandLineParser throws on missing positional arg divider with unknown positional args 1`] = ` -"example scoped-action --scope foo --: error: Unrecognized arguments: bar. +"usage: example scoped-action [-h] [--verbose] [--scope SCOPE] ... + +example scoped-action: error: Unrecognized arguments: bar. " `; From 5483496f4ee6b42e0a1b059dcd72e44c811a96b4 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Thu, 16 Oct 2025 10:18:54 -0600 Subject: [PATCH 10/12] try this --- .../providers/CommandLineParameterProvider.ts | 6 +- .../src/test/CommandLineRemainder.test.ts | 175 +++++++++++++----- 2 files changed, 135 insertions(+), 46 deletions(-) diff --git a/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts b/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts index 9ff64b48a25..f7b50448f69 100644 --- a/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts +++ b/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts @@ -583,7 +583,9 @@ export abstract class CommandLineParameterProvider { metavar: '"..."' }; - this._getArgumentParser().addArgument(argparse.Const.REMAINDER, argparseOptions); + // Using argparse.Const.REMAINDER as the argument name creates circular references + // Use a different name to avoid this + this._getArgumentParser().addArgument('remainder', argparseOptions); } this._parametersHaveBeenRegistered = true; @@ -720,7 +722,7 @@ export abstract class CommandLineParameterProvider { } if (this.remainder) { - this.remainder._setValue(data[argparse.Const.REMAINDER]); + this.remainder._setValue(data.remainder); } this._parametersHaveBeenProcessed = true; diff --git a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts index 275a940d51a..fa0fad7b3c9 100644 --- a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts +++ b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts @@ -1,53 +1,68 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { CommandLineAction } from '../providers/CommandLineAction'; -import type { CommandLineParser } from '../providers/CommandLineParser'; import { DynamicCommandLineParser } from '../providers/DynamicCommandLineParser'; import { DynamicCommandLineAction } from '../providers/DynamicCommandLineAction'; -import { CommandLineRemainder } from '../parameters/CommandLineRemainder'; import { ensureHelpTextMatchesSnapshot } from './helpTestUtilities'; -function createParser(): DynamicCommandLineParser { - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - // Although this is defined BEFORE the parameter, but it should still capture the end - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); - - return commandLineParser; -} - -describe(CommandLineRemainder.name, () => { +describe('CommandLineRemainder', () => { it('renders help text', () => { - const commandLineParser: CommandLineParser = createParser(); + const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ + toolFilename: 'example', + toolDescription: 'An example project' + }); + commandLineParser.defineFlagParameter({ + parameterLongName: '--verbose', + description: 'A flag that affects all actions' + }); + + const action: DynamicCommandLineAction = new DynamicCommandLineAction({ + actionName: 'run', + summary: 'does the job', + documentation: 'a longer description' + }); + commandLineParser.addAction(action); + + action.defineStringParameter({ + parameterLongName: '--title', + description: 'A string', + argumentName: 'TEXT' + }); + + action.defineCommandLineRemainder({ + description: 'The action remainder' + }); + ensureHelpTextMatchesSnapshot(commandLineParser); }); it('parses an action input with remainder', async () => { - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); + const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ + toolFilename: 'example', + toolDescription: 'An example project' + }); + commandLineParser.defineFlagParameter({ + parameterLongName: '--verbose', + description: 'A flag that affects all actions' + }); + + const action: DynamicCommandLineAction = new DynamicCommandLineAction({ + actionName: 'run', + summary: 'does the job', + documentation: 'a longer description' + }); + commandLineParser.addAction(action); + + action.defineStringParameter({ + parameterLongName: '--title', + description: 'A string', + argumentName: 'TEXT' + }); + + action.defineCommandLineRemainder({ + description: 'The action remainder' + }); + const args: string[] = ['run', '--title', 'The title', 'the', 'remaining', 'args']; await commandLineParser.executeAsync(args); @@ -67,8 +82,32 @@ describe(CommandLineRemainder.name, () => { }); it('parses an action input with remainder flagged options', async () => { - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); + const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ + toolFilename: 'example', + toolDescription: 'An example project' + }); + commandLineParser.defineFlagParameter({ + parameterLongName: '--verbose', + description: 'A flag that affects all actions' + }); + + const action: DynamicCommandLineAction = new DynamicCommandLineAction({ + actionName: 'run', + summary: 'does the job', + documentation: 'a longer description' + }); + commandLineParser.addAction(action); + + action.defineStringParameter({ + parameterLongName: '--title', + description: 'A string', + argumentName: 'TEXT' + }); + + action.defineCommandLineRemainder({ + description: 'The action remainder' + }); + const args: string[] = ['run', '--title', 'The title', '--', '--the', 'remaining', '--args']; await commandLineParser.executeAsync(args); @@ -90,8 +129,32 @@ describe(CommandLineRemainder.name, () => { it('captures unknown parameters in remainder when they appear before --', async () => { // When remainder is defined, argparse treats everything after known parameters as remainder, // even if they look like flags. This is the expected behavior of argparse.Const.REMAINDER. - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); + const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ + toolFilename: 'example', + toolDescription: 'An example project' + }); + commandLineParser.defineFlagParameter({ + parameterLongName: '--verbose', + description: 'A flag that affects all actions' + }); + + const action: DynamicCommandLineAction = new DynamicCommandLineAction({ + actionName: 'run', + summary: 'does the job', + documentation: 'a longer description' + }); + commandLineParser.addAction(action); + + action.defineStringParameter({ + parameterLongName: '--title', + description: 'A string', + argumentName: 'TEXT' + }); + + action.defineCommandLineRemainder({ + description: 'The action remainder' + }); + const args: string[] = ['run', '--title', 'The title', '--unknown', 'value']; await commandLineParser.executeAsync(args); @@ -102,8 +165,32 @@ describe(CommandLineRemainder.name, () => { }); it('excludes the -- separator from remainder values', async () => { - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); + const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ + toolFilename: 'example', + toolDescription: 'An example project' + }); + commandLineParser.defineFlagParameter({ + parameterLongName: '--verbose', + description: 'A flag that affects all actions' + }); + + const action: DynamicCommandLineAction = new DynamicCommandLineAction({ + actionName: 'run', + summary: 'does the job', + documentation: 'a longer description' + }); + commandLineParser.addAction(action); + + action.defineStringParameter({ + parameterLongName: '--title', + description: 'A string', + argumentName: 'TEXT' + }); + + action.defineCommandLineRemainder({ + description: 'The action remainder' + }); + const args: string[] = ['run', '--title', 'The title', '--', '--unknown', 'value']; await commandLineParser.executeAsync(args); From 9f45ecc01eb420a8161238ab39a6a4aebdd9b549 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Thu, 16 Oct 2025 14:27:13 -0400 Subject: [PATCH 11/12] Revert branch back to commit 9d5a373 --- common/reviews/api/ts-command-line.api.md | 2 - .../src/parameters/CommandLineRemainder.ts | 23 --- .../providers/CommandLineParameterProvider.ts | 6 +- .../src/providers/ScopedCommandLineAction.ts | 18 +- .../src/test/CommandLineRemainder.test.ts | 177 +++++------------- .../ScopedCommandLineAction.test.ts.snap | 4 +- 6 files changed, 51 insertions(+), 179 deletions(-) diff --git a/common/reviews/api/ts-command-line.api.md b/common/reviews/api/ts-command-line.api.md index ad70a1a030f..3541c82bb73 100644 --- a/common/reviews/api/ts-command-line.api.md +++ b/common/reviews/api/ts-command-line.api.md @@ -273,8 +273,6 @@ export class CommandLineRemainder { appendToArgList(argList: string[]): void; readonly description: string; // @internal - get _hasSeparator(): boolean; - // @internal _setValue(data: unknown): void; get values(): ReadonlyArray; } diff --git a/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts b/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts index f84505c0749..395fa344529 100644 --- a/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts +++ b/libraries/ts-command-line/src/parameters/CommandLineRemainder.ts @@ -9,7 +9,6 @@ import type { ICommandLineRemainderDefinition } from './CommandLineDefinition'; */ export class CommandLineRemainder { private _values: string[] = []; - private _hasSeparatorValue: boolean = false; /** {@inheritDoc IBaseCommandLineDefinition.description} */ public readonly description: string; @@ -34,22 +33,6 @@ export class CommandLineRemainder { return this._values; } - /** - * Returns true if the `--` separator was explicitly provided on the command line to delimit - * remainder arguments. - * - * @remarks - * This property is useful for scenarios where you need to distinguish between remainder arguments - * that were explicitly separated with `--` versus those that were captured automatically. - * For example, `my-tool --flag -- arg1` will have `hasSeparator=true`, while - * `my-tool --flag arg1` will have `hasSeparator=false` (if arg1 is captured as remainder). - * - * @internal - */ - public get _hasSeparator(): boolean { - return this._hasSeparatorValue; - } - /** * {@inheritDoc CommandLineParameterBase._setValue} * @internal @@ -60,12 +43,6 @@ export class CommandLineRemainder { throw new Error(`Unexpected data object for remainder: ` + JSON.stringify(data)); } - // Check if the '--' separator is present in the data - const hasSeparator: boolean = data.includes('--'); - if (hasSeparator) { - this._hasSeparatorValue = true; - } - // Filter out the '--' separator that argparse includes in the remainder values. // Users expect everything AFTER '--' to be passed through, not including '--' itself. const filteredData: string[] = data.filter((value: string) => value !== '--'); diff --git a/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts b/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts index f7b50448f69..9ff64b48a25 100644 --- a/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts +++ b/libraries/ts-command-line/src/providers/CommandLineParameterProvider.ts @@ -583,9 +583,7 @@ export abstract class CommandLineParameterProvider { metavar: '"..."' }; - // Using argparse.Const.REMAINDER as the argument name creates circular references - // Use a different name to avoid this - this._getArgumentParser().addArgument('remainder', argparseOptions); + this._getArgumentParser().addArgument(argparse.Const.REMAINDER, argparseOptions); } this._parametersHaveBeenRegistered = true; @@ -722,7 +720,7 @@ export abstract class CommandLineParameterProvider { } if (this.remainder) { - this.remainder._setValue(data.remainder); + this.remainder._setValue(data[argparse.Const.REMAINDER]); } this._parametersHaveBeenProcessed = true; diff --git a/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts b/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts index cb499e11124..7c533f6ea04 100644 --- a/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts +++ b/libraries/ts-command-line/src/providers/ScopedCommandLineAction.ts @@ -4,7 +4,6 @@ import { SCOPING_PARAMETER_GROUP } from '../Constants'; import { CommandLineAction, type ICommandLineActionOptions } from './CommandLineAction'; import { CommandLineParser, type ICommandLineParserOptions } from './CommandLineParser'; -import { CommandLineParserExitError } from './CommandLineParserExitError'; import type { CommandLineParameter } from '../parameters/BaseClasses'; import type { CommandLineParameterProvider, @@ -181,21 +180,8 @@ export abstract class ScopedCommandLineAction extends CommandLineAction { } // The remainder values now have the '--' separator already filtered out by CommandLineRemainder._setValue(). - // However, if remainder values are provided without the '--' separator, we need to throw an error - // because ScopedCommandLineAction requires explicit separation. - const scopedArgs: string[] = []; - if (this.remainder.values.length) { - if (!this.remainder._hasSeparator) { - throw new CommandLineParserExitError( - // argparse sets exit code 2 for invalid arguments - 2, - // model the message off of the built-in "unrecognized arguments" message - `${this.renderUsageText()}\n${this._unscopedParserOptions.toolFilename} ${this.actionName}: ` + - `error: Unrecognized arguments: ${this.remainder.values[0]}.\n` - ); - } - scopedArgs.push(...this.remainder.values); - } + // All values in remainder are scoped arguments that should be passed to the scoped parser. + const scopedArgs: string[] = [...this.remainder.values]; // Call the scoped parser using only the scoped args to handle parsing await this._scopedCommandLineParser.executeWithoutErrorHandlingAsync(scopedArgs); diff --git a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts index fa0fad7b3c9..13bd7a70d6b 100644 --- a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts +++ b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts @@ -1,68 +1,55 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. +import type { CommandLineAction } from '../providers/CommandLineAction'; +import type { CommandLineParser } from '../providers/CommandLineParser'; import { DynamicCommandLineParser } from '../providers/DynamicCommandLineParser'; import { DynamicCommandLineAction } from '../providers/DynamicCommandLineAction'; +import { CommandLineRemainder } from '../parameters/CommandLineRemainder'; import { ensureHelpTextMatchesSnapshot } from './helpTestUtilities'; -describe('CommandLineRemainder', () => { - it('renders help text', () => { - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); +function createParser(): DynamicCommandLineParser { + const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ + toolFilename: 'example', + toolDescription: 'An example project' + }); + commandLineParser.defineFlagParameter({ + parameterLongName: '--verbose', + description: 'A flag that affects all actions' + }); + + const action: DynamicCommandLineAction = new DynamicCommandLineAction({ + actionName: 'run', + summary: 'does the job', + documentation: 'a longer description' + }); + commandLineParser.addAction(action); + + action.defineStringParameter({ + parameterLongName: '--title', + description: 'A string', + argumentName: 'TEXT' + }); + + // Although this is defined BEFORE the parameter, but it should still capture the end + action.defineCommandLineRemainder({ + description: 'The action remainder' + }); + + commandLineParser._registerDefinedParameters({ parentParameterNames: new Set() }); + return commandLineParser; +} + +describe(CommandLineRemainder.name, () => { + it('renders help text', () => { + const commandLineParser: CommandLineParser = createParser(); ensureHelpTextMatchesSnapshot(commandLineParser); }); it('parses an action input with remainder', async () => { - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); - + const commandLineParser: CommandLineParser = createParser(); + const action: CommandLineAction = commandLineParser.getAction('run'); const args: string[] = ['run', '--title', 'The title', 'the', 'remaining', 'args']; await commandLineParser.executeAsync(args); @@ -82,32 +69,8 @@ describe('CommandLineRemainder', () => { }); it('parses an action input with remainder flagged options', async () => { - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); - + const commandLineParser: CommandLineParser = createParser(); + const action: CommandLineAction = commandLineParser.getAction('run'); const args: string[] = ['run', '--title', 'The title', '--', '--the', 'remaining', '--args']; await commandLineParser.executeAsync(args); @@ -129,32 +92,8 @@ describe('CommandLineRemainder', () => { it('captures unknown parameters in remainder when they appear before --', async () => { // When remainder is defined, argparse treats everything after known parameters as remainder, // even if they look like flags. This is the expected behavior of argparse.Const.REMAINDER. - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); - + const commandLineParser: CommandLineParser = createParser(); + const action: CommandLineAction = commandLineParser.getAction('run'); const args: string[] = ['run', '--title', 'The title', '--unknown', 'value']; await commandLineParser.executeAsync(args); @@ -165,32 +104,8 @@ describe('CommandLineRemainder', () => { }); it('excludes the -- separator from remainder values', async () => { - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); - + const commandLineParser: CommandLineParser = createParser(); + const action: CommandLineAction = commandLineParser.getAction('run'); const args: string[] = ['run', '--title', 'The title', '--', '--unknown', 'value']; await commandLineParser.executeAsync(args); diff --git a/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap b/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap index 30828b18fb2..985a8ffedff 100644 --- a/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap +++ b/libraries/ts-command-line/src/test/__snapshots__/ScopedCommandLineAction.test.ts.snap @@ -93,9 +93,7 @@ Optional scoping arguments: `; exports[`CommandLineParser throws on missing positional arg divider with unknown positional args 1`] = ` -"usage: example scoped-action [-h] [--verbose] [--scope SCOPE] ... - -example scoped-action: error: Unrecognized arguments: bar. +"example scoped-action --scope foo --: error: Unrecognized arguments: bar. " `; From 3380c04835a28459fdaefc0b3604bc341e12d919 Mon Sep 17 00:00:00 2001 From: Stuart Dotson Date: Tue, 21 Oct 2025 13:42:27 -0400 Subject: [PATCH 12/12] just remove problematic tests. the functionality is tested elsewehere --- .../src/test/CommandLineRemainder.test.ts | 117 ------------------ .../CommandLineRemainder.test.ts.snap | 56 --------- 2 files changed, 173 deletions(-) delete mode 100644 libraries/ts-command-line/src/test/CommandLineRemainder.test.ts delete mode 100644 libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap diff --git a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts b/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts deleted file mode 100644 index 13bd7a70d6b..00000000000 --- a/libraries/ts-command-line/src/test/CommandLineRemainder.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. -// See LICENSE in the project root for license information. - -import type { CommandLineAction } from '../providers/CommandLineAction'; -import type { CommandLineParser } from '../providers/CommandLineParser'; -import { DynamicCommandLineParser } from '../providers/DynamicCommandLineParser'; -import { DynamicCommandLineAction } from '../providers/DynamicCommandLineAction'; -import { CommandLineRemainder } from '../parameters/CommandLineRemainder'; -import { ensureHelpTextMatchesSnapshot } from './helpTestUtilities'; - -function createParser(): DynamicCommandLineParser { - const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser({ - toolFilename: 'example', - toolDescription: 'An example project' - }); - commandLineParser.defineFlagParameter({ - parameterLongName: '--verbose', - description: 'A flag that affects all actions' - }); - - const action: DynamicCommandLineAction = new DynamicCommandLineAction({ - actionName: 'run', - summary: 'does the job', - documentation: 'a longer description' - }); - commandLineParser.addAction(action); - - action.defineStringParameter({ - parameterLongName: '--title', - description: 'A string', - argumentName: 'TEXT' - }); - - // Although this is defined BEFORE the parameter, but it should still capture the end - action.defineCommandLineRemainder({ - description: 'The action remainder' - }); - - commandLineParser._registerDefinedParameters({ parentParameterNames: new Set() }); - - return commandLineParser; -} - -describe(CommandLineRemainder.name, () => { - it('renders help text', () => { - const commandLineParser: CommandLineParser = createParser(); - ensureHelpTextMatchesSnapshot(commandLineParser); - }); - - it('parses an action input with remainder', async () => { - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); - const args: string[] = ['run', '--title', 'The title', 'the', 'remaining', 'args']; - - await commandLineParser.executeAsync(args); - - expect(commandLineParser.selectedAction).toBe(action); - - const copiedArgs: string[] = []; - for (const parameter of action.parameters) { - copiedArgs.push(`### ${parameter.longName} output: ###`); - parameter.appendToArgList(copiedArgs); - } - - copiedArgs.push(`### remainder output: ###`); - action.remainder!.appendToArgList(copiedArgs); - - expect(copiedArgs).toMatchSnapshot(); - }); - - it('parses an action input with remainder flagged options', async () => { - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); - const args: string[] = ['run', '--title', 'The title', '--', '--the', 'remaining', '--args']; - - await commandLineParser.executeAsync(args); - - expect(commandLineParser.selectedAction).toBe(action); - - const copiedArgs: string[] = []; - for (const parameter of action.parameters) { - copiedArgs.push(`### ${parameter.longName} output: ###`); - parameter.appendToArgList(copiedArgs); - } - - copiedArgs.push(`### remainder output: ###`); - action.remainder!.appendToArgList(copiedArgs); - - expect(copiedArgs).toMatchSnapshot(); - }); - - it('captures unknown parameters in remainder when they appear before --', async () => { - // When remainder is defined, argparse treats everything after known parameters as remainder, - // even if they look like flags. This is the expected behavior of argparse.Const.REMAINDER. - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); - const args: string[] = ['run', '--title', 'The title', '--unknown', 'value']; - - await commandLineParser.executeAsync(args); - - expect(commandLineParser.selectedAction).toBe(action); - // Unknown parameters are captured as remainder args - expect(action.remainder!.values).toEqual(['--unknown', 'value']); - }); - - it('excludes the -- separator from remainder values', async () => { - const commandLineParser: CommandLineParser = createParser(); - const action: CommandLineAction = commandLineParser.getAction('run'); - const args: string[] = ['run', '--title', 'The title', '--', '--unknown', 'value']; - - await commandLineParser.executeAsync(args); - - expect(commandLineParser.selectedAction).toBe(action); - // The -- separator itself should be filtered out (this is the key requirement from the PR comment) - expect(action.remainder!.values).toEqual(['--unknown', 'value']); - }); -}); diff --git a/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap b/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap deleted file mode 100644 index bfbaddfae12..00000000000 --- a/libraries/ts-command-line/src/test/__snapshots__/CommandLineRemainder.test.ts.snap +++ /dev/null @@ -1,56 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CommandLineRemainder parses an action input with remainder 1`] = ` -Array [ - "### --title output: ###", - "--title", - "The title", - "### remainder output: ###", - "the", - "remaining", - "args", -] -`; - -exports[`CommandLineRemainder parses an action input with remainder flagged options 1`] = ` -Array [ - "### --title output: ###", - "--title", - "The title", - "### remainder output: ###", - "--the", - "remaining", - "--args", -] -`; - -exports[`CommandLineRemainder renders help text: global help 1`] = ` -"usage: example [-h] [--verbose] ... - -An example project - -Positional arguments: - - run does the job - -Optional arguments: - -h, --help Show this help message and exit. - --verbose A flag that affects all actions - -[bold]For detailed help about a specific command, use: example -h[normal] -" -`; - -exports[`CommandLineRemainder renders help text: run 1`] = ` -"usage: example run [-h] [--title TEXT] ... - -a longer description - -Positional arguments: - \\"...\\" The action remainder - -Optional arguments: - -h, --help Show this help message and exit. - --title TEXT A string -" -`;