Skip to content

Commit 6640b0f

Browse files
committedNov 1, 2023
add ability to define custom templates
1 parent 1c2092f commit 6640b0f

16 files changed

+10037
-1425
lines changed
 

‎.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,6 @@ Thumbs.db
9898
__tests__/runner/*
9999
lib/**/*
100100

101-
.build-dev.npm
101+
.build-dev.npm
102+
code-coverage-results.md
103+
dist/artifacts

‎Makefile

+13-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@ lint-fix: .build-dev.npm
1515

1616
act: act-schedule act-push
1717

18-
act-%: /usr/bin/gh /usr/local/bin/act build
19-
@act -s GITHUB_TOKEN="$(shell gh auth token)" --artifact-server-path /tmp/artifacts $(*)
18+
act-pull_request: /usr/bin/gh /usr/local/bin/act /usr/bin/glow build
19+
@act --bind -e __tests__/pull-request.json -s GITHUB_TOKEN="$(shell gh auth token)" --artifact-server-path /tmp/artifacts pull_request
20+
@glow code-coverage-results.md
21+
@rm -Rf code-coverage-results.md
22+
23+
act-%: /usr/bin/gh /usr/local/bin/act /usr/bin/glow build
24+
@act --bind -s GITHUB_TOKEN="$(shell gh auth token)" --artifact-server-path /tmp/artifacts $(*)
25+
@glow code-coverage-results.md
26+
@rm -Rf code-coverage-results.md
2027

2128
/usr/bin/gh:
2229
@echo "gh is not installed. Please install it from https://cli.github.com/"
@@ -26,6 +33,10 @@ act-%: /usr/bin/gh /usr/local/bin/act build
2633
@echo "act is not installed. Please install it from https://github.com/nektos/act"
2734
@exit 1
2835

36+
/usr/bin/glow:
37+
@echo "glow is not installed. Please install it from https://github.com/charmbracelet/glow"
38+
@exit 1
39+
2940
.build-dev.npm: package-lock.json package.json node_modules
3041
@npm update
3142
@touch $@

‎__tests__/utils.test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ test('getInputs', () => {
129129
artifactDownloadWorkflowNames: null,
130130
artifactName: 'coverage-%name%',
131131
negativeDifferenceBy: 'package',
132-
retention: undefined
132+
retention: undefined,
133+
//This is a cheat
134+
withBaseCoverageTemplate: f.withBaseCoverageTemplate,
135+
withoutBaseCoverageTemplate: f.withoutBaseCoverageTemplate
133136
})
134137
})

‎action.yml

+7
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ inputs:
5454
retention_days:
5555
description: 'The number of days to retain uploaded artifacts. When `undefined` (default), the default retention period for the repository is used.'
5656
required: false
57+
without_base_coverage_template:
58+
description: 'Path to custom template without base coverage'
59+
required: false
60+
with_base_coverage_template:
61+
description: 'Path to custom template with base coverage'
62+
required: false
63+
5764

5865
outputs:
5966
file:

‎dist/index.js

+9,712-1,311
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/index.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/licenses.txt

+55
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/with-base-coverage.hbs

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/without-base-coverage.hbs

+16
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package-lock.json

