diff --git a/common/changes/@microsoft/rush/copilot-reporter-r2a-experiment-config_2026-08-28-02-38.json b/common/changes/@microsoft/rush/copilot-reporter-r2a-experiment-config_2026-08-28-02-38.json new file mode 100644 index 0000000000..2130da2c58 --- /dev/null +++ b/common/changes/@microsoft/rush/copilot-reporter-r2a-experiment-config_2026-08-28-02-38.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Add repository configuration for opting into and configuring the experimental Rush reporter.", + "type": "patch" + } + ], + "packageName": "@microsoft/rush", + "email": "TheLarkInn@users.noreply.github.com" +} diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index ea311a232d..278cbccd79 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -497,6 +497,7 @@ export interface IExperimentsJson { usePnpmLockfileOnlyThenFrozenLockfileForRushUpdate?: boolean; usePnpmPreferFrozenLockfileForRushUpdate?: boolean; usePnpmSyncForInjectedDependencies?: boolean; + useRushReporter?: boolean; } // @beta @@ -971,6 +972,11 @@ export interface _IRushProjectJson { operationSettings?: IOperationSettings[]; } +// @beta +export interface IRushReportingConfiguration { + readonly agentEnvironmentVariables: readonly string[]; +} + // @beta (undocumented) export interface IRushSessionOptions { // (undocumented) @@ -1471,6 +1477,8 @@ export class RushConfiguration { get projectsByName(): ReadonlyMap; // @beta get projectsByTag(): ReadonlyMap>; + // @beta + readonly reportingConfiguration: IRushReportingConfiguration; readonly repositoryDefaultBranch: string; get repositoryDefaultFullyQualifiedRemoteBranch(): string; readonly repositoryDefaultRemote: string; diff --git a/libraries/rush-lib/assets/rush-init/common/config/rush/experiments.json b/libraries/rush-lib/assets/rush-init/common/config/rush/experiments.json index 1bead4b105..e89b450b1c 100644 --- a/libraries/rush-lib/assets/rush-init/common/config/rush/experiments.json +++ b/libraries/rush-lib/assets/rush-init/common/config/rush/experiments.json @@ -141,5 +141,11 @@ * must implement the optional file-based methods for this to take effect; otherwise it falls back to the * buffer-based approach. */ - /*[LINE "HYPOTHETICAL"]*/ "useDirectFileTransfersForBuildCache": true + /*[LINE "HYPOTHETICAL"]*/ "useDirectFileTransfersForBuildCache": true, + + /** + * If true, Rush may use the experimental Rush reporter system. If omitted or false, + * Rush preserves the legacy reporting behavior. + */ + /*[LINE "HYPOTHETICAL"]*/ "useRushReporter": true } diff --git a/libraries/rush-lib/assets/rush-init/rush.json b/libraries/rush-lib/assets/rush-init/rush.json index a972877f6d..4cf8209cc2 100644 --- a/libraries/rush-lib/assets/rush-init/rush.json +++ b/libraries/rush-lib/assets/rush-init/rush.json @@ -316,6 +316,19 @@ */ /*[LINE "HYPOTHETICAL"]*/ "telemetryEnabled": false, + /** + * Configures repository settings used by the experimental Rush reporter system. + */ + /*[BEGIN "HYPOTHETICAL"]*/ + "reporting": { + /** + * Additional environment variable names that identify an agent environment. + * The built-in COPILOT_CLI variable does not need to be listed here. + */ + "agentEnvironmentVariables": ["MY_AGENT_CLI", "ANOTHER_AGENT"] + }, + /*[END "HYPOTHETICAL"]*/ + /** * Allows creation of hotfix changes. This feature is experimental so it is disabled by default. * If this is set, 'rush change' only allows a 'hotfix' change type to be specified. This change type diff --git a/libraries/rush-lib/src/api/ExperimentsConfiguration.ts b/libraries/rush-lib/src/api/ExperimentsConfiguration.ts index 0f8db8e9d0..e077575bfa 100644 --- a/libraries/rush-lib/src/api/ExperimentsConfiguration.ts +++ b/libraries/rush-lib/src/api/ExperimentsConfiguration.ts @@ -153,6 +153,12 @@ export interface IExperimentsJson { * effect; otherwise it falls back to the buffer-based approach. */ useDirectFileTransfersForBuildCache?: boolean; + + /** + * If true, Rush may use the experimental Rush reporter system. If omitted or false, + * Rush preserves the legacy reporting behavior. + */ + useRushReporter?: boolean; } const _EXPERIMENTS_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(schemaJson); diff --git a/libraries/rush-lib/src/api/RushConfiguration.ts b/libraries/rush-lib/src/api/RushConfiguration.ts index 527ec92be9..f75dffc795 100644 --- a/libraries/rush-lib/src/api/RushConfiguration.ts +++ b/libraries/rush-lib/src/api/RushConfiguration.ts @@ -154,6 +154,21 @@ export interface IRushVariantOptionsJson { description: string; } +interface IRushReportingConfigurationJson { + agentEnvironmentVariables?: string[]; +} + +/** + * Repository settings used by the Rush reporter system. + * @beta + */ +export interface IRushReportingConfiguration { + /** + * Additional environment variable names that identify an agent environment. + */ + readonly agentEnvironmentVariables: readonly string[]; +} + /** * This represents the JSON data structure for the "rush.json" configuration file. * See rush.schema.json for documentation. @@ -184,6 +199,7 @@ export interface IRushConfigurationJson { yarnOptions?: IYarnOptionsJson; ensureConsistentVersions?: boolean; variants?: IRushVariantOptionsJson[]; + reporting?: IRushReportingConfigurationJson; } /** @@ -523,6 +539,12 @@ export class RushConfiguration { */ public readonly telemetryEnabled: boolean; + /** + * Repository settings used by the Rush reporter system. + * @beta + */ + public readonly reportingConfiguration: IRushReportingConfiguration; + /** * {@inheritDoc NpmOptionsConfiguration} */ @@ -853,6 +875,9 @@ export class RushConfiguration { } this.telemetryEnabled = !!rushConfigurationJson.telemetryEnabled; + this.reportingConfiguration = { + agentEnvironmentVariables: rushConfigurationJson.reporting?.agentEnvironmentVariables || [] + }; this.eventHooks = new EventHooks(rushConfigurationJson.eventHooks || {}); this.versionPolicyConfigurationFilePath = path.join( diff --git a/libraries/rush-lib/src/api/test/ExperimentsConfiguration.test.ts b/libraries/rush-lib/src/api/test/ExperimentsConfiguration.test.ts new file mode 100644 index 0000000000..dfe9526cab --- /dev/null +++ b/libraries/rush-lib/src/api/test/ExperimentsConfiguration.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 * as path from 'node:path'; + +import { FileSystem, JsonFile } from '@rushstack/node-core-library'; + +import { ExperimentsConfiguration } from '../ExperimentsConfiguration'; + +const TEMP_FOLDER: string = path.join(__dirname, 'temp', ExperimentsConfiguration.name); +const EXPERIMENTS_JSON_PATH: string = path.join(TEMP_FOLDER, 'experiments.json'); + +describe(ExperimentsConfiguration.name, () => { + beforeEach(() => { + FileSystem.ensureEmptyFolder(TEMP_FOLDER); + }); + + afterEach(() => { + FileSystem.ensureEmptyFolder(TEMP_FOLDER); + }); + + it('preserves legacy reporting behavior when the experiment file is absent', () => { + const experimentsConfiguration: ExperimentsConfiguration = new ExperimentsConfiguration( + EXPERIMENTS_JSON_PATH + ); + + expect(experimentsConfiguration.configuration.useRushReporter).toBeUndefined(); + }); + + it('loads the Rush reporter opt-in', () => { + JsonFile.save({ useRushReporter: true }, EXPERIMENTS_JSON_PATH); + + const experimentsConfiguration: ExperimentsConfiguration = new ExperimentsConfiguration( + EXPERIMENTS_JSON_PATH + ); + + expect(experimentsConfiguration.configuration.useRushReporter).toBe(true); + }); + + it('keeps an explicit false value disabled', () => { + JsonFile.save({ useRushReporter: false }, EXPERIMENTS_JSON_PATH); + + const experimentsConfiguration: ExperimentsConfiguration = new ExperimentsConfiguration( + EXPERIMENTS_JSON_PATH + ); + + expect(experimentsConfiguration.configuration.useRushReporter).toBe(false); + }); + + it('rejects a non-boolean Rush reporter opt-in', () => { + JsonFile.save({ useRushReporter: 'yes' }, EXPERIMENTS_JSON_PATH); + + expect(() => new ExperimentsConfiguration(EXPERIMENTS_JSON_PATH)).toThrow(/useRushReporter/); + }); +}); diff --git a/libraries/rush-lib/src/api/test/RushConfigurationReporting.test.ts b/libraries/rush-lib/src/api/test/RushConfigurationReporting.test.ts new file mode 100644 index 0000000000..a188189023 --- /dev/null +++ b/libraries/rush-lib/src/api/test/RushConfigurationReporting.test.ts @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as path from 'node:path'; + +import { FileSystem, JsonFile } from '@rushstack/node-core-library'; + +import { Rush } from '../Rush'; +import { RushConfiguration } from '../RushConfiguration'; + +const TEMP_FOLDER: string = path.join(__dirname, 'temp', 'RushConfigurationReporting'); +const RUSH_JSON_PATH: string = path.join(TEMP_FOLDER, 'rush.json'); + +function writeRushJson(reporting?: unknown): void { + JsonFile.save( + { + rushVersion: Rush.version, + pnpmVersion: '10.0.0', + projects: [], + ...(reporting === undefined ? {} : { reporting }) + }, + RUSH_JSON_PATH + ); +} + +describe('RushConfiguration reporting configuration', () => { + beforeEach(() => { + FileSystem.ensureEmptyFolder(TEMP_FOLDER); + }); + + afterEach(() => { + FileSystem.ensureEmptyFolder(TEMP_FOLDER); + }); + + it('defaults agent environment variables to an empty array', () => { + writeRushJson(); + + const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile(RUSH_JSON_PATH); + + expect(rushConfiguration.reportingConfiguration.agentEnvironmentVariables).toEqual([]); + }); + + it('loads configured agent environment variables', () => { + writeRushJson({ + agentEnvironmentVariables: ['MY_AGENT_CLI', 'ANOTHER_AGENT'] + }); + + const rushConfiguration: RushConfiguration = RushConfiguration.loadFromConfigurationFile(RUSH_JSON_PATH); + + expect(rushConfiguration.reportingConfiguration.agentEnvironmentVariables).toEqual([ + 'MY_AGENT_CLI', + 'ANOTHER_AGENT' + ]); + }); + + it('rejects invalid agent environment variables', () => { + writeRushJson({ + agentEnvironmentVariables: ['MY_AGENT_CLI', 123] + }); + + expect(() => RushConfiguration.loadFromConfigurationFile(RUSH_JSON_PATH)).toThrow( + /agentEnvironmentVariables/ + ); + }); + + it('rejects unsupported reporting settings', () => { + writeRushJson({ + agentEnvironmentVariables: [], + defaultReporter: 'ai' + }); + + expect(() => RushConfiguration.loadFromConfigurationFile(RUSH_JSON_PATH)).toThrow(/defaultReporter/); + }); +}); diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index f2df5c851a..0fdd200e77 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -20,7 +20,11 @@ export { export { ApprovedPackagesPolicy } from './api/ApprovedPackagesPolicy'; -export { RushConfiguration, type ITryFindRushJsonLocationOptions } from './api/RushConfiguration'; +export { + RushConfiguration, + type IRushReportingConfiguration, + type ITryFindRushJsonLocationOptions +} from './api/RushConfiguration'; export { Subspace } from './api/Subspace'; export { SubspacesConfiguration } from './api/SubspacesConfiguration'; diff --git a/libraries/rush-lib/src/schemas/experiments.schema.json b/libraries/rush-lib/src/schemas/experiments.schema.json index dee04051b8..7f40290233 100644 --- a/libraries/rush-lib/src/schemas/experiments.schema.json +++ b/libraries/rush-lib/src/schemas/experiments.schema.json @@ -93,6 +93,10 @@ "useDirectFileTransfersForBuildCache": { "description": "If true, the build cache will use file-based APIs to transfer cache entries to and from cloud storage. This avoids loading the entire cache entry into memory, which can prevent out-of-memory errors for large build outputs and allow cache entries to exceed the limit of a single Buffer. The cloud cache provider plugin must implement the optional file-based methods for this to take effect; otherwise it falls back to the buffer-based approach.", "type": "boolean" + }, + "useRushReporter": { + "description": "If true, Rush may use the experimental Rush reporter system. If omitted or false, Rush preserves the legacy reporting behavior.", + "type": "boolean" } }, "additionalProperties": false diff --git a/libraries/rush-lib/src/schemas/rush.schema.json b/libraries/rush-lib/src/schemas/rush.schema.json index dce5fcaae3..9593b014ee 100644 --- a/libraries/rush-lib/src/schemas/rush.schema.json +++ b/libraries/rush-lib/src/schemas/rush.schema.json @@ -248,6 +248,22 @@ "description": "Indicates whether telemetry data should be collected and stored in the Rush temp folder during Rush runs.", "type": "boolean" }, + "reporting": { + "description": "Configures repository settings used by the Rush reporter system.", + "type": "object", + "properties": { + "agentEnvironmentVariables": { + "description": "Additional environment variable names that identify an agent environment.", + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + }, + "additionalProperties": false + }, "allowedProjectTags": { "description": "This is an optional, but recommended, list of allowed tags that can be applied to Rush projects using the \"tags\" setting in this file. This list is useful for preventing mistakes such as misspelling, and it also provides a centralized place to document your tags. If \"allowedProjectTags\" list is not specified, then any valid tag is allowed. A tag name must be one or more words separated by hyphens or slashes, where a word may contain lowercase ASCII letters, digits, \".\", and \"@\" characters.", "type": "array",