1717 */
1818
1919// Single source of truth for the set of valid build outcomes. The
20- // `as const` tuple drives BOTH the compile-time `BuildOutcome` union
21- // AND the runtime `VALID_STATUSES` set, so adding a status in one
22- // place is enforced in the other. The exhaustiveness check below
23- // guarantees the tuple and the union stay in lockstep.
20+ // `as const` tuple drives BOTH the runtime `VALID_STATUSES` set AND
21+ // the compile-time `BuildOutcome` union (derived via indexed access
22+ // below), so the tuple is the only place a status needs to be added.
23+ // Since `BuildOutcome` is derived from this tuple there is no separate
24+ // union that could drift out of sync — no redundant exhaustiveness
25+ // assertion is needed.
2426const BUILD_OUTCOMES = [ "success" , "failure" , "skipped" ] as const ;
2527
2628export type BuildOutcome = ( typeof BUILD_OUTCOMES ) [ number ] ;
2729
2830const VALID_STATUSES : ReadonlySet < BuildOutcome > = new Set ( BUILD_OUTCOMES ) ;
2931
30- // Compile-time exhaustiveness check: if BuildOutcome ever drifts from
31- // the BUILD_OUTCOMES tuple (e.g. a hand-edited union), this assignment
32- // will fail to typecheck.
33- const _exhaustive : ReadonlyArray < BuildOutcome > = BUILD_OUTCOMES ;
34- void _exhaustive ;
35-
3632export interface ServiceBuildResult {
3733 service : string ;
3834 status : BuildOutcome ;
@@ -55,7 +51,7 @@ function validateServiceBuildResult(
5551 raw : unknown ,
5652 contextLabel : string ,
5753) : ServiceBuildResult {
58- if ( typeof raw !== "object" || raw === null ) {
54+ if ( typeof raw !== "object" || raw === null || Array . isArray ( raw ) ) {
5955 throw new Error (
6056 `${ contextLabel } : expected object with {service, status}, got ${ JSON . stringify ( raw ) } ` ,
6157 ) ;
@@ -66,7 +62,8 @@ function validateServiceBuildResult(
6662 `${ contextLabel } : missing required string field "service": ${ JSON . stringify ( raw ) } ` ,
6763 ) ;
6864 }
69- if ( service . trim ( ) . length === 0 ) {
65+ const trimmedService = service . trim ( ) ;
66+ if ( trimmedService . length === 0 ) {
7067 throw new Error (
7168 `${ contextLabel } : field "service" must be a non-empty, non-whitespace string: ${ JSON . stringify ( raw ) } ` ,
7269 ) ;
@@ -80,7 +77,7 @@ function validateServiceBuildResult(
8077 `${ contextLabel } : invalid "status" (must be success|failure|skipped): ${ JSON . stringify ( raw ) } ` ,
8178 ) ;
8279 }
83- return { service, status : status as BuildOutcome } ;
80+ return { service : trimmedService , status : status as BuildOutcome } ;
8481}
8582
8683export function parseBuildOutputs ( raw : string ) : ServiceBuildResult [ ] {
0 commit comments