+49-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "code-coverage-report-action",
3-
"version": "3.0.0",
3+
"version": "4.0.0",
44
"description": "",
5-
"main": "lib/main.js",
5+
"main": "dist/index.js",
66
"private": true,
77
"scripts": {
88
"format": "prettier --write 'src/**/*.ts'",
@@ -12,9 +12,7 @@
1212
"package": "ncc build src/main.ts --source-map --license licenses.txt",
1313
"watch": "ncc build src/main.ts --watch",
1414
"test": "jest",
15-
"all": "npm run format && npm run lint && npm run package && npm test",
16-
"act": "act --artifact-server-path /tmp/artifacts -s GITHUB_TOKEN --env GITHUB_STEP_SUMMARY=/dev/stdout",
17-
"act:pr": "act pull_request --artifact-server-path /tmp/artifacts -s GITHUB_TOKEN --env GITHUB_STEP_SUMMARY=/dev/stdout -e __tests__/pull-request.json"
15+
"all": "npm run format && npm run lint && npm run package && npm test"
1816
},
1917
"repository": {
2018
"type": "git",
@@ -32,7 +30,8 @@
3230
"@actions/core": "^1.10.1",
3331
"@actions/github": "^6.0.0",
3432
"adm-zip": "^0.5.10",
35-
"fast-xml-parser": "^4.3.2"
33+
"fast-xml-parser": "^4.3.2",
34+
"handlebars": "^4.7.8"
3635
},
3736
"devDependencies": {
3837
"@tsconfig/node20": "^20.1.2",

‎src/interfaces.ts

+17
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,25 @@ export interface Inputs {
2424
artifactName: string
2525
negativeDifferenceBy: string
2626
retention: number | undefined
27+
withBaseCoverageTemplate: string
28+
withoutBaseCoverageTemplate: string
2729
}
2830

2931
export interface Files {
3032
[key: string]: CoverageFile
3133
}
34+
35+
export interface HandlebarContextCoverage {
36+
package: string
37+
base_coverage: string
38+
new_coverage?: string
39+
difference?: string
40+
}
41+
42+
export interface HandlebarContext {
43+
coverage_badge?: string
44+
minimum_allowed_coverage?: string
45+
new_coverage?: string
46+
coverage: HandlebarContextCoverage[]
47+
overall_coverage: HandlebarContextCoverage
48+
}

‎src/main.ts

+97-94
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ import {
88
roundPercentage,
99
uploadArtifacts
1010
} from './utils'
11-
import {Coverage} from './interfaces'
11+
import {
12+
Coverage,
13+
HandlebarContext,
14+
HandlebarContextCoverage
15+
} from './interfaces'
1216
import {writeFile} from 'fs/promises'
1317
import path from 'path'
18+
import * as handlebars from 'handlebars'
19+
import {readFile} from 'node:fs/promises'
1420

1521
async function run(): Promise<void> {
1622
try {
@@ -108,61 +114,10 @@ async function generateMarkdown(
108114
fileCoverageWarningMax,
109115
badge,
110116
markdownFilename,
111-
negativeDifferenceBy
117+
negativeDifferenceBy,
118+
withBaseCoverageTemplate,
119+
withoutBaseCoverageTemplate
112120
} = getInputs()
113-
114-
const baseMap = Object.entries(headCoverage.files).map(([hash, file]) => {
115-
if (baseCoverage === null) {
116-
return [
117-
file.relative,
118-
`${colorizePercentageByThreshold(
119-
file.coverage,
120-
fileCoverageWarningMax,
121-
fileCoverageErrorMin
122-
)}`
123-
]
124-
}
125-
126-
const baseCoveragePercentage = baseCoverage.files[hash]
127-
? baseCoverage.files[hash].coverage
128-
: 0
129-
130-
const differencePercentage = baseCoveragePercentage
131-
? roundPercentage(file.coverage - baseCoveragePercentage)
132-
: roundPercentage(file.coverage)
133-
134-
if (
135-
failOnNegativeDifference &&
136-
negativeDifferenceBy === 'package' &&
137-
differencePercentage !== null &&
138-
differencePercentage < 0
139-
) {
140-
core.setFailed(
141-
`${headCoverage.files[hash].relative} coverage difference was ${differencePercentage}%`
142-
)
143-
}
144-
145-
return [
146-
file.relative,
147-
`${colorizePercentageByThreshold(
148-
baseCoveragePercentage,
149-
fileCoverageWarningMax,
150-
fileCoverageErrorMin
151-
)}`,
152-
`${colorizePercentageByThreshold(
153-
file.coverage,
154-
fileCoverageWarningMax,
155-
fileCoverageErrorMin
156-
)}`,
157-
colorizePercentageByThreshold(differencePercentage)
158-
]
159-
})
160-
161-
const map = baseMap.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
162-
163-
// Add a "summary row" showing changes in overall overage.
164-
map.push(await addOverallRow(headCoverage, baseCoverage))
165-
166121
const overallDifferencePercentage = baseCoverage
167122
? roundPercentage(headCoverage.coverage - baseCoverage.coverage)
168123
: null
@@ -201,36 +156,84 @@ async function generateMarkdown(
201156
color = 'green'
202157
}
203158

204-
const summary = core.summary.addHeading('Code Coverage Report')
205-
206-
const headers =
159+
const templatePath =
207160
baseCoverage === null
208-
? [
209-
{data: 'Package', header: true},
210-
{data: 'Coverage', header: true}
211-
]
212-
: [
213-
{data: 'Package', header: true},
214-
{data: 'Base Coverage', header: true},
215-
{data: 'New Coverage', header: true},
216-
{data: 'Difference', header: true}
217-
]
161+
? withoutBaseCoverageTemplate
162+
: withBaseCoverageTemplate
163+
164+
if (!(await checkFileExists(templatePath))) {
165+
core.setFailed(`Unable to access template ${templatePath}`)
166+
return
167+
}
168+
169+
const contents = await readFile(templatePath, {
170+
encoding: 'utf8'
171+
})
172+
const compiledTemplate = handlebars.compile(contents)
173+
174+
const context: HandlebarContext = {
175+
minimum_allowed_coverage: `${overallCoverageFailThreshold}%`,
176+
new_coverage: `${headCoverage.coverage}%`,
177+
coverage: Object.entries(headCoverage.files)
178+
.map(([hash, file]) => {
179+
if (baseCoverage === null) {
180+
return {
181+
package: file.relative,
182+
base_coverage: `${colorizePercentageByThreshold(
183+
file.coverage,
184+
fileCoverageWarningMax,
185+
fileCoverageErrorMin
186+
)}`
187+
}
188+
}
189+
190+
const baseCoveragePercentage = baseCoverage.files[hash]
191+
? baseCoverage.files[hash].coverage
192+
: 0
193+
194+
const differencePercentage = baseCoveragePercentage
195+
? roundPercentage(file.coverage - baseCoveragePercentage)
196+
: roundPercentage(file.coverage)
197+
198+
if (
199+
failOnNegativeDifference &&
200+
negativeDifferenceBy === 'package' &&
201+
differencePercentage !== null &&
202+
differencePercentage < 0
203+
) {
204+
core.setFailed(
205+
`${headCoverage.files[hash].relative} coverage difference was ${differencePercentage}%`
206+
)
207+
}
208+
209+
return {
210+
package: file.relative,
211+
base_coverage: `${colorizePercentageByThreshold(
212+
baseCoveragePercentage,
213+
fileCoverageWarningMax,
214+
fileCoverageErrorMin
215+
)}`,
216+
new_coverage: `${colorizePercentageByThreshold(
217+
file.coverage,
218+
fileCoverageWarningMax,
219+
fileCoverageErrorMin
220+
)}`,
221+
difference: colorizePercentageByThreshold(differencePercentage)
222+
}
223+
})
224+
.sort((a, b) =>
225+
a.package < b.package ? -1 : a.package > b.package ? 1 : 0
226+
),
227+
overall_coverage: addOverallRow(headCoverage, baseCoverage)
228+
}
218229

219230
if (badge) {
220-
summary.addImage(
221-
`https://img.shields.io/badge/${encodeURIComponent(
222-
`Code Coverage-${headCoverage.coverage}%-${color}`
223-
)}?style=for-the-badge`,
224-
'Code Coverage'
225-
)
231+
context.coverage_badge = `https://img.shields.io/badge/Code Coverage-${headCoverage.coverage}%-${color}?style=for-the-badge`
226232
}
227233

228-
summary
229-
.addTable([headers, ...map])
230-
.addBreak()
231-
.addRaw(
232-
`<i>Minimum allowed coverage is</i> <code>${overallCoverageFailThreshold}%</code>, this run produced</i> <code>${headCoverage.coverage}%</code>`
233-
)
234+
const markdown = compiledTemplate(context)
235+
236+
const summary = core.summary.addRaw(markdown)
234237

235238
//If this is run after write the buffer is empty
236239
core.info(`Writing results to ${markdownFilename}.md`)
@@ -245,41 +248,41 @@ async function generateMarkdown(
245248
/**
246249
* Generate overall coverage row
247250
*/
248-
async function addOverallRow(
251+
function addOverallRow(
249252
headCoverage: Coverage,
250253
baseCoverage: Coverage | null = null
251-
): Promise<string[]> {
254+
): HandlebarContextCoverage {
252255
const {overallCoverageFailThreshold} = getInputs()
253256

254257
const overallDifferencePercentage = baseCoverage
255258
? roundPercentage(headCoverage.coverage - baseCoverage.coverage)
256259
: null
257260

258261
if (baseCoverage === null) {
259-
return [
260-
'Overall Coverage',
261-
`${colorizePercentageByThreshold(
262+
return {
263+
package: 'Overall Coverage',
264+
base_coverage: `${colorizePercentageByThreshold(
262265
headCoverage.coverage,
263266
0,
264267
overallCoverageFailThreshold
265268
)}`
266-
]
269+
}
267270
}
268271

269-
return [
270-
'<b>Overall Coverage</b>',
271-
`<b>${colorizePercentageByThreshold(
272+
return {
273+
package: 'Overall Coverage',
274+
base_coverage: `${colorizePercentageByThreshold(
272275
baseCoverage.coverage,
273276
0,
274277
overallCoverageFailThreshold
275-
)}</b>`,
276-
`<b>${colorizePercentageByThreshold(
278+
)}`,
279+
new_coverage: `${colorizePercentageByThreshold(
277280
headCoverage.coverage,
278281
0,
279282
overallCoverageFailThreshold
280-
)}</b>`,
281-
`<b>${colorizePercentageByThreshold(overallDifferencePercentage)}</b>`
282-
]
283+
)}`,
284+
difference: `${colorizePercentageByThreshold(overallDifferencePercentage)}`
285+
}
283286
}
284287

285288
run()

‎src/utils.ts

+10-1
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,13 @@ export function getInputs(): Inputs {
366366
? tempArtifactDownloadWorkflowNames.split(',').map(n => n.trim())
367367
: null
368368

369+
const withoutBaseCoverageTemplate =
370+
core.getInput('without_base_coverage_template') ||
371+
`${__dirname}/../templates/without-base-coverage.hbs`
372+
const withBaseCoverageTemplate =
373+
core.getInput('with_base_coverage_template') ||
374+
`${__dirname}/../templates/with-base-coverage.hbs`
375+
369376
inputs = {
370377
token,
371378
filename,
@@ -378,7 +385,9 @@ export function getInputs(): Inputs {
378385
artifactDownloadWorkflowNames,
379386
artifactName,
380387
negativeDifferenceBy,
381-
retention: retentionDays
388+
retention: retentionDays,
389+
withoutBaseCoverageTemplate,
390+
withBaseCoverageTemplate
382391
}
383392

384393
return inputs

‎templates/with-base-coverage.hbs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Code Coverage Report
2+
3+
{{#if coverage_badge}}
4+
![Code Coverage]({{coverage_badge}})
5+
6+
{{/if}}
7+
| Package | Base Coverage | New Coverage | Difference |
8+
| ------- | ------------- | ------------ | ---------- |
9+
{{#each coverage}}
10+
| {{package}} | {{base_coverage}} | {{new_coverage}} | {{difference}} |
11+
{{/each}}
12+
{{#with overall_coverage}}
13+
| **{{package}}** | **{{base_coverage}}** | **{{new_coverage}}** | **{{difference}}** |
14+
{{/with}}
15+
16+
_Minimum allowed coverage is_ `{{minimum_allowed_coverage}}`_, this run produced_ `{{new_coverage}}`

‎templates/without-base-coverage.hbs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Code Coverage Report
2+
3+
{{#if coverage_badge}}
4+
![Code Coverage]({{coverage_badge}})
5+
6+
{{/if}}
7+
| Package | Coverage |
8+
| ------- | ------------- |
9+
{{#each coverage}}
10+
| {{package}} | {{base_coverage}} |
11+
{{/each}}
12+
{{#with overall_coverage}}
13+
| **{{package}}** | **{{base_coverage}}** |
14+
{{/with}}
15+
16+
_Minimum allowed coverage is_ `{{minimum_allowed_coverage}}`_, this run produced_ `{{new_coverage}}`

0 commit comments

Comments
 (0)
Please sign in to comment.