Skip to content

Commit e524559

Browse files
committed
Add --code-only and --type-only flags to coverage script
1 parent 2363dc7 commit e524559

File tree

1 file changed

+159
-67
lines changed

1 file changed

+159
-67
lines changed

scripts/cover.mjs

Lines changed: 159 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
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

610
import path from 'node:path'
11+
import { parseArgs } from 'node:util'
712
import { fileURLToPath } from 'node:url'
813

914
import { printError, printHeader, printSuccess } from './utils/cli-helpers.mjs'
@@ -12,9 +17,20 @@ import { runCommandQuiet } from './utils/run-command.mjs'
1217
const __dirname = path.dirname(fileURLToPath(import.meta.url))
1318
const 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+
1529
printHeader('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']
1834
const 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
]
3147
const typeCoverageArgs = ['exec', 'type-coverage']
3248

3349
try {
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-
/Test Files\s+\d+[^\n]*\n[\s\S]*?Duration\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-
/ % Coverage report from v8\n([-|]+)\n([^\n]+)\n\1/,
66-
)
67-
const allFilesMatch = output.match(/All files\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+
/Test Files\s+\d+[^\n]*\n[\s\S]*?Duration\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+
/ % Coverage report from v8\n([-|]+)\n([^\n]+)\n\1/,
108+
)
109+
const allFilesMatch = output.match(/All files\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+
/Test Files\s+\d+[^\n]*\n[\s\S]*?Duration\s+[\d.]+m?s[^\n]*/,
152+
)
153+
154+
// Extract coverage summary
155+
const coverageHeaderMatch = output.match(
156+
/ % Coverage report from v8\n([-|]+)\n([^\n]+)\n\1/,
157+
)
158+
const allFilesMatch = output.match(/All files\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

Comments
 (0)