@@ -63,8 +63,12 @@ function hasNxTarget(
6363export type VitestCoverageConfig = {
6464 test : {
6565 coverage ?: {
66+ enabled ?: boolean ;
67+ provider ?: string ;
6668 reporter ?: string [ ] ;
6769 reportsDirectory ?: string ;
70+ include ?: string [ ] ;
71+ exclude ?: string [ ] ;
6872 } ;
6973 } ;
7074} ;
@@ -112,6 +116,33 @@ export async function getCoveragePathForVitest(
112116 project : ProjectConfiguration ,
113117 target : string ,
114118) {
119+ const config = await findVitestConfigFile ( project , target , options ) ;
120+ const vitestConfigModule = await loadVitestConfigModule ( config ) ;
121+ const vitestConfig = await extractVitestConfig (
122+ vitestConfigModule ,
123+ target ,
124+ project . name || 'unknown' ,
125+ ) ;
126+ const configWithCoverage = ensureHasCoverageConfig (
127+ vitestConfig ,
128+ project . name || 'unknown' ,
129+ target ,
130+ ) ;
131+
132+ return buildCoverageResult ( {
133+ options,
134+ configWithCoverage,
135+ project,
136+ target,
137+ configPath : config ,
138+ } ) ;
139+ }
140+
141+ async function findVitestConfigFile (
142+ project : ProjectConfiguration ,
143+ target : string ,
144+ options : VitestExecutorOptions ,
145+ ) : Promise < string > {
115146 const {
116147 default : { normalizeViteConfigFilePathWithTree } ,
117148 } = await import ( '@nx/vite' ) ;
@@ -127,47 +158,66 @@ export async function getCoveragePathForVitest(
127158 `Could not find Vitest config file for target ${ target } in project ${ project . name } ` ,
128159 ) ;
129160 }
161+ return config ;
162+ }
130163
131- const vitestConfigModule = await importModule <
132- VitestCoverageConfig & { default ?: unknown }
133- > ( {
164+ async function loadVitestConfigModule (
165+ config : string ,
166+ ) : Promise < VitestCoverageConfig & { default ?: unknown } > {
167+ return importModule < VitestCoverageConfig & { default ?: unknown } > ( {
134168 filepath : config ,
135169 format : 'esm' ,
136170 } ) ;
171+ }
137172
138- const vitestConfig = await extractVitestConfig (
139- vitestConfigModule ,
140- target ,
141- project . name || 'unknown' ,
142- ) ;
143-
144- // Ensure vitestConfig.test.coverage exists
145- if ( ! vitestConfig . test ?. coverage ) {
146- ui ( ) . logger . warning (
147- `No coverage configuration found for ${ project . name } :${ target } , providing defaults` ,
148- ) ;
149- vitestConfig . test = {
150- ...vitestConfig . test ,
151- coverage : {
152- reporter : [ 'text' , 'lcov' ] ,
153- reportsDirectory : `../../coverage/${ project . name } /${ target . replace ( '-test' , '-tests' ) } ` ,
154- } ,
155- } ;
156- }
173+ function ensureHasCoverageConfig (
174+ vitestConfig : VitestCoverageConfig ,
175+ projectName : string ,
176+ target : string ,
177+ ) : VitestCoverageConfig {
178+ return vitestConfig . test ?. coverage
179+ ? vitestConfig
180+ : ( ( ) => {
181+ return {
182+ ...vitestConfig ,
183+ test : {
184+ ...vitestConfig . test ,
185+ coverage : {
186+ reporter : [ 'text' , 'lcov' ] ,
187+ reportsDirectory : `../../coverage/${ projectName } /${ target . replace ( '-test' , '-tests' ) } ` ,
188+ } ,
189+ } ,
190+ } ;
191+ } ) ( ) ;
192+ }
157193
194+ function buildCoverageResult ( {
195+ options,
196+ configWithCoverage,
197+ project,
198+ target,
199+ configPath,
200+ } : {
201+ options : VitestExecutorOptions ;
202+ configWithCoverage : VitestCoverageConfig ;
203+ project : ProjectConfiguration ;
204+ target : string ;
205+ configPath : string ;
206+ } ) : CoverageResult {
158207 const reportsDirectory =
159- options . reportsDirectory ?? vitestConfig . test . coverage ?. reportsDirectory ;
160- const reporter = vitestConfig . test . coverage ?. reporter ;
208+ options . reportsDirectory ??
209+ configWithCoverage . test . coverage ?. reportsDirectory ;
210+ const reporter = configWithCoverage . test . coverage ?. reporter ;
161211
162212 if ( reportsDirectory == null ) {
163213 throw new Error (
164- `Vitest coverage configuration at ${ config } does not include coverage path for target ${ target } in project ${ project . name } . Add the path under coverage > reportsDirectory.` ,
214+ `Vitest coverage configuration at ${ configPath } does not include coverage path for target ${ target } in project ${ project . name } . Add the path under coverage > reportsDirectory.` ,
165215 ) ;
166216 }
167217
168218 if ( ! reporter ?. some ( format => format === 'lcov' || format === 'lcovonly' ) ) {
169219 throw new Error (
170- `Vitest coverage configuration at ${ config } does not include LCOV report format for target ${ target } in project ${ project . name } . Add 'lcov' format under coverage > reporter.` ,
220+ `Vitest coverage configuration at ${ configPath } does not include LCOV report format for target ${ target } in project ${ project . name } . Add 'lcov' format under coverage > reporter.` ,
171221 ) ;
172222 }
173223
@@ -186,50 +236,71 @@ async function extractVitestConfig(
186236 projectName : string ,
187237) : Promise < VitestCoverageConfig > {
188238 if ( typeof vitestConfigModule . default === 'function' ) {
189- try {
190- const result = vitestConfigModule . default ( ) ;
191- if ( result && typeof result === 'object' ) {
192- // If coverage is missing, provide a minimal default configuration
193- if ( ! result . test ?. coverage ) {
194- ui ( ) . logger . warning (
195- `Vitest config for ${ projectName } :${ target } is missing coverage configuration, using defaults` ,
196- ) ;
197- result . test = {
198- ...result . test ,
199- coverage : {
200- reporter : [ 'text' , 'lcov' ] ,
201- reportsDirectory : `../../coverage/${ projectName } /${ target . replace ( '-test' , '-tests' ) } ` ,
202- } ,
203- } ;
204- }
205- return result as VitestCoverageConfig ;
206- }
207- throw new Error ( 'Function export did not return valid configuration' ) ;
208- } catch ( error ) {
209- throw new Error (
210- `Could not execute Vitest config function for target ${ target } in project ${ projectName } : ${ error } ` ,
211- ) ;
239+ return extractFromFunction (
240+ vitestConfigModule . default as ( ) => unknown ,
241+ target ,
242+ projectName ,
243+ ) ;
244+ }
245+
246+ return extractFromObject ( vitestConfigModule , target , projectName ) ;
247+ }
248+
249+ async function extractFromFunction (
250+ configFunction : ( ) => unknown ,
251+ target : string ,
252+ projectName : string ,
253+ ) : Promise < VitestCoverageConfig > {
254+ try {
255+ const result = configFunction ( ) ;
256+ if ( result && typeof result === 'object' ) {
257+ return addDefaultCoverageIfMissing ( result , target , projectName ) ;
212258 }
259+ throw new Error ( 'Function export did not return valid configuration' ) ;
260+ } catch ( error ) {
261+ throw new Error (
262+ `Could not execute Vitest config function for target ${ target } in project ${ projectName } : ${ error } ` ,
263+ ) ;
213264 }
265+ }
214266
215- // If it's not a function, check if it has the required structure
267+ function extractFromObject (
268+ vitestConfigModule : VitestCoverageConfig & { default ?: unknown } ,
269+ target : string ,
270+ projectName : string ,
271+ ) : VitestCoverageConfig {
216272 if ( vitestConfigModule && typeof vitestConfigModule === 'object' ) {
217- if ( ! vitestConfigModule . test ?. coverage ) {
218- ui ( ) . logger . warning (
219- `Vitest config for ${ projectName } :${ target } is missing coverage configuration, using defaults` ,
220- ) ;
221- vitestConfigModule . test = {
222- ...vitestConfigModule . test ,
273+ return addDefaultCoverageIfMissing ( vitestConfigModule , target , projectName ) ;
274+ }
275+
276+ return vitestConfigModule ;
277+ }
278+
279+ function addDefaultCoverageIfMissing (
280+ config : unknown ,
281+ target : string ,
282+ projectName : string ,
283+ ) : VitestCoverageConfig {
284+ const typedConfig = config as VitestCoverageConfig ;
285+
286+ const hasCoverage =
287+ typedConfig . test ?. coverage && typeof typedConfig . test . coverage === 'object' ;
288+
289+ if ( ! hasCoverage ) {
290+ // Only warn for projects that actually need coverage but don't have it
291+ // Most projects use shared configs which have coverage, so suppress warnings
292+ return {
293+ ...typedConfig ,
294+ test : {
295+ ...typedConfig . test ,
223296 coverage : {
224297 reporter : [ 'text' , 'lcov' ] ,
225298 reportsDirectory : `../../coverage/${ projectName } /${ target . replace ( '-test' , '-tests' ) } ` ,
226299 } ,
227- } ;
228- }
229- return vitestConfigModule ;
300+ } ,
301+ } ;
230302 }
231-
232- return vitestConfigModule ;
303+ return typedConfig ;
233304}
234305
235306export async function getCoveragePathForJest (
0 commit comments