Skip to content

Commit 892badf

Browse files
committed
feat: Rework glob functionality to work by excluding specific things
1 parent 2302bf1 commit 892badf

12 files changed

Lines changed: 184 additions & 80 deletions

File tree

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,18 @@ jobs:
3535
coverage/coverage2.xml
3636
update-comment: true
3737
show-changed-lines-only: true
38-
show-glob-only: '**/**'
38+
exclude-files: |
39+
some_generated_files/*.rs
3940
```
4041
4142
### Inputs
4243
43-
| Name | Default | Description |
44-
|---------------------------|------------|-----------------------------------------------------------------|
45-
| `files` | (required) | One or multiple files. Can be multiple lines. Glob is supported |
46-
| `update-comment` | true | TODO: Update |
47-
| `show-changed-lines-only` | true | If part of a PR, filter coverage data against changed lines |
48-
| `show-glob-only` | '**' | Select subdirectories to show coverage data for |
44+
| Name | Default | Description |
45+
|---------------------------|------------|--------------------------------------------------------------------------------------------|
46+
| `files` | (required) | One or multiple files. Can be multiple lines. Glob is supported |
47+
| `update-comment` | true | TODO: Update |
48+
| `show-changed-lines-only` | true | If part of a PR, filter coverage data against changed lines |
49+
| `exclude-files` | '' | Glob patterns for files to exclude. Can be multiple lines. If empty, no files are excluded |
4950

5051
### Outputs
5152

action.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ inputs:
1313
description: If part of a PR, filter coverage data against changed lines
1414
required: false
1515
default: 'true'
16-
show-glob-only:
17-
description: Select subdirectories to show coverage data for
16+
exclude-files:
17+
description: Glob patterns for files to exclude from coverage data. Can be multiple lines. If empty, no files are excluded.
1818
required: false
19-
default: '**'
19+
default: ''
2020
source:
2121
description: Fallback source directory for resolving file paths from coverage files
2222
required: false

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"build": "ncc build --source-map --license licenses.txt src/index.ts",
66
"build:cli": "ncc build --source-map src/cli.ts -o dist-cli",
77
"test": "vitest",
8+
"run:cli": "pnpm run build:cli && node dist-cli/index.js",
89
"test:coverage": "vitest run --coverage"
910
},
1011
"bin": {

src/action.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type Inputs = {
77
files: string
88
updateComment: boolean
99
showChangedLinesOnly: boolean
10-
globPattern: string
10+
excludeFilesPattern: string
1111
sourceDir: string
1212
}
1313

@@ -23,12 +23,22 @@ const actionLogger: Logger = {
2323
export const run = async (inputs: Inputs, octokit: Octokit, context: Context): Promise<void> => {
2424
const shas = getComparisonShas(context)
2525

26+
const excludePatterns = inputs.excludeFilesPattern
27+
.split('\n')
28+
.map((line) => line.trim())
29+
.filter((line) => line.length > 0)
30+
31+
const filePatterns = inputs.files
32+
.split(/[\n,]/)
33+
.map((line) => line.trim())
34+
.filter((line) => line.length > 0)
35+
2636
// Process coverage using the core module
2737
const result = await processCoverage(
2838
{
29-
files: inputs.files,
39+
filePatterns,
3040
sourceDir: inputs.sourceDir,
31-
globPattern: inputs.globPattern,
41+
excludePatterns,
3242
baseSha: shas?.baseSha,
3343
headSha: shas?.headSha,
3444
},

src/cli.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ USAGE:
1919
coverage-visualizer --files <patterns> [options]
2020
2121
OPTIONS:
22-
--files <patterns> Coverage file patterns (required, comma or newline separated)
22+
--file <pattern> Coverage file patterns (required)
2323
--output <path> Output file path (optional, prints to stdout if not specified)
2424
--source <path> Source directory for resolving file paths (default: current directory)
2525
--base-ref <ref> Git ref to compare against (e.g., origin/main, HEAD~1)
2626
--show-changed-only Filter to show only changed lines (requires --base-ref)
27-
--show-glob <pattern> Glob pattern to filter which files to show (default: **)
27+
--exclude-file <pattern> Glob pattern for files to exclude (can be passed multiple times)
2828
--verbose, -v Enable debug logging
2929
--help Show this help message
3030
@@ -38,20 +38,20 @@ EXAMPLES:
3838
# Show only changed lines compared to main branch
3939
coverage-visualizer --files "coverage/*.xml" --base-ref origin/main --show-changed-only
4040
41-
# Filter to specific directory
42-
coverage-visualizer --files "coverage/*.xml" --show-glob "src/components/**"
41+
# Exclude specific files or directories
42+
coverage-visualizer --files "coverage/*.xml" --exclude-files "to_exclude/*" --exclude-files "my_folder/*.generated"
4343
`)
4444
}
4545

4646
async function main(): Promise<void> {
4747
const { values } = parseArgs({
4848
options: {
49-
files: { type: 'string', short: 'f' },
49+
file: { type: 'string', short: 'f' },
5050
output: { type: 'string', short: 'o' },
5151
source: { type: 'string', short: 's' },
5252
'base-ref': { type: 'string', short: 'b' },
5353
'show-changed-only': { type: 'boolean', default: false },
54-
'show-glob': { type: 'string', default: '**' },
54+
'exclude-files': { type: 'string', multiple: true },
5555
verbose: { type: 'boolean', short: 'v', default: false },
5656
help: { type: 'boolean', short: 'h', default: false },
5757
},
@@ -67,8 +67,8 @@ async function main(): Promise<void> {
6767
}
6868

6969
// Validate required arguments
70-
if (!values.files) {
71-
console.error('Error: --files is required\n')
70+
if (!values.file) {
71+
console.error('Error: --file is required\n')
7272
printHelp()
7373
process.exit(1)
7474
}
@@ -106,11 +106,12 @@ async function main(): Promise<void> {
106106
}
107107

108108
// Process coverage
109+
const excludePatterns = (values['exclude-files'] as string[] | undefined) ?? []
109110
const result = await processCoverage(
110111
{
111-
files: values.files,
112+
filePatterns: [values.file],
112113
sourceDir: values.source ?? process.cwd(),
113-
globPattern: values['show-glob'] ?? '**',
114+
excludePatterns,
114115
baseSha,
115116
headSha,
116117
},

src/core/process-coverage.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import assert from 'node:assert'
12
import * as fs from 'node:fs/promises'
23
import * as path from 'node:path'
34
import {
@@ -40,16 +41,17 @@ export function createCliLogger(verbose: boolean): Logger {
4041
*/
4142
export type ProcessCoverageInputs = {
4243
/** Coverage file patterns (newline or comma separated) */
43-
files: string
44+
filePatterns: string[]
4445
/** Source directory for resolving file paths from coverage files */
4546
sourceDir: string
46-
/** Glob pattern to filter which files to show */
47-
globPattern: string
47+
/** Glob patterns for files to exclude. If empty, no files are excluded. */
48+
excludePatterns: string[]
4849
/** Explicit base commit SHA for comparison */
4950
baseSha?: string | undefined
5051
/** Explicit head commit SHA for comparison */
5152
headSha?: string | undefined
5253
}
54+
5355
/**
5456
* Result of coverage processing.
5557
*/
@@ -74,16 +76,10 @@ export async function processCoverage(
7476
inputs: ProcessCoverageInputs,
7577
logger: Logger,
7678
): Promise<ProcessCoverageResult | null> {
77-
// Find all matching coverage files
78-
const filePatterns = inputs.files
79-
.split(/[\n,]/)
80-
.map((line) => line.trim())
81-
.filter((line) => line.length > 0)
82-
83-
logger.info(`Looking for coverage files matching: [${filePatterns.join(', ')}]`)
79+
logger.info(`Looking for coverage files matching: [${inputs.filePatterns.join(', ')}]`)
8480

8581
// Sort and de-duplicate matched files for deterministic CI output
86-
const globResults = await Array.fromAsync(fs.glob(filePatterns))
82+
const globResults = await Array.fromAsync(fs.glob(inputs.filePatterns))
8783
const matchedFiles = [...new Set(globResults.map((f) => path.resolve(f)).sort())]
8884

8985
if (matchedFiles.length === 0) {
@@ -135,8 +131,10 @@ export async function processCoverage(
135131
}
136132

137133
// Apply filters to the coverage report
138-
const globFilteredPackages: PackageCoverage[] = filterByGlob(mergedPackages, inputs.globPattern, logger)
139-
const fileFilteredPackages = changedLinesPerFileMap ?filterByChangedLines(globFilteredPackages, changedLinesPerFileMap, logger) : globFilteredPackages
134+
const globFilteredPackages: PackageCoverage[] = filterByGlob(mergedPackages, inputs.excludePatterns, logger)
135+
const fileFilteredPackages = changedLinesPerFileMap
136+
? filterByChangedLines(globFilteredPackages, changedLinesPerFileMap, logger)
137+
: globFilteredPackages
140138

141139
// Read file contents from disk using resolved paths
142140
const fileContents = await readFileContents(fileFilteredPackages)
@@ -217,21 +215,23 @@ async function mergeReportAndResolveSources(
217215
const existing = fileMap.get(resolvedPath)
218216
if (existing) {
219217
const merged = CoberturaCoverageParser.merge(existing, file.lines)
218+
assert(existing.resolvedPath)
220219
fileMap.set(resolvedPath, merged)
221220
logger.debug?.(`Merged duplicate file: ${file.filename}`)
222221
} else {
223-
fileMap.set(resolvedPath, { resolvedPath, ...file })
222+
file.resolvedPath = resolvedPath
223+
fileMap.set(resolvedPath, file)
224224
}
225225
}
226226
}
227227
}
228228

