11/**
22 * @fileoverview Coverage script that runs tests with coverage reporting.
33 * Masks test output and shows only the coverage summary.
4+ *
5+ * Options:
6+ * --code-only Run only code coverage (skip type coverage)
7+ * --type-only Run only type coverage (skip code coverage)
48 */
59
610import path from 'node:path'
11+ import { parseArgs } from 'node:util'
712import { fileURLToPath } from 'node:url'
813
914import { printError , printHeader , printSuccess } from './utils/cli-helpers.mjs'
@@ -12,9 +17,20 @@ import { runCommandQuiet } from './utils/run-command.mjs'
1217const __dirname = path . dirname ( fileURLToPath ( import . meta. url ) )
1318const rootPath = path . join ( __dirname , '..' )
1419
20+ // Parse custom flags
21+ const { values } = parseArgs ( {
22+ options : {
23+ 'code-only' : { type : 'boolean' , default : false } ,
24+ 'type-only' : { type : 'boolean' , default : false } ,
25+ } ,
26+ strict : false ,
27+ } )
28+
1529printHeader ( 'Running Coverage' )
1630
1731// Run vitest with coverage enabled, capturing output
32+ // Filter out custom flags that vitest doesn't understand
33+ const customFlags = [ '--code-only' , '--type-only' ]
1834const vitestArgs = [
1935 '-q' ,
2036 'run' ,
@@ -26,96 +42,172 @@ const vitestArgs = [
2642 '.config/vitest.config.mts' ,
2743 '--run' ,
2844 '--coverage' ,
29- ...process . argv . slice ( 2 ) ,
45+ ...process . argv . slice ( 2 ) . filter ( ( arg ) => ! customFlags . includes ( arg ) ) ,
3046]
3147const typeCoverageArgs = [ 'exec' , 'type-coverage' ]
3248
3349try {
3450 // Build first
3551 await runCommandQuiet ( 'pnpm' , [ 'run' , 'build' ] , { cwd : rootPath } )
3652
37- const { exitCode, stdout, stderr } = await runCommandQuiet (
38- 'dotenvx' ,
39- vitestArgs ,
40- {
53+ let exitCode = 0
54+ let codeCoverageResult
55+ let typeCoverageResult
56+
57+ // Handle --type-only flag
58+ if ( values [ 'type-only' ] ) {
59+ typeCoverageResult = await runCommandQuiet ( 'pnpm' , typeCoverageArgs , {
4160 cwd : rootPath ,
42- } ,
43- )
44-
45- // Run type coverage
46- const typeCoverageResult = await runCommandQuiet ( 'pnpm' , typeCoverageArgs , {
47- cwd : rootPath ,
48- } )
49-
50- // Combine and clean output - remove ANSI color codes and spinner artifacts
51- const ansiRegex = new RegExp ( `${ String . fromCharCode ( 27 ) } \\[[0-9;]*m` , 'g' )
52- const output = ( stdout + stderr )
53- . replace ( ansiRegex , '' ) // Remove ANSI color codes
54- . replace ( / (?: ✧ | ︎ | ⚡ ) \s * / g, '' ) // Remove spinner artifacts
55- . trim ( )
56-
57- // Extract test summary (Test Files ... Duration)
58- const testSummaryMatch = output . match (
59- / T e s t F i l e s \s + \d + [ ^ \n ] * \n [ \s \S ] * ?D u r a t i o n \s + [ \d . ] + m ? s [ ^ \n ] * / ,
60- )
61-
62- // Extract coverage summary: header + All files row
63- // Match from "% Coverage" header through the All files line and closing border
64- const coverageHeaderMatch = output . match (
65- / % C o v e r a g e r e p o r t f r o m v 8 \n ( [ - | ] + ) \n ( [ ^ \n ] + ) \n \1/ ,
66- )
67- const allFilesMatch = output . match ( / A l l f i l e s \s + \| \s + ( [ \d . ] + ) \s + \| [ ^ \n ] * / )
68-
69- // Extract type coverage percentage
70- const typeCoverageOutput = (
71- typeCoverageResult . stdout + typeCoverageResult . stderr
72- ) . trim ( )
73- const typeCoverageMatch = typeCoverageOutput . match ( / \( [ \d \s / ] + \) \s + ( [ \d . ] + ) % / )
74-
75- // Display clean output
76- if ( testSummaryMatch ) {
77- console . log ( )
78- console . log ( testSummaryMatch [ 0 ] )
79- console . log ( )
80- }
61+ } )
62+ exitCode = typeCoverageResult . exitCode
8163
82- if ( coverageHeaderMatch && allFilesMatch ) {
83- console . log ( ' % Coverage report from v8' )
84- console . log ( coverageHeaderMatch [ 1 ] ) // Top border
85- console . log ( coverageHeaderMatch [ 2 ] ) // Header row
86- console . log ( coverageHeaderMatch [ 1 ] ) // Middle border
87- console . log ( allFilesMatch [ 0 ] ) // All files row
88- console . log ( coverageHeaderMatch [ 1 ] ) // Bottom border
89- console . log ( )
64+ // Display type coverage only
65+ const typeCoverageOutput = (
66+ typeCoverageResult . stdout + typeCoverageResult . stderr
67+ ) . trim ( )
68+ const typeCoverageMatch = typeCoverageOutput . match (
69+ / \( [ \d \s / ] + \) \s + ( [ \d . ] + ) % / ,
70+ )
9071
91- // Display type coverage and cumulative summary
9272 if ( typeCoverageMatch ) {
93- const codeCoveragePercent = parseFloat ( allFilesMatch [ 1 ] )
94- const typeCoveragePercent = parseFloat ( typeCoverageMatch [ 1 ] )
95- const cumulativePercent = (
96- ( codeCoveragePercent + typeCoveragePercent ) /
97- 2
98- ) . toFixed ( 2 )
99-
73+ const typeCoveragePercent = Number . parseFloat ( typeCoverageMatch [ 1 ] )
74+ console . log ( )
10075 console . log ( ' Coverage Summary' )
10176 console . log ( ' ───────────────────────────────' )
10277 console . log ( ` Type Coverage: ${ typeCoveragePercent . toFixed ( 2 ) } %` )
103- console . log ( ` Code Coverage: ${ codeCoveragePercent . toFixed ( 2 ) } %` )
78+ console . log ( )
79+ }
80+ }
81+ // Handle --code-only flag
82+ else if ( values [ 'code-only' ] ) {
83+ codeCoverageResult = await runCommandQuiet ( 'dotenvx' , vitestArgs , {
84+ cwd : rootPath ,
85+ } )
86+ exitCode = codeCoverageResult . exitCode
87+
88+ // Process code coverage output only
89+ const ansiRegex = new RegExp ( `${ String . fromCharCode ( 27 ) } \\[[0-9;]*m` , 'g' )
90+ const output = ( codeCoverageResult . stdout + codeCoverageResult . stderr )
91+ . replace ( ansiRegex , '' )
92+ . replace ( / (?: ✧ | ︎ | ⚡ ) \s * / g, '' )
93+ . trim ( )
94+
95+ // Extract and display test summary
96+ const testSummaryMatch = output . match (
97+ / T e s t F i l e s \s + \d + [ ^ \n ] * \n [ \s \S ] * ?D u r a t i o n \s + [ \d . ] + m ? s [ ^ \n ] * / ,
98+ )
99+ if ( testSummaryMatch ) {
100+ console . log ( )
101+ console . log ( testSummaryMatch [ 0 ] )
102+ console . log ( )
103+ }
104+
105+ // Extract and display coverage summary
106+ const coverageHeaderMatch = output . match (
107+ / % C o v e r a g e r e p o r t f r o m v 8 \n ( [ - | ] + ) \n ( [ ^ \n ] + ) \n \1/ ,
108+ )
109+ const allFilesMatch = output . match ( / A l l f i l e s \s + \| \s + ( [ \d . ] + ) \s + \| [ ^ \n ] * / )
110+
111+ if ( coverageHeaderMatch && allFilesMatch ) {
112+ console . log ( ' % Coverage report from v8' )
113+ console . log ( coverageHeaderMatch [ 1 ] )
114+ console . log ( coverageHeaderMatch [ 2 ] )
115+ console . log ( coverageHeaderMatch [ 1 ] )
116+ console . log ( allFilesMatch [ 0 ] )
117+ console . log ( coverageHeaderMatch [ 1 ] )
118+ console . log ( )
119+
120+ const codeCoveragePercent = Number . parseFloat ( allFilesMatch [ 1 ] )
121+ console . log ( ' Coverage Summary' )
104122 console . log ( ' ───────────────────────────────' )
105- console . log ( ` Cumulative: ${ cumulativePercent } %` )
123+ console . log ( ` Code Coverage: ${ codeCoveragePercent . toFixed ( 2 ) } %` )
124+ console . log ( )
125+ } else if ( exitCode !== 0 ) {
126+ console . log ( '\n--- Output ---' )
127+ console . log ( output )
128+ }
129+ }
130+ // Default: run both code and type coverage
131+ else {
132+ codeCoverageResult = await runCommandQuiet ( 'dotenvx' , vitestArgs , {
133+ cwd : rootPath ,
134+ } )
135+ exitCode = codeCoverageResult . exitCode
136+
137+ // Run type coverage
138+ typeCoverageResult = await runCommandQuiet ( 'pnpm' , typeCoverageArgs , {
139+ cwd : rootPath ,
140+ } )
141+
142+ // Combine and clean output
143+ const ansiRegex = new RegExp ( `${ String . fromCharCode ( 27 ) } \\[[0-9;]*m` , 'g' )
144+ const output = ( codeCoverageResult . stdout + codeCoverageResult . stderr )
145+ . replace ( ansiRegex , '' )
146+ . replace ( / (?: ✧ | ︎ | ⚡ ) \s * / g, '' )
147+ . trim ( )
148+
149+ // Extract test summary
150+ const testSummaryMatch = output . match (
151+ / T e s t F i l e s \s + \d + [ ^ \n ] * \n [ \s \S ] * ?D u r a t i o n \s + [ \d . ] + m ? s [ ^ \n ] * / ,
152+ )
153+
154+ // Extract coverage summary
155+ const coverageHeaderMatch = output . match (
156+ / % C o v e r a g e r e p o r t f r o m v 8 \n ( [ - | ] + ) \n ( [ ^ \n ] + ) \n \1/ ,
157+ )
158+ const allFilesMatch = output . match ( / A l l f i l e s \s + \| \s + ( [ \d . ] + ) \s + \| [ ^ \n ] * / )
159+
160+ // Extract type coverage
161+ const typeCoverageOutput = (
162+ typeCoverageResult . stdout + typeCoverageResult . stderr
163+ ) . trim ( )
164+ const typeCoverageMatch = typeCoverageOutput . match (
165+ / \( [ \d \s / ] + \) \s + ( [ \d . ] + ) % / ,
166+ )
167+
168+ // Display output
169+ if ( testSummaryMatch ) {
170+ console . log ( )
171+ console . log ( testSummaryMatch [ 0 ] )
106172 console . log ( )
107173 }
174+
175+ if ( coverageHeaderMatch && allFilesMatch ) {
176+ console . log ( ' % Coverage report from v8' )
177+ console . log ( coverageHeaderMatch [ 1 ] )
178+ console . log ( coverageHeaderMatch [ 2 ] )
179+ console . log ( coverageHeaderMatch [ 1 ] )
180+ console . log ( allFilesMatch [ 0 ] )
181+ console . log ( coverageHeaderMatch [ 1 ] )
182+ console . log ( )
183+
184+ // Display cumulative summary
185+ if ( typeCoverageMatch ) {
186+ const codeCoveragePercent = Number . parseFloat ( allFilesMatch [ 1 ] )
187+ const typeCoveragePercent = Number . parseFloat ( typeCoverageMatch [ 1 ] )
188+ const cumulativePercent = (
189+ ( codeCoveragePercent + typeCoveragePercent ) /
190+ 2
191+ ) . toFixed ( 2 )
192+
193+ console . log ( ' Coverage Summary' )
194+ console . log ( ' ───────────────────────────────' )
195+ console . log ( ` Type Coverage: ${ typeCoveragePercent . toFixed ( 2 ) } %` )
196+ console . log ( ` Code Coverage: ${ codeCoveragePercent . toFixed ( 2 ) } %` )
197+ console . log ( ' ───────────────────────────────' )
198+ console . log ( ` Cumulative: ${ cumulativePercent } %` )
199+ console . log ( )
200+ }
201+ } else if ( exitCode !== 0 ) {
202+ console . log ( '\n--- Output ---' )
203+ console . log ( output )
204+ }
108205 }
109206
110207 if ( exitCode === 0 ) {
111208 printSuccess ( 'Coverage completed successfully' )
112209 } else {
113210 printError ( 'Coverage failed' )
114- // Show relevant output on failure for debugging
115- if ( ! testSummaryMatch && ! coverageHeaderMatch ) {
116- console . log ( '\n--- Output ---' )
117- console . log ( output )
118- }
119211 }
120212
121213 process . exitCode = exitCode
0 commit comments