@code-pushup/coverage-plugin
Version:
Code PushUp plugin for tracking code coverage ☂
107 lines • 5.45 kB
JavaScript
import path from 'node:path';
import { importModule, logger, pluralize, pluralizeToken, stringifyError, } from '@code-pushup/utils';
import { formatMetaLog } from '../format.js';
/**
* Resolves the cached project graph for the current Nx workspace.
* First tries to read cache and if not possible, go for the async creation.
*/
async function resolveCachedProjectGraph() {
const { readCachedProjectGraph, createProjectGraphAsync } = await import('@nx/devkit');
try {
return readCachedProjectGraph();
}
catch (error) {
logger.warn(`Could not read cached project graph, falling back to async creation - ${stringifyError(error)}`);
return await createProjectGraphAsync({ exitOnError: false });
}
}
/**
* @param targets nx targets to be used for measuring coverage, test by default
* @returns An array of coverage result information for the coverage plugin.
*/
export async function getNxCoveragePaths(targets = ['test']) {
const { nodes } = await resolveCachedProjectGraph();
const coverageResultsPerTarget = Object.fromEntries(await Promise.all(targets.map(async (target) => {
const relevantNodes = Object.values(nodes).filter(graph => hasNxTarget(graph, target));
return [
target,
await Promise.all(relevantNodes.map(({ data }) => getCoveragePathsForTarget(data, target))),
];
})));
const coverageResults = Object.values(coverageResultsPerTarget).flat();
logger.info(formatMetaLog(`Inferred ${pluralizeToken('coverage report', coverageResults.length)} from Nx projects with ${pluralize('target', targets.length)} ${targets.length === 1
? targets[0]
: Object.entries(coverageResultsPerTarget)
.map(([target, results]) => `${target} (${results.length})`)
.join(' and ')}`));
logger.debug(formatMetaLog(coverageResults
.map(result => `• ${typeof result === 'string' ? result : result.resultsPath}`)
.join('\n')));
return coverageResults;
}
function hasNxTarget(project, target) {
return project.data.targets != null && target in project.data.targets;
}
export async function getCoveragePathsForTarget(project, target) {
const targetConfig = project.targets?.[target];
if (!targetConfig) {
throw new Error(`No configuration found for target ${target} in project ${project.name}`);
}
if (targetConfig.executor?.includes('@nx/vite')) {
return getCoveragePathForVitest(targetConfig.options, project, target);
}
if (targetConfig.executor?.includes('@nx/jest')) {
return getCoveragePathForJest(targetConfig.options, project, target);
}
throw new Error(`Unsupported executor ${targetConfig.executor}. Only @nx/vite and @nx/jest are currently supported.`);
}
export async function getCoveragePathForVitest(options, project, target) {
const { normalizeViteConfigFilePathWithTree } = await import('@nx/vite');
const config = normalizeViteConfigFilePathWithTree(
// HACK: only tree.exists is called, so injecting existSync from node:fs instead
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
{ exists: (await import('node:fs')).existsSync }, project.root, options.configFile);
if (!config) {
throw new Error(`Could not find Vitest config file for target ${target} in project ${project.name}`);
}
const vitestConfig = await importModule({
filepath: config,
format: 'esm',
});
const reportsDirectory = options.reportsDirectory ?? vitestConfig.test.coverage?.reportsDirectory;
const reporter = vitestConfig.test.coverage?.reporter;
if (reportsDirectory == null) {
throw new Error(`Vitest coverage configuration at ${config} does not include coverage path for target ${target} in project ${project.name}. Add the path under coverage > reportsDirectory.`);
}
if (!reporter?.some(format => format === 'lcov' || format === 'lcovonly')) {
throw new Error(`Vitest coverage configuration at ${config} does not include LCOV report format for target ${target} in project ${project.name}. Add 'lcov' format under coverage > reporter.`);
}
if (path.isAbsolute(reportsDirectory)) {
return path.join(reportsDirectory, 'lcov.info');
}
return {
pathToProject: project.root,
resultsPath: path.join(project.root, reportsDirectory, 'lcov.info'),
};
}
export async function getCoveragePathForJest(options, project, target) {
const { jestConfig } = options;
const testConfig = await importModule({
filepath: jestConfig,
});
const { coverageDirectory, coverageReporters } = {
...testConfig,
...options,
};
if (coverageDirectory == null) {
throw new Error(`Jest coverage configuration at ${jestConfig} does not include coverage path for target ${target} in ${project.name}. Add the path under coverageDirectory.`);
}
if (!coverageReporters?.includes('lcov') && !('preset' in testConfig)) {
throw new Error(`Jest coverage configuration at ${jestConfig} does not include LCOV report format for target ${target} in ${project.name}. Add 'lcov' format under coverageReporters.`);
}
if (path.isAbsolute(coverageDirectory)) {
return path.join(coverageDirectory, 'lcov.info');
}
return path.join(project.root, coverageDirectory, 'lcov.info');
}
//# sourceMappingURL=coverage-paths.js.map