Skip to content

Commit 1416c5e

Browse files
ASAS
authored andcommitted
fix: coverage fallback
1 parent 54f0ea7 commit 1416c5e

File tree

1 file changed

+132
-61
lines changed

1 file changed

+132
-61
lines changed

packages/plugin-coverage/src/lib/nx/coverage-paths.ts

Lines changed: 132 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,12 @@ function hasNxTarget(
6363
export 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

235306
export async function getCoveragePathForJest(

0 commit comments

Comments
 (0)