From 4d64f0db906d8bedd5f21efc7cd652032008cdaa Mon Sep 17 00:00:00 2001 From: Musa Toktas Date: Wed, 7 Oct 2026 14:56:12 +0400 Subject: [PATCH] [rush] Do not print Rush alerts when --json or --quiet is specified Rush alerts are written to stdout after commands such as "rush list". When the experiment is enabled, "rush list --json | jq" therefore received the alert banner after the JSON document. Skip the alerts when RushCommandLineParser.shouldRestrictConsoleOutput() is true. --- ...fix-5228-alerts-quiet-json_2026-10-07.json | 9 +++ .../rush-lib/src/cli/RushCommandLineParser.ts | 4 +- .../test/RushCommandLineParserAlerts.test.ts | 55 +++++++++++++++++++ .../cli/test/rushAlertsRepo/a/package.json | 4 ++ .../common/config/rush/experiments.json | 1 + .../src/cli/test/rushAlertsRepo/rush.json | 10 ++++ 6 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 common/changes/@microsoft/rush/fix-5228-alerts-quiet-json_2026-10-07.json create mode 100644 libraries/rush-lib/src/cli/test/RushCommandLineParserAlerts.test.ts create mode 100644 libraries/rush-lib/src/cli/test/rushAlertsRepo/a/package.json create mode 100644 libraries/rush-lib/src/cli/test/rushAlertsRepo/common/config/rush/experiments.json create mode 100644 libraries/rush-lib/src/cli/test/rushAlertsRepo/rush.json diff --git a/common/changes/@microsoft/rush/fix-5228-alerts-quiet-json_2026-10-07.json b/common/changes/@microsoft/rush/fix-5228-alerts-quiet-json_2026-10-07.json new file mode 100644 index 00000000000..4e7b3611453 --- /dev/null +++ b/common/changes/@microsoft/rush/fix-5228-alerts-quiet-json_2026-10-07.json @@ -0,0 +1,9 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Do not print Rush alerts when console output is restricted by \"--quiet\" or \"--json\", so that commands such as \"rush list --json\" write only JSON to stdout.", + "type": "patch" + } + ] +} diff --git a/libraries/rush-lib/src/cli/RushCommandLineParser.ts b/libraries/rush-lib/src/cli/RushCommandLineParser.ts index f9b5fdce416..6cd561ab9aa 100644 --- a/libraries/rush-lib/src/cli/RushCommandLineParser.ts +++ b/libraries/rush-lib/src/cli/RushCommandLineParser.ts @@ -490,7 +490,9 @@ export class RushCommandLineParser extends CommandLineParser { try { const { configuration: experiments } = this.rushConfiguration.experimentsConfiguration; - if (experiments.rushAlerts) { + // Alerts are written to stdout, so they must not be printed when console output is restricted + // (for example "--json" or "--quiet"), or they would corrupt machine-readable output. + if (experiments.rushAlerts && !this.#restrictConsoleOutput) { // TODO: Fix this // eslint-disable-next-line @typescript-eslint/no-explicit-any const actionName: string = (this as any) diff --git a/libraries/rush-lib/src/cli/test/RushCommandLineParserAlerts.test.ts b/libraries/rush-lib/src/cli/test/RushCommandLineParserAlerts.test.ts new file mode 100644 index 00000000000..6b27b101a1e --- /dev/null +++ b/libraries/rush-lib/src/cli/test/RushCommandLineParserAlerts.test.ts @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { RushCommandLineParser } from '../RushCommandLineParser'; +import { EnvironmentConfiguration } from '../../api/EnvironmentConfiguration'; +import { RushAlerts } from '../../utilities/RushAlerts'; + +describe('RushCommandLineParser alerts', () => { + const originalArgv: string[] = process.argv; + let originalExitCode: string | number | undefined; + let printAlertsAsync: jest.Mock, []>; + + beforeEach(() => { + originalExitCode = process.exitCode; + process.exitCode = undefined; + printAlertsAsync = jest.fn(async () => undefined); + jest + .spyOn(RushAlerts, 'loadFromConfigurationAsync') + .mockResolvedValue({ printAlertsAsync } as unknown as RushAlerts); + jest.spyOn(console, 'log').mockImplementation(() => undefined); + jest.spyOn(console, 'error').mockImplementation(() => undefined); + }); + + afterEach(() => { + process.exitCode = originalExitCode; + process.argv = originalArgv; + EnvironmentConfiguration.reset(); + jest.restoreAllMocks(); + }); + + async function executeAsync(args: string[]): Promise { + process.argv = ['pretend-this-is-node.exe', 'pretend-this-is-rush', ...args]; + const parser: RushCommandLineParser = new RushCommandLineParser({ + cwd: `${__dirname}/rushAlertsRepo`, + reporterCloseAsync: async () => undefined + }); + await expect(parser.executeAsync(args)).resolves.toBe(true); + } + + it('prints alerts after a command when console output is not restricted', async () => { + await executeAsync(['list']); + + expect(printAlertsAsync).toHaveBeenCalledTimes(1); + }); + + it.each(['list --json', '--quiet list', '-q list'])( + 'does not print alerts for "rush %s"', + async (commandLine) => { + await executeAsync(commandLine.split(' ')); + + expect(RushAlerts.loadFromConfigurationAsync).not.toHaveBeenCalled(); + expect(printAlertsAsync).not.toHaveBeenCalled(); + } + ); +}); diff --git a/libraries/rush-lib/src/cli/test/rushAlertsRepo/a/package.json b/libraries/rush-lib/src/cli/test/rushAlertsRepo/a/package.json new file mode 100644 index 00000000000..9113c2528ed --- /dev/null +++ b/libraries/rush-lib/src/cli/test/rushAlertsRepo/a/package.json @@ -0,0 +1,4 @@ +{ + "name": "a", + "version": "1.0.0" +} diff --git a/libraries/rush-lib/src/cli/test/rushAlertsRepo/common/config/rush/experiments.json b/libraries/rush-lib/src/cli/test/rushAlertsRepo/common/config/rush/experiments.json new file mode 100644 index 00000000000..d4554664bdb --- /dev/null +++ b/libraries/rush-lib/src/cli/test/rushAlertsRepo/common/config/rush/experiments.json @@ -0,0 +1 @@ +{ "rushAlerts": true } diff --git a/libraries/rush-lib/src/cli/test/rushAlertsRepo/rush.json b/libraries/rush-lib/src/cli/test/rushAlertsRepo/rush.json new file mode 100644 index 00000000000..343bc763eb5 --- /dev/null +++ b/libraries/rush-lib/src/cli/test/rushAlertsRepo/rush.json @@ -0,0 +1,10 @@ +{ + "pnpmVersion": "4.5.0", + "rushVersion": "5.0.0", + "projects": [ + { + "packageName": "a", + "projectFolder": "a" + } + ] +}