Skip to content

Commit 1ce5da2

Browse files
TheLarkInnCopilot
andcommitted
Merge reporter contracts base
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2 parents 7aa64c9 + a3aacd8 commit 1ce5da2

15 files changed

Lines changed: 512 additions & 54 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ These GitHub repositories provide supplementary resources for Rush Stack:
9696
| [/libraries/package-deps-hash](./libraries/package-deps-hash/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fpackage-deps-hash.svg)](https://badge.fury.io/js/%40rushstack%2Fpackage-deps-hash) | [changelog](./libraries/package-deps-hash/CHANGELOG.md) | [@rushstack/package-deps-hash](https://www.npmjs.com/package/@rushstack/package-deps-hash) |
9797
| [/libraries/package-extractor](./libraries/package-extractor/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fpackage-extractor.svg)](https://badge.fury.io/js/%40rushstack%2Fpackage-extractor) | [changelog](./libraries/package-extractor/CHANGELOG.md) | [@rushstack/package-extractor](https://www.npmjs.com/package/@rushstack/package-extractor) |
9898
| [/libraries/problem-matcher](./libraries/problem-matcher/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fproblem-matcher.svg)](https://badge.fury.io/js/%40rushstack%2Fproblem-matcher) | [changelog](./libraries/problem-matcher/CHANGELOG.md) | [@rushstack/problem-matcher](https://www.npmjs.com/package/@rushstack/problem-matcher) |
99-
| [/libraries/reporter](./libraries/reporter/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Freporter.svg)](https://badge.fury.io/js/%40rushstack%2Freporter) | [changelog](./libraries/reporter/CHANGELOG.md) | [@rushstack/rush-reporter](https://www.npmjs.com/package/@rushstack/rush-reporter) |
99+
| [/libraries/reporter](./libraries/reporter/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-reporter.svg)](https://badge.fury.io/js/%40rushstack%2Frush-reporter) | [changelog](./libraries/reporter/CHANGELOG.md) | [@rushstack/rush-reporter](https://www.npmjs.com/package/@rushstack/rush-reporter) |
100100
| [/libraries/rig-package](./libraries/rig-package/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frig-package.svg)](https://badge.fury.io/js/%40rushstack%2Frig-package) | [changelog](./libraries/rig-package/CHANGELOG.md) | [@rushstack/rig-package](https://www.npmjs.com/package/@rushstack/rig-package) |
101101
| [/libraries/rush-lib](./libraries/rush-lib/) | [![npm version](https://badge.fury.io/js/%40microsoft%2Frush-lib.svg)](https://badge.fury.io/js/%40microsoft%2Frush-lib) | | [@microsoft/rush-lib](https://www.npmjs.com/package/@microsoft/rush-lib) |
102102
| [/libraries/rush-pnpm-kit-v10](./libraries/rush-pnpm-kit-v10/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frush-pnpm-kit-v10.svg)](https://badge.fury.io/js/%40rushstack%2Frush-pnpm-kit-v10) | [changelog](./libraries/rush-pnpm-kit-v10/CHANGELOG.md) | [@rushstack/rush-pnpm-kit-v10](https://www.npmjs.com/package/@rushstack/rush-pnpm-kit-v10) |

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

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,11 @@ export interface INdjsonOptions {
160160
readonly maxRecordBytes?: number;
161161
}
162162

163+
// @beta
164+
export class InvalidReporterHelloError extends Error {
165+
constructor(reason: string);
166+
}
167+
163168
// @beta
164169
export interface IOldEngineOutputAdapterOptions {
165170
readonly maxChunkBytes?: number;
@@ -378,15 +383,15 @@ export interface IScopedMessageOptions {
378383
// @beta
379384
export interface IScopedReporter {
380385
emitDiagnostic(diagnostic: IRushDiagnostic): string;
381-
emitExtension<TPayload>(name: ReporterExtensionEventName, payload: TPayload): string;
386+
emitExtension(name: ReporterExtensionEventName, payload: ReporterJsonValue): string;
382387
emitMessage(options: IScopedMessageOptions): string;
383388
}
384389

385390
// @beta
386391
export function isReporterEventRequired(type: ReporterEventType): boolean;
387392

388393
// @beta
389-
export function isReporterExtensionEventName(name: string): boolean;
394+
export function isReporterExtensionEventName(name: string): name is ReporterExtensionEventName;
390395

391396
// @beta
392397
export function isReporterProtocolCompatible(consumer: IReporterProtocolVersion, producer: IReporterProtocolVersion): boolean;
@@ -416,14 +421,22 @@ export class NdjsonDecoder {
416421
flush(): unknown[];
417422
}
418423

424+
// @beta
425+
export class NdjsonInvalidRecordError extends Error {
426+
constructor(decodedRecords: readonly unknown[], parseError: Error);
427+
readonly decodedRecords: readonly unknown[];
428+
readonly parseError: Error;
429+
}
430+
419431
// @beta
420432
export class NdjsonRecordTooLargeError extends Error {
421-
constructor(maxRecordBytes: number);
433+
constructor(maxRecordBytes: number, decodedRecords?: readonly unknown[]);
434+
readonly decodedRecords: readonly unknown[];
422435
readonly maxRecordBytes: number;
423436
}
424437

425438
// @beta
426-
export function negotiateReporterHello(hello: IReporterHello, options: IReporterHandshakeOptions): IReporterHandshakeResult;
439+
export function negotiateReporterHello(helloValue: unknown, options: IReporterHandshakeOptions): IReporterHandshakeResult;
427440

428441
// @beta
429442
export class OldEngineOutputAdapter {
@@ -432,11 +445,17 @@ export class OldEngineOutputAdapter {
432445
}
433446

434447
// @beta
435-
export type OneOrMoreRushDiagnosticCodeSegments<S extends string = RushDiagnosticCodeSegment> = S extends string ? S | `${S}${RushDiagnosticCodeSegment}` : never;
448+
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;
436449

437450
// @beta
438451
export function parseEarlyReporterControls(argv: readonly string[], env: Record<string, string | undefined>): IEarlyReporterControls;
439452

453+
// @beta
454+
export function parseReporterExtensionEventName(name: string): ReporterExtensionEventName;
455+
456+
// @beta
457+
export function parseReporterHello(value: unknown): IReporterHello;
458+
440459
// @beta
441460
export function readBootstrapHandoffFileAsync(filePath: string): Promise<{
442461
header: IBootstrapHandoffHeader | undefined;
@@ -468,7 +487,9 @@ export type ReporterCompatibilityMode = 'structured' | 'new-frontend-old-engine'
468487
export type ReporterEventType = (typeof REPORTER_EVENT_TYPES)[number];
469488

470489
// @beta
471-
export type ReporterExtensionEventName = `${Lowercase<string>}.${Lowercase<string>}`;
490+
export type ReporterExtensionEventName = `${string}.${string}` & {
491+
readonly __reporterExtensionEventNameBrand: 'ReporterExtensionEventName';
492+
};
472493

473494
// @beta
474495
export class ReporterHost {
@@ -592,13 +613,13 @@ export const RUSH_REPORTER_BOOTSTRAP_NONCE_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_NO
592613
export type RushDiagnosticCategory = KnownRushDiagnosticCategory | (string & {});
593614

594615
// @beta
595-
export type RushDiagnosticCode = `RUSH${RushDiagnosticCodeSegment}${OneOrMoreRushDiagnosticCodeSegments}`;
616+
export type RushDiagnosticCode<TCode extends string = string> = string extends TCode ? `RUSH_${Uppercase<string>}_${Uppercase<string>}` : TCode extends `RUSH_${infer Domain}_${infer Name}` ? Domain extends '' ? never : Name extends '' ? never : TCode extends Uppercase<TCode> ? TCode : never : never;
596617

597618
// @beta
598619
export type RushDiagnosticCodes = (typeof RUSH_DIAGNOSTIC_CODE_DEFINITIONS)[number]['code'];
599620

600621
// @beta
601-
export type RushDiagnosticCodeSegment = `_${Uppercase<string>}`;
622+
export type RushDiagnosticCodeSegment<TSegment extends string = string> = string extends TSegment ? `_${Uppercase<string>}` : TSegment extends `_${infer Segment}` ? Segment extends '' ? never : TSegment extends Uppercase<TSegment> ? TSegment : never : never;
602623

603624
// @beta
604625
export type RushDiagnosticDetailKey = `diagnostic.${RushDiagnosticCode}.detail`;

libraries/reporter/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Change Log - @rushstack/rush-reporter
2+
3+
This log was last generated on Tue, 18 Aug 2026 18:00:00 GMT and should not be manually modified.
4+
5+
## 0.1.0
6+
Tue, 18 Aug 2026 18:00:00 GMT
7+
8+
### Minor changes
9+
10+
- Introduce the public-beta Rush reporter contracts, diagnostics, NDJSON protocol, and reporter manager.

libraries/reporter/src/diagnostics/RushDiagnosticCode.ts

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,62 @@
22
// See LICENSE in the project root for license information.
33

44
/**
5-
* A single underscore-prefixed uppercase segment of a Rush diagnostic code,
6-
* for example `_CONFIG`.
5+
* A single underscore-prefixed uppercase alphanumeric segment of a Rush
6+
* diagnostic code, for example `_CONFIG`.
77
*
88
* @beta
99
*/
10-
export type RushDiagnosticCodeSegment = `_${Uppercase<string>}`;
10+
export type RushDiagnosticCodeSegment<TSegment extends string = string> = string extends TSegment
11+
? `_${Uppercase<string>}`
12+
: TSegment extends `_${infer Segment}`
13+
? Segment extends ''
14+
? never
15+
: TSegment extends Uppercase<TSegment>
16+
? TSegment
17+
: never
18+
: never;
1119

1220
/**
13-
* One or more {@link RushDiagnosticCodeSegment} values, for example
21+
* One or more valid {@link RushDiagnosticCodeSegment} values, for example
1422
* `_INVALID_JSON`.
1523
*
1624
* @beta
1725
*/
18-
export type OneOrMoreRushDiagnosticCodeSegments<
19-
S extends string = RushDiagnosticCodeSegment
20-
> = S extends string ? S | `${S}${RushDiagnosticCodeSegment}` : never;
26+
export type OneOrMoreRushDiagnosticCodeSegments<TSegments extends string = string> =
27+
string extends TSegments
28+
? `_${Uppercase<string>}`
29+
: TSegments extends `_${infer Segments}`
30+
? Segments extends ''
31+
? never
32+
: TSegments extends Uppercase<TSegments>
33+
? TSegments
34+
: never
35+
: never;
2136

2237
/**
2338
* The shape of a stable, never-reused Rush diagnostic code:
2439
* `RUSH_<DOMAIN>_<NAME>`, for example `RUSH_DEPENDENCY_TOOL_FAILED`.
2540
*
2641
* @remarks
27-
* The type system enforces the naming convention for Rush-owned code.
42+
* Supplying a string literal as the type parameter enforces every segment at
43+
* compile time. The central registry applies this validation to every authored
44+
* code before it can be registered.
2845
* {@link isValidRushDiagnosticCode} performs the equivalent runtime check for
2946
* untrusted wire data, where types have been erased.
3047
*
3148
* @beta
3249
*/
33-
export type RushDiagnosticCode = `RUSH${RushDiagnosticCodeSegment}${OneOrMoreRushDiagnosticCodeSegments}`;
50+
export type RushDiagnosticCode<TCode extends string = string> = string extends TCode
51+
? `RUSH_${Uppercase<string>}_${Uppercase<string>}`
52+
: TCode extends `RUSH_${infer Domain}_${infer Name}`
53+
? Domain extends ''
54+
? never
55+
: Name extends ''
56+
? never
57+
: TCode extends Uppercase<TCode>
58+
? TCode
59+
: never
60+
: never;
3461

3562
const CODE_PREFIX: 'RUSH_' = 'RUSH_';
3663

libraries/reporter/src/diagnostics/RushDiagnosticCodeRegistry.ts

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,88 @@ export interface IRushDiagnosticCodeDefinition {
5454
readonly detailKey: RushDiagnosticDetailKey | undefined;
5555
}
5656

57+
type UppercaseAlphanumeric =
58+
| 'A'
59+
| 'B'
60+
| 'C'
61+
| 'D'
62+
| 'E'
63+
| 'F'
64+
| 'G'
65+
| 'H'
66+
| 'I'
67+
| 'J'
68+
| 'K'
69+
| 'L'
70+
| 'M'
71+
| 'N'
72+
| 'O'
73+
| 'P'
74+
| 'Q'
75+
| 'R'
76+
| 'S'
77+
| 'T'
78+
| 'U'
79+
| 'V'
80+
| 'W'
81+
| 'X'
82+
| 'Y'
83+
| 'Z'
84+
| '0'
85+
| '1'
86+
| '2'
87+
| '3'
88+
| '4'
89+
| '5'
90+
| '6'
91+
| '7'
92+
| '8'
93+
| '9';
94+
95+
type IsValidRushDiagnosticCodeSegment<TSegment extends string> = TSegment extends ''
96+
? false
97+
: TSegment extends `${UppercaseAlphanumeric}${infer Rest}`
98+
? Rest extends ''
99+
? true
100+
: IsValidRushDiagnosticCodeSegment<Rest>
101+
: false;
102+
103+
type AreValidRushDiagnosticCodeSegments<
104+
TSegments extends string,
105+
THasPriorSegment extends boolean = false
106+
> = TSegments extends `${infer Segment}_${infer Rest}`
107+
? IsValidRushDiagnosticCodeSegment<Segment> extends true
108+
? AreValidRushDiagnosticCodeSegments<Rest, true>
109+
: false
110+
: THasPriorSegment extends true
111+
? IsValidRushDiagnosticCodeSegment<TSegments>
112+
: false;
113+
114+
type ValidateRushDiagnosticCode<TCode extends string> =
115+
TCode extends `RUSH_${infer Segments}`
116+
? AreValidRushDiagnosticCodeSegments<Segments> extends true
117+
? TCode
118+
: never
119+
: never;
120+
121+
type ValidatedRushDiagnosticCodeDefinitions<
122+
TDefinitions extends readonly IRushDiagnosticCodeDefinition[]
123+
> = {
124+
readonly [K in keyof TDefinitions]: TDefinitions[K] extends IRushDiagnosticCodeDefinition
125+
? TDefinitions[K] & {
126+
readonly code: ValidateRushDiagnosticCode<TDefinitions[K]['code']>;
127+
}
128+
: TDefinitions[K];
129+
};
130+
131+
function defineRushDiagnosticCodeDefinitions<
132+
const TDefinitions extends readonly IRushDiagnosticCodeDefinition[]
133+
>(
134+
definitions: TDefinitions & ValidatedRushDiagnosticCodeDefinitions<TDefinitions>
135+
): TDefinitions {
136+
return definitions;
137+
}
138+
57139
/**
58140
* The stable code used for unexpected internal (programmer) failures.
59141
*
@@ -68,13 +150,13 @@ export const RUSH_INTERNAL_ERROR_CODE: 'RUSH_INTERNAL_UNEXPECTED' = 'RUSH_INTERN
68150
* Codes are append-only. A code is never removed or repurposed, so consumers can
69151
* rely on a code always meaning the same thing. The `as const satisfies` typing
70152
* lets {@link RushDiagnosticCodes} and {@link RushDiagnosticTemplateKey} be
71-
* derived from this list, so adding a code without its templates is a
72-
* compile-time error.
153+
* derived from this list, so malformed codes and missing templates are
154+
* compile-time errors.
73155
*
74156
* @beta
75157
*/
76-
// eslint-disable-next-line @typescript-eslint/typedef -- literal inference feeds the derived RushDiagnosticCodes/RushDiagnosticTemplateKey unions
77-
export const RUSH_DIAGNOSTIC_CODE_DEFINITIONS = [
158+
// eslint-disable-next-line @typescript-eslint/typedef -- literal inference feeds the validated code/template unions
159+
export const RUSH_DIAGNOSTIC_CODE_DEFINITIONS = defineRushDiagnosticCodeDefinitions([
78160
{
79161
code: 'RUSH_CONFIG_INVALID_JSON',
80162
category: 'configuration',
@@ -131,7 +213,7 @@ export const RUSH_DIAGNOSTIC_CODE_DEFINITIONS = [
131213
summaryKey: 'diagnostic.RUSH_INTERNAL_UNEXPECTED.summary',
132214
detailKey: 'diagnostic.RUSH_INTERNAL_UNEXPECTED.detail'
133215
}
134-
] as const satisfies readonly IRushDiagnosticCodeDefinition[];
216+
]);
135217

136218
/**
137219
* The union of every registered Rush diagnostic code.

libraries/reporter/src/index.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,25 @@ export {
7777
isReporterProtocolCompatible
7878
} from './protocol/ReporterProtocol';
7979
export type { INdjsonOptions } from './protocol/Ndjson';
80-
export { NdjsonRecordTooLargeError, encodeNdjsonRecord, NdjsonDecoder } from './protocol/Ndjson';
80+
export {
81+
NdjsonInvalidRecordError,
82+
NdjsonRecordTooLargeError,
83+
encodeNdjsonRecord,
84+
NdjsonDecoder
85+
} from './protocol/Ndjson';
8186
export type {
8287
IReporterHello,
8388
IReporterHelloAck,
8489
IReporterHandshakeOptions,
8590
IReporterHandshakeResult,
8691
ReporterCapability
8792
} from './protocol/ReporterHandshake';
88-
export { negotiateReporterHello, REPORTER_KNOWN_CAPABILITIES } from './protocol/ReporterHandshake';
93+
export {
94+
InvalidReporterHelloError,
95+
negotiateReporterHello,
96+
parseReporterHello,
97+
REPORTER_KNOWN_CAPABILITIES
98+
} from './protocol/ReporterHandshake';
8999

90100
export type { IReporter, IReporterContext } from './manager/IReporter';
91101
export type { IReporterRegistrationOptions, IReporterManagerOptions } from './manager/ReporterManager';
@@ -150,4 +160,7 @@ export type {
150160
IScopedReporter
151161
} from './producers/IScopedReporter';
152162
export type { ReporterExtensionEventName } from './producers/ReporterExtensionEventName';
153-
export { isReporterExtensionEventName } from './producers/ReporterExtensionEventName';
163+
export {
164+
isReporterExtensionEventName,
165+
parseReporterExtensionEventName
166+
} from './producers/ReporterExtensionEventName';

0 commit comments

Comments
 (0)