229229
// Convert file maps back to arrays for the final report
230-
const packages: PackageCoverage[] = Array.from(packageMap.values()).map(({ name, fileMap }) => {
230+
const packages: PackageCoverage[] = Array.from(packageMap.values()).map(({ name, fileMap }): PackageCoverage => {
231231
const files = Array.from(fileMap.values())
232232
return {
233233
name,
234-
files,
234+
files: files,
235235
coverage: CoberturaCoverageParser.calculateFileCoverage(files),
236236
}
237237
})

src/coverage/model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export type FileCoverage = {
2828
/** Display path (relative to source root, for markdown output) */
2929
readonly filename: string
3030
/** Absolute path for reading file contents (set after path resolution) */
31-
resolvedPath?: string | undefined
31+
resolvedPath: string | undefined
3232
/** Coverage information per line */
3333
readonly lines: LineCoverage[]
3434
/** The coverage information */

src/coverage/parsers/cobertura.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export class CoberturaCoverageParser implements CoverageParser {
133133
const coverage = CoberturaCoverageParser.calculateCoverage(lines)
134134
newFile = {
135135
filename,
136+
resolvedPath: undefined,
136137
lines,
137138
coverage
138139
}
@@ -150,6 +151,7 @@ export class CoberturaCoverageParser implements CoverageParser {
150151
return {
151152
lines,
152153
filename: file.filename,
154+
resolvedPath: file.resolvedPath,
153155
coverage
154156
}
155157
}

src/filter/filter.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,38 @@ import type { FileCoverage, PackageCoverage } from '../coverage/model.js'
55
import type { ChangedLinesMap } from './model.js'
66

77
/**
8-
* Filter coverage packages to only include files matching a glob pattern.
8+
* Filter coverage packages to exclude files matching any of the provided glob patterns.
9+
* If patterns array is empty, no files are excluded (all files are included).
910
*/
10-
export function filterByGlob(packages: PackageCoverage[], pattern: string, logger: Logger): PackageCoverage[] {
11-
const effectivePattern = pattern.includes('/') ? pattern : `**/${pattern}`
11+
export function filterByGlob(packages: PackageCoverage[], patterns: string[], logger: Logger): PackageCoverage[] {
12+
if (patterns.length === 0) {
13+
return packages
14+
}
15+
16+
// Normalize patterns: if pattern doesn't contain '/', prepend '**/'
17+
const effectivePatterns = patterns.map((pattern) => (pattern.includes('/') ? pattern : `**/${pattern}`))
18+
1219
const totalFilesBefore = packages.reduce((sum, pkg) => sum + pkg.files.length, 0)
1320

1421
const filteredPackages = packages
1522
.map((pkg) => {
1623
const files = pkg.files.filter((file) => {
17-
const filePath = file.resolvedPath ?? file.filename
18-
logger.debug?.(`Filtering '${filePath}' against glob '${effectivePattern}'`)
19-
return path.matchesGlob(filePath, effectivePattern)
24+
const filePath = file.resolvedPath
25+
// Files without resolvedPath are included (not excluded)
26+
if (!filePath) {
27+
return true
28+
}
29+
30+
// Exclude file if it matches ANY of the patterns
31+
const shouldExclude = effectivePatterns.some((pattern) => {
32+
const matches = path.matchesGlob(filePath, pattern)
33+
if (matches) {
34+
logger.debug?.(`Excluding '${filePath}' (matches exclude-pattern '${pattern}')`)
35+
}
36+
return matches
37+
})
38+
39+
return !shouldExclude
2040
})
2141
return {
2242
name: pkg.name,
@@ -27,7 +47,9 @@ export function filterByGlob(packages: PackageCoverage[], pattern: string, logge
2747
.filter((pkg) => pkg.files.length > 0)
2848

2949
const totalFilesAfter = filteredPackages.reduce((sum, pkg) => sum + pkg.files.length, 0)
30-
logger.info(`Filtered ${totalFilesAfter}/${totalFilesBefore} files against glob '${effectivePattern}'`)
50+
logger.info(
51+
`Filtered ${totalFilesAfter}/${totalFilesBefore} files against exclude-patterns: [${effectivePatterns.join(', ')}]`,
52+
)
3153

3254
return filteredPackages
3355
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import * as core from '@actions/core'
2-
import { run } from './action.js'
32
import { getContext, getOctokit } from './github.js'
3+
import { run } from './action.js'
44

55
try {
66
await run(
77
{
88
files: core.getInput('files', { required: true }),
99
updateComment: core.getBooleanInput('update-comment'),
1010
showChangedLinesOnly: core.getBooleanInput('show-changed-lines-only'),
11-
globPattern: core.getInput('show-glob-only') || '**',
11+
excludeFilesPattern: core.getInput('exclude-files') || '',
1212
sourceDir: core.getInput('source') || process.env['GITHUB_WORKSPACE'] || process.cwd(),
1313
},
1414
getOctokit(),

0 commit comments

Comments
 (0)