@@ -4,10 +4,15 @@ import path from 'node:path';
44import type {
55 CategoryConfig ,
66 PluginAnswer ,
7- PluginCodegenResult ,
87 PluginSetupBinding ,
8+ PluginSetupTree ,
99} from '@code-pushup/models' ;
10- import { hasDependency , readJsonFile , singleQuote } from '@code-pushup/utils' ;
10+ import {
11+ hasDependency ,
12+ pluralize ,
13+ readJsonFile ,
14+ singleQuote ,
15+ } from '@code-pushup/utils' ;
1116import { addLcovReporter , hasLcovReporter } from './config-file.js' ;
1217import {
1318 ALL_COVERAGE_TYPES ,
@@ -25,6 +30,9 @@ const VITEST_WORKSPACE = new RegExp(`^vitest\\.workspace\\.${CONFIG_EXT}$`);
2530const JEST_CONFIG = new RegExp ( `^jest\\.config\\.${ CONFIG_EXT } $` ) ;
2631const DEFAULT_REPORT_PATH = 'coverage/lcov.info' ;
2732
33+ const LCOV_COMMENT =
34+ '// NOTE: Ensure your test config includes "lcov" in coverage reporters.' ;
35+
2836const FRAMEWORKS = [
2937 { name : 'Jest' , value : 'jest' } ,
3038 { name : 'Vitest' , value : 'vitest' } ,
@@ -63,6 +71,7 @@ export const coverageSetupBinding = {
6371 title : COVERAGE_PLUGIN_TITLE ,
6472 packageName : PACKAGE_NAME ,
6573 isRecommended,
74+ // eslint-disable-next-line max-lines-per-function
6675 prompts : async ( targetDir : string ) => {
6776 const framework = await detectFramework ( targetDir ) ;
6877 const configFile = await detectConfigFile ( targetDir , framework ) ;
@@ -96,7 +105,10 @@ export const coverageSetupBinding = {
96105 key : 'coverage.types' ,
97106 message : 'Coverage types to measure' ,
98107 type : 'checkbox' ,
99- choices : ALL_COVERAGE_TYPES . map ( type => ( { name : type , value : type } ) ) ,
108+ choices : ALL_COVERAGE_TYPES . map ( type => ( {
109+ name : pluralize ( type ) ,
110+ value : type ,
111+ } ) ) ,
100112 default : [ ...ALL_COVERAGE_TYPES ] ,
101113 } ,
102114 {
@@ -113,20 +125,22 @@ export const coverageSetupBinding = {
113125 } ,
114126 ] ;
115127 } ,
116- generateConfig : ( answers : Record < string , PluginAnswer > ) => {
128+ generateConfig : async (
129+ answers : Record < string , PluginAnswer > ,
130+ tree ?: PluginSetupTree ,
131+ ) => {
117132 const args = parseAnswers ( answers ) ;
133+ const lcovConfigured = await configureLcovReporter ( args , tree ) ;
118134 return {
119135 imports : [
120136 { moduleSpecifier : PACKAGE_NAME , defaultImport : 'coveragePlugin' } ,
121137 ] ,
122- pluginInit : formatPluginInit ( args ) ,
138+ pluginInit : formatPluginInit ( args , lcovConfigured ) ,
123139 ...( args . categories ? { categories : CATEGORIES } : { } ) ,
124- ...resolveAdjustments ( args ) ,
125140 } ;
126141 } ,
127142} satisfies PluginSetupBinding ;
128143
129- /** Applies defaults for missing or empty values. */
130144function parseAnswers ( answers : Record < string , PluginAnswer > ) : CoverageOptions {
131145 const string = ( key : string ) => {
132146 const value = answers [ key ] ;
@@ -149,44 +163,54 @@ function parseAnswers(answers: Record<string, PluginAnswer>): CoverageOptions {
149163 } ;
150164}
151165
152- /** Omits options that match plugin defaults. */
153- function formatPluginInit ( options : CoverageOptions ) : string {
166+ /** Returns true if lcov reporter is already present or was successfully added. */
167+ async function configureLcovReporter (
168+ options : CoverageOptions ,
169+ tree ?: PluginSetupTree ,
170+ ) : Promise < boolean > {
171+ const { framework, configFile } = options ;
172+ if ( framework === 'other' || ! configFile || ! tree ) {
173+ return false ;
174+ }
175+ const content = await tree . read ( configFile ) ;
176+ if ( content == null ) {
177+ return false ;
178+ }
179+ if ( hasLcovReporter ( content , framework ) ) {
180+ return true ;
181+ }
182+ const modified = addLcovReporter ( content , framework ) ;
183+ if ( modified === content ) {
184+ return false ;
185+ }
186+ await tree . write ( configFile , modified ) ;
187+ return true ;
188+ }
189+
190+ function formatPluginInit (
191+ options : CoverageOptions ,
192+ lcovConfigured : boolean ,
193+ ) : string {
154194 const { reportPath, testCommand, types, continueOnFail } = options ;
155195
156- const args = [
196+ const hasCustomTypes =
197+ types . length > 0 && types . length < ALL_COVERAGE_TYPES . length ;
198+
199+ const body = [
157200 `reports: [${ singleQuote ( reportPath ) } ]` ,
158201 testCommand
159202 ? `coverageToolCommand: { command: ${ singleQuote ( testCommand ) } }`
160203 : '' ,
161- types . length > 0 && types . length < ALL_COVERAGE_TYPES . length
204+ hasCustomTypes
162205 ? `coverageTypes: [${ types . map ( singleQuote ) . join ( ', ' ) } ]`
163206 : '' ,
164207 continueOnFail ? '' : 'continueOnCommandFail: false' ,
165- ] . filter ( Boolean ) ;
166-
167- return `await coveragePlugin({
168- ${ args . join ( ',\n ' ) } ,
169- })` ;
170- }
208+ ]
209+ . filter ( Boolean )
210+ . join ( ',\n ' ) ;
171211
172- function resolveAdjustments (
173- options : CoverageOptions ,
174- ) : Pick < PluginCodegenResult , 'adjustments' > {
175- const { framework, configFile } = options ;
176- if ( framework === 'other' || ! configFile ) {
177- return { } ;
178- }
179- return {
180- adjustments : [
181- {
182- path : configFile ,
183- transform : ( content : string ) =>
184- hasLcovReporter ( content , framework )
185- ? content
186- : addLcovReporter ( content , framework ) ,
187- } ,
188- ] ,
189- } ;
212+ const init = `await coveragePlugin({\n ${ body } ,\n })` ;
213+ return lcovConfigured ? init : `${ LCOV_COMMENT } \n ${ init } ` ;
190214}
191215
192216async function isRecommended ( targetDir : string ) : Promise < boolean > {
0 commit comments