Skip to content

Commit 5d903ee

Browse files
TheLarkInnCopilot
andcommitted
Address reporter parity review feedback
Complete lifecycle status coverage, root-session parity, telemetry compatibility, extension privacy, and Rush semver-range plugin gating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent fefabfc commit 5d903ee

22 files changed

Lines changed: 281 additions & 104 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Allow Rush plugin manifests to declare an optional supported Rush version range.",
6+
"type": "patch"
7+
}
8+
],
9+
"packageName": "@microsoft/rush",
10+
"email": "TheLarkInn@users.noreply.github.com"
11+
}

common/changes/@rushstack/rush-reporter/docs-rush-reporter-overhaul-spec_2026-07-15-00-18-26.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"changes": [
33
{
44
"packageName": "@rushstack/rush-reporter",
5-
"comment": "Add scoped session reporting (createScopedReporter, RushSessionReporting, IScopedLogger, execution context) and plugin API version compatibility with a migration diagnostic",
5+
"comment": "Add scoped session reporting (createScopedReporter, RushSessionReporting, IScopedLogger, execution context) and Rush version range compatibility with a migration diagnostic",
66
"type": "minor"
77
}
88
],

common/config/subspaces/default/pnpm-lock.yaml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/reviews/api/rush-reporter.api.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export function createBeforeLogAdapter(hooks: readonly LegacyBeforeLogHook[]): (
4545
export function createEngineSink(providedSink?: IReporterEventSink): IEngineSinkResolution;
4646

4747
// @beta
48-
export function createPluginApiIncompatibleDiagnostic(manifest: IRushPluginManifest): IRushDiagnostic;
48+
export function createPluginApiIncompatibleDiagnostic(manifest: IRushPluginManifest, rushVersion: string): IRushDiagnostic;
4949

5050
// @beta
5151
export function createRushDiagnostic(code: RushDiagnosticCodes, options?: ICreateRushDiagnosticOptions): IRushDiagnostic;
@@ -471,8 +471,8 @@ export interface IRushFileDiagnosticSource {
471471

472472
// @beta
473473
export interface IRushPluginManifest {
474-
readonly pluginApiVersion: string;
475474
readonly pluginName: string;
475+
readonly rushVersionRange: string;
476476
}
477477

478478
// @beta
@@ -544,9 +544,6 @@ export interface IShadowResultSummary {
544544
readonly succeeded: boolean;
545545
}
546546

547-
// @beta
548-
export function isPluginApiVersionSupported(declaredApiVersion: string, supportedApiVersion?: string): boolean;
549-
550547
// @beta
551548
export function isReporterEventRequired(type: ReporterEventType): boolean;
552549

@@ -556,6 +553,9 @@ export function isReporterExtensionEventName(name: string): name is ReporterExte
556553
// @beta
557554
export function isReporterProtocolCompatible(consumer: IReporterProtocolVersion, producer: IReporterProtocolVersion): boolean;
558555

556+
// @beta
557+
export function isRushVersionSupported(rushVersionRange: string, rushVersion: string): boolean;
558+
559559
// @beta
560560
export function isValidRushDiagnosticCode(code: string): boolean;
561561

@@ -657,7 +657,7 @@ export class OldEngineOutputAdapter {
657657
export type OneOrMoreRushDiagnosticCodeSegments<TSegments extends string = string> = string extends TSegments ? `_${Uppercase<string>}` : TSegments extends `_${infer Segments}` ? Segments extends '' ? never : TSegments extends Uppercase<TSegments> ? TSegments : never : never;
658658

659659
// @beta
660-
export type OperationStatus = 'ready' | 'executing' | 'success' | 'successWithWarnings' | 'failure' | 'blocked' | 'skipped' | 'fromCache' | 'noOp';
660+
export type OperationStatus = 'ready' | 'waiting' | 'queued' | 'executing' | 'success' | 'successWithWarnings' | 'failure' | 'blocked' | 'skipped' | 'fromCache' | 'noOp' | 'aborted';
661661

662662
// @beta
663663
export function parseEarlyReporterControls(argv: readonly string[], env: Record<string, string | undefined>): IEarlyReporterControls;
@@ -828,9 +828,6 @@ export const RUSH_DIAGNOSTIC_TEMPLATES: Readonly<Record<RushDiagnosticTemplateKe
828828
// @beta
829829
export const RUSH_INTERNAL_ERROR_CODE: 'RUSH_INTERNAL_UNEXPECTED';
830830

831-
// @beta
832-
export const RUSH_PLUGIN_API_VERSION: '1.0.0';
833-
834831
// @beta
835832
export const RUSH_REPORTER_BOOTSTRAP_HANDOFF_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_HANDOFF';
836833

libraries/reporter/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"devDependencies": {
5050
"@rushstack/heft": "workspace:*",
5151
"eslint": "~9.37.0",
52-
"local-node-rig": "workspace:*"
52+
"local-node-rig": "workspace:*",
53+
"@types/semver": "7.7.1"
5354
},
5455
"peerDependencies": {
5556
"@types/node": "*"
@@ -59,5 +60,8 @@
5960
"optional": true
6061
}
6162
},
62-
"sideEffects": false
63+
"sideEffects": false,
64+
"dependencies": {
65+
"semver": "~7.7.4"
66+
}
6367
}

libraries/reporter/src/diagnostics/templates/configuration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
export const CONFIGURATION_DIAGNOSTIC_TEMPLATES = {
1313
'diagnostic.RUSH_CONFIG_INVALID_JSON.summary': 'The configuration file {file} contains invalid JSON.',
1414
'diagnostic.RUSH_PLUGIN_API_INCOMPATIBLE.summary':
15-
'The plugin {pluginName} declares plugin API version {declaredApiVersion}, which is incompatible with this Rush (supported: {supportedApiVersion}).'
15+
'The plugin {pluginName} supports Rush {rushVersionRange}, which does not include the running Rush version {rushVersion}.'
1616
} as const;

libraries/reporter/src/exit/ExitStatus.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,10 @@ export interface IResolveExitStatusFromEventsOptions {
129129
* Resolves a command's exit status from its structured event stream.
130130
*
131131
* @remarks
132-
* A failure is any failed command result, any error-severity diagnostic, or any
133-
* failed operation. Diagnostic categories and the selected reporter are never
134-
* consulted, so they cannot influence the exit code. Warning-severity
135-
* diagnostics never cause failure.
132+
* A failure is any failed command result, nonzero root completion code,
133+
* error-severity diagnostic, or failed or aborted operation. Diagnostic
134+
* categories and the selected reporter are never consulted, so they cannot
135+
* influence the exit code. Warning-severity diagnostics never cause failure.
136136
*
137137
* @param events - the structured events emitted during the command
138138
* @param options - cancellation and signal state
@@ -145,16 +145,24 @@ export function resolveExitStatusFromEvents(
145145
): IRushExitStatus {
146146
let hasFailures: boolean = false;
147147
for (const event of events) {
148+
if (event.parentSessionId !== undefined) {
149+
continue;
150+
}
148151
if (event.type === 'commandResult') {
149152
if ((event.payload as { succeeded: boolean }).succeeded === false) {
150153
hasFailures = true;
151154
}
155+
} else if (event.type === 'commandCompleted' || event.type === 'sessionCompleted') {
156+
if ((event.payload as { exitCode: number }).exitCode !== 0) {
157+
hasFailures = true;
158+
}
152159
} else if (event.type === 'diagnosticEmitted') {
153160
if ((event.payload as { severity?: string }).severity === 'error') {
154161
hasFailures = true;
155162
}
156163
} else if (event.type === 'operationStatusChanged') {
157-
if ((event.payload as { status?: string }).status === 'failure') {
164+
const status: string | undefined = (event.payload as { status?: string }).status;
165+
if (status === 'failure' || status === 'aborted') {
158166
hasFailures = true;
159167
}
160168
}

libraries/reporter/src/index.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,7 @@ export { createScopedLogger } from './session/ScopedLogger';
160160
export type { IRushSessionReportingOptions, IReporterExecutionContext } from './session/RushSessionReporting';
161161
export { RushSessionReporting } from './session/RushSessionReporting';
162162
export type { IRushPluginManifest } from './session/PluginApi';
163-
export {
164-
RUSH_PLUGIN_API_VERSION,
165-
isPluginApiVersionSupported,
166-
createPluginApiIncompatibleDiagnostic
167-
} from './session/PluginApi';
163+
export { isRushVersionSupported, createPluginApiIncompatibleDiagnostic } from './session/PluginApi';
168164

169165
export type {
170166
OperationStatus,

libraries/reporter/src/lifecycle/LifecycleEvents.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@
88
*/
99
export type OperationStatus =
1010
| 'ready'
11+
| 'waiting'
12+
| 'queued'
1113
| 'executing'
1214
| 'success'
1315
| 'successWithWarnings'
1416
| 'failure'
1517
| 'blocked'
1618
| 'skipped'
1719
| 'fromCache'
18-
| 'noOp';
20+
| 'noOp'
21+
| 'aborted';
1922

2023
/**
2124
* The payload of a `sessionStarted` event.

libraries/reporter/src/lifecycle/ShadowParity.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export interface IShadowResultSummary {
4848
export function deriveExitCodeFromEvents(events: readonly IReporterEventEnvelope<unknown>[]): number {
4949
let commandResult: ICommandResultPayload | undefined;
5050
for (const event of events) {
51-
if (event.type === 'commandResult') {
51+
if (event.parentSessionId === undefined && event.type === 'commandResult') {
5252
commandResult = event.payload as ICommandResultPayload;
5353
}
5454
}
@@ -61,7 +61,7 @@ export function deriveExitCodeFromEvents(events: readonly IReporterEventEnvelope
6161

6262
let sessionExitCode: number | undefined;
6363
for (const event of events) {
64-
if (event.type === 'sessionCompleted') {
64+
if (event.parentSessionId === undefined && event.type === 'sessionCompleted') {
6565
sessionExitCode = (event.payload as { exitCode: number }).exitCode;
6666
}
6767
}
@@ -83,21 +83,29 @@ export function deriveExitCodeFromEvents(events: readonly IReporterEventEnvelope
8383
export function summarizeShadowResult(
8484
events: readonly IReporterEventEnvelope<unknown>[]
8585
): IShadowResultSummary {
86-
const operationCounts: { [status: string]: number } = {};
86+
const operationStatuses: Map<string, IOperationStatusChangedPayload['status']> = new Map();
8787
let commandName: string | undefined;
8888
let commandSucceeded: boolean | undefined;
8989

9090
for (const event of events) {
91+
if (event.parentSessionId !== undefined) {
92+
continue;
93+
}
9194
if (event.type === 'operationStatusChanged') {
9295
const payload: IOperationStatusChangedPayload = event.payload as IOperationStatusChangedPayload;
93-
operationCounts[payload.status] = (operationCounts[payload.status] ?? 0) + 1;
96+
operationStatuses.set(payload.operationId, payload.status);
9497
} else if (event.type === 'commandResult') {
9598
const payload: ICommandResultPayload = event.payload as ICommandResultPayload;
9699
commandName = payload.commandName;
97100
commandSucceeded = payload.succeeded;
98101
}
99102
}
100103

104+
const operationCounts: { [status: string]: number } = {};
105+
for (const status of operationStatuses.values()) {
106+
operationCounts[status] = (operationCounts[status] ?? 0) + 1;
107+
}
108+
101109
const exitCode: number = deriveExitCodeFromEvents(events);
102110
return {
103111
commandName,

0 commit comments

Comments
 (0)