@@ -19,7 +19,7 @@ interface ProjectCoverageResult {
1919 functions : number ;
2020 branches : number ;
2121 } | null ;
22- status : 'PASSED' | 'FAILED' | 'SKIPPED' ;
22+ status : 'PASSED' | 'FAILED' | 'SKIPPED' | 'PENDING' ;
2323}
2424
2525/**
@@ -182,10 +182,91 @@ function shouldSkipProject(thresholds: CoverageThreshold | null): boolean {
182182 return thresholds === null || ( thresholds && Object . keys ( thresholds ) . length === 0 ) ;
183183}
184184
185+ /**
186+ * Evaluates coverage for build status only (no PR comment generation)
187+ * Used by jobs that shouldn't generate coverage reports but need to fail if coverage is insufficient
188+ */
189+ export function evaluateCoverageForBuildStatus ( projects : string [ ] , thresholds : ThresholdConfig ) : number {
190+ if ( ! process . env . COVERAGE_THRESHOLDS ) {
191+ core . info ( 'No coverage thresholds defined for build status check, skipping evaluation' ) ;
192+ return 0 ; // No thresholds defined, 0 failures
193+ }
194+
195+ let failedProjectsCount = 0 ;
196+
197+ core . info ( `Evaluating coverage for build status only: ${ projects . length } projects: ${ projects . join ( ', ' ) } ` ) ;
198+
199+ for ( const project of projects ) {
200+ const projectThresholds = getProjectThresholds ( project , thresholds ) ;
201+
202+ // Skip projects with null thresholds or explicitly empty thresholds
203+ if ( shouldSkipProject ( projectThresholds ) ) {
204+ const reason = projectThresholds === null ? 'null thresholds' : 'no thresholds defined' ;
205+ core . info ( `Coverage evaluation skipped for build status for ${ project } (${ reason } )` ) ;
206+ continue ;
207+ }
208+
209+ // Try to find coverage file in various locations
210+ const coveragePath = findCoverageSummaryFile ( project ) ;
211+ let summary : CoverageSummary | null = null ;
212+
213+ if ( coveragePath ) {
214+ summary = extractCoverageData ( coveragePath , project ) ;
215+ }
216+
217+ // If we didn't find project-specific coverage, try global coverage file
218+ if ( ! summary ) {
219+ summary = tryReadFromGlobalCoverage ( project ) ;
220+ }
221+
222+ if ( ! summary ) {
223+ core . warning ( `No coverage data found for ${ project } - marking as build failure` ) ;
224+ failedProjectsCount ++ ;
225+ continue ;
226+ }
227+
228+ // Log the actual coverage data found
229+ core . info ( `Build status coverage data for ${ project } : lines=${ summary . lines . pct } %, statements=${ summary . statements . pct } %, functions=${ summary . functions . pct } %, branches=${ summary . branches . pct } %` ) ;
230+
231+ let projectPassed = true ;
232+ const failedMetrics : string [ ] = [ ] ;
233+
234+ // Check each metric if threshold is defined
235+ if ( projectThresholds . lines !== undefined && summary . lines . pct < projectThresholds . lines ) {
236+ projectPassed = false ;
237+ failedMetrics . push ( `lines: ${ summary . lines . pct . toFixed ( 2 ) } % < ${ projectThresholds . lines } %` ) ;
238+ }
239+
240+ if ( projectThresholds . statements !== undefined && summary . statements . pct < projectThresholds . statements ) {
241+ projectPassed = false ;
242+ failedMetrics . push ( `statements: ${ summary . statements . pct . toFixed ( 2 ) } % < ${ projectThresholds . statements } %` ) ;
243+ }
244+
245+ if ( projectThresholds . functions !== undefined && summary . functions . pct < projectThresholds . functions ) {
246+ projectPassed = false ;
247+ failedMetrics . push ( `functions: ${ summary . functions . pct . toFixed ( 2 ) } % < ${ projectThresholds . functions } %` ) ;
248+ }
249+
250+ if ( projectThresholds . branches !== undefined && summary . branches . pct < projectThresholds . branches ) {
251+ projectPassed = false ;
252+ failedMetrics . push ( `branches: ${ summary . branches . pct . toFixed ( 2 ) } % < ${ projectThresholds . branches } %` ) ;
253+ }
254+
255+ if ( ! projectPassed ) {
256+ core . error ( `Project ${ project } failed coverage thresholds for build status: ${ failedMetrics . join ( ', ' ) } ` ) ;
257+ failedProjectsCount ++ ;
258+ } else {
259+ core . info ( `Project ${ project } passed all coverage thresholds for build status` ) ;
260+ }
261+ }
262+
263+ return failedProjectsCount ;
264+ }
265+
185266/**
186267 * Evaluates coverage for all projects against their thresholds
187268 */
188- export function evaluateCoverage ( projects : string [ ] , thresholds : ThresholdConfig ) : number {
269+ export function evaluateCoverage ( projects : string [ ] , thresholds : ThresholdConfig , jobProjects ?: string [ ] ) : number {
189270 if ( ! process . env . COVERAGE_THRESHOLDS ) {
190271 core . info ( 'No coverage thresholds defined, skipping evaluation' ) ;
191272 return 0 ; // No thresholds defined, 0 failures
@@ -195,6 +276,9 @@ export function evaluateCoverage(projects: string[], thresholds: ThresholdConfig
195276 const coverageResults : ProjectCoverageResult [ ] = [ ] ;
196277
197278 core . info ( `Evaluating coverage for ${ projects . length } projects: ${ projects . join ( ', ' ) } ` ) ;
279+ if ( jobProjects ) {
280+ core . info ( `Projects actually processed by this job: ${ jobProjects . join ( ', ' ) } ` ) ;
281+ }
198282
199283 // Debug: List coverage directory contents
200284 debugCoverageDirectory ( ) ;
@@ -230,22 +314,27 @@ export function evaluateCoverage(projects: string[], thresholds: ThresholdConfig
230314 }
231315
232316 if ( ! summary ) {
233- core . warning ( `No coverage data found for ${ project } in any location` ) ;
234-
235- // Try to list what files exist for this project specifically
236- const projectSpecificDir = path . resolve ( process . cwd ( ) , `coverage/${ project } ` ) ;
237- if ( fs . existsSync ( projectSpecificDir ) ) {
238- const files = fs . readdirSync ( projectSpecificDir ) ;
239- core . info ( `Files in coverage/${ project } /: ${ files . join ( ', ' ) } ` ) ;
317+ // Check if this project was processed by this job or another job
318+ const processedByThisJob = ! jobProjects || jobProjects . includes ( project ) ;
319+
320+ if ( processedByThisJob ) {
321+ core . warning ( `No coverage data found for ${ project } in any location` ) ;
322+ coverageResults . push ( {
323+ project,
324+ thresholds : projectThresholds ,
325+ actual : null ,
326+ status : 'FAILED' // Mark as failed if no coverage report is found for this job's projects
327+ } ) ;
328+ failedProjectsCount ++ ;
329+ } else {
330+ core . info ( `Project ${ project } will be processed by another job - showing as pending` ) ;
331+ coverageResults . push ( {
332+ project,
333+ thresholds : projectThresholds ,
334+ actual : null ,
335+ status : 'PENDING'
336+ } ) ;
240337 }
241-
242- coverageResults . push ( {
243- project,
244- thresholds : projectThresholds ,
245- actual : null ,
246- status : 'FAILED' // Mark as failed if no coverage report is found
247- } ) ;
248- failedProjectsCount ++ ;
249338 continue ;
250339 }
251340
@@ -329,6 +418,30 @@ function formatCoverageComment(results: ProjectCoverageResult[], artifactUrl: st
329418
330419 comment += `| ${ projectCell } | ${ metric } | N/A | N/A | ⏩ SKIPPED |\n` ;
331420 } ) ;
421+ } else if ( result . status === 'PENDING' ) {
422+ // Show individual metrics for projects being processed by other jobs
423+ const metrics = [ 'lines' , 'statements' , 'functions' , 'branches' ] ;
424+ let hasAnyThreshold = false ;
425+ let firstRow = true ;
426+
427+ metrics . forEach ( ( metric ) => {
428+ // Skip metrics that don't have a threshold
429+ if ( ! result . thresholds || ! result . thresholds [ metric ] ) return ;
430+
431+ hasAnyThreshold = true ;
432+ const threshold = result . thresholds [ metric ] ;
433+
434+ // Only include project name in the first row for this project
435+ const projectCell = firstRow ? result . project : '' ;
436+ firstRow = false ;
437+
438+ comment += `| ${ projectCell } | ${ metric } | ${ threshold } % | Pending | ⏳ PENDING |\n` ;
439+ } ) ;
440+
441+ // Fallback if no specific thresholds are defined
442+ if ( ! hasAnyThreshold ) {
443+ comment += `| ${ result . project } | All | Defined | Pending | ⏳ PENDING |\n` ;
444+ }
332445 } else if ( result . actual === null ) {
333446 // Show individual thresholds when coverage data is missing
334447 const metrics = [ 'lines' , 'statements' , 'functions' , 'branches' ] ;
@@ -375,18 +488,27 @@ function formatCoverageComment(results: ProjectCoverageResult[], artifactUrl: st
375488 }
376489 } ) ;
377490
378- // Add overall status with failed project count
379- const overallStatus = failedProjectsCount === 0 ? '✅ PASSED' :
380- failedProjectsCount === 1 ? '⚠️ WARNING (1 project failing)' :
381- `❌ FAILED (${ failedProjectsCount } projects failing)` ;
491+ // Add overall status with failed project count (excluding pending projects)
492+ const actualFailedCount = results . filter ( r => r . status === 'FAILED' ) . length ;
493+ const overallStatus = actualFailedCount === 0 ? '✅ PASSED' :
494+ actualFailedCount === 1 ? '⚠️ WARNING (1 project failing)' :
495+ `❌ FAILED (${ actualFailedCount } projects failing)` ;
382496 comment += `\n### Overall Status: ${ overallStatus } \n` ;
383497
384- if ( failedProjectsCount === 1 ) {
498+ if ( actualFailedCount === 1 ) {
385499 comment += '\n> Note: The build will continue, but this project should be fixed before merging.\n' ;
386- } else if ( failedProjectsCount > 1 ) {
500+ } else if ( actualFailedCount > 1 ) {
387501 comment += '\n> Note: Multiple projects fail coverage thresholds. This PR will be blocked until fixed.\n' ;
388502 }
389503
504+ // Add explanation of status symbols
505+ comment += '\n---\n' ;
506+ comment += '**Status Legend:**\n' ;
507+ comment += '- ✅ **PASSED**: Coverage meets or exceeds threshold\n' ;
508+ comment += '- ❌ **FAILED**: Coverage is below threshold\n' ;
509+ comment += '- ⏩ **SKIPPED**: Project excluded from coverage requirements\n' ;
510+ comment += '- ⏳ **PENDING**: Project is being processed by another parallel job\n' ;
511+
390512 // Add link to detailed HTML reports
391513 if ( artifactUrl ) {
392514 comment += `\n📊 [View Detailed HTML Coverage Reports](${ artifactUrl } )\n` ;
0 commit comments