Skip to content

Commit 02f8d89

Browse files
authored
feat: adding check against readme (#14)
```yml - name: Ensure coverage has not dropped 📈 run: npx set-gh-status --check-against-readme env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ```
1 parent 7061bd0 commit 02f8d89

File tree

6 files changed

+143
-39
lines changed

6 files changed

+143
-39
lines changed

.github/workflows/ci.yml

+9-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ jobs:
1717
node bin/only-covered main1.js main2.js
1818
1919
- name: Check totals 🛡
20-
run: node bin/check-total --min 90
21-
22-
- name: Update code coverage badge 🥇
23-
run: node bin/update-badge
20+
run: node bin/check-total --min 30
2421

2522
- name: Set commit status using REST
2623
# https://developer.github.com/v3/repos/statuses/
@@ -40,6 +37,14 @@ jobs:
4037
env:
4138
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4239

40+
- name: Check coverage change from README 📫
41+
run: node bin/set-gh-status --check-against-readme
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
45+
- name: Update code coverage badge 🥇
46+
run: node bin/update-badge
47+
4348
- name: Semantic Release 🚀
4449
uses: cycjimmy/semantic-release-action@v2
4550
env:

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ Which should show a commit status message like:
9898
9999
This script reads the code coverage summary from `coverage/coverage-summary.json` by default (you can specific a different file name using `--from` option) and posts the commit status, always passing for now.
100100

101+
If there is a coverage badge in the README file, you can add 2nd status check. This check will read the code coverage from the README file (by parsing the badge text), then will set a failing status check if the coverage dropped more than 1 percent. **Tip:** use this check on pull requests to ensure tests and code are updated together before merging.
102+
103+
```yaml
104+
- name: Ensure coverage has not dropped 📈
105+
run: npx set-gh-status --check-against-readme
106+
env:
107+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
108+
```
109+
110+
![Coverage diff](images/coverage-diff.png)
111+
101112
## Debug
102113

103114
To see verbose log messages, run with `DEBUG=check-code-coverage` environment variable

bin/set-gh-status.js

+54-3
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
const got = require('got')
55
const debug = require('debug')('check-code-coverage')
6-
const {readCoverage, toPercent} = require('..')
6+
const {readCoverage, toPercent, badge} = require('..')
77

88
const arg = require('arg')
99

1010
const args = arg({
11-
'--from': String // input json-summary filename, by default "coverage/coverage-summary.json"
11+
'--from': String, // input json-summary filename, by default "coverage/coverage-summary.json"
12+
'--check-against-readme': Boolean
1213
})
1314
debug('args: %o', args)
1415

@@ -46,6 +47,55 @@ async function setGitHubCommitStatus(options, envOptions) {
4647
}
4748
})
4849
console.log('response status: %d %s', res.statusCode, res.statusMessage)
50+
51+
if (options.checkAgainstReadme) {
52+
const readmePercent = badge.getCoverageFromReadme()
53+
if (typeof readmePercent !== 'number') {
54+
console.error('Could not get code coverage percentage from README')
55+
process.exit(1)
56+
}
57+
58+
if (pct > readmePercent) {
59+
console.log('coverage 📈 from %d% to %d%', readmePercent, pct)
60+
// @ts-ignore
61+
await got.post(url, {
62+
headers: {
63+
authorization: `Bearer ${envOptions.token}`
64+
},
65+
json: {
66+
context: 'code-coverage Δ',
67+
state: 'success',
68+
description: `went up from ${readmePercent}% to ${pct}%`
69+
}
70+
})
71+
} else if (Math.abs(pct - readmePercent) < 1) {
72+
console.log('coverage stayed the same %d% ~ %d%', readmePercent, pct)
73+
// @ts-ignore
74+
await got.post(url, {
75+
headers: {
76+
authorization: `Bearer ${envOptions.token}`
77+
},
78+
json: {
79+
context: 'code-coverage Δ',
80+
state: 'success',
81+
description: `stayed the same at ${pct}%`
82+
}
83+
})
84+
} else {
85+
console.log('coverage 📉 from %d% to %d%', readmePercent, pct)
86+
// @ts-ignore
87+
await got.post(url, {
88+
headers: {
89+
authorization: `Bearer ${envOptions.token}`
90+
},
91+
json: {
92+
context: 'code-coverage Δ',
93+
state: 'failure',
94+
description: `decreased from ${readmePercent}% to ${pct}%`
95+
}
96+
})
97+
}
98+
}
4999
}
50100

51101
function checkEnvVariables(env) {
@@ -68,7 +118,8 @@ function checkEnvVariables(env) {
68118
checkEnvVariables(process.env)
69119

70120
const options = {
71-
filename: args['--file']
121+
filename: args['--file'],
122+
checkAgainstReadme: args['--check-against-readme']
72123
}
73124
const envOptions = {
74125
token: process.env.GITHUB_TOKEN,

bin/update-badge.js

+8-31
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,14 @@ const path = require('path')
66
const fs = require('fs')
77
const os = require('os')
88
const arg = require('arg')
9-
const {readCoverage, toPercent} = require('..')
9+
const {readCoverage, toPercent, badge} = require('..')
1010

1111
const args = arg({
1212
'--from': String, // input json-summary filename, by default "coverage/coverage-summary.json"
1313
'--set': String // so we can convert "78%" into numbers ourselves
1414
})
1515
debug('args: %o', args)
1616

17-
const availableColors = ['red', 'yellow', 'green', 'brightgreen']
18-
19-
const availableColorsReStr = '(:?' + availableColors.join('|') + ')'
20-
21-
function getColor(coveredPercent) {
22-
if (coveredPercent < 60) {
23-
return 'red'
24-
}
25-
if (coveredPercent < 80) {
26-
return 'yellow'
27-
}
28-
if (coveredPercent < 90) {
29-
return 'green'
30-
}
31-
return 'brightgreen'
32-
}
33-
3417
function updateBadge(args) {
3518
let pct = 0
3619
if (args['--set']) {
@@ -47,21 +30,15 @@ function updateBadge(args) {
4730
const readmeText = fs.readFileSync(readmeFilename, 'utf8')
4831

4932
function replaceShield() {
50-
const color = getColor(pct)
51-
debug('for coverage %d% badge color "%s"', pct, color)
52-
if (!availableColors.includes(color)) {
53-
console.error('cannot pick code coverage color for %d%', pct)
54-
console.error('color "%s" is invalid', color)
55-
return readmeText
56-
}
57-
58-
// note, Shields.io escaped '-' with '--'
59-
const coverageRe = new RegExp(
60-
`https://img\\.shields\\.io/badge/code--coverage-\\d+%25-${availableColorsReStr}`,
61-
)
62-
const coverageBadge = `https://img.shields.io/badge/code--coverage-${pct}%25-${color}`
33+
const coverageRe = badge.getCoverageRe()
6334
debug('coverage regex: "%s"', coverageRe)
35+
36+
const coverageBadge = badge.getCoverageBadge(pct)
6437
debug('new coverage badge: "%s"', coverageBadge)
38+
if (!coverageBadge) {
39+
console.error('cannot form new badge for %d%', pct)
40+
return readmeText
41+
}
6542

6643
let found
6744
let updatedReadmeText = readmeText.replace(

images/coverage-diff.png

99.1 KB
Loading

src/index.js

+61-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @ts-check
22

33
const path = require('path')
4+
const fs = require('fs')
45
const debug = require('debug')('check-code-coverage')
56

67
/**
@@ -32,7 +33,66 @@ function toPercent(x) {
3233
return x
3334
}
3435

36+
const availableColors = ['red', 'yellow', 'green', 'brightgreen']
37+
38+
const availableColorsReStr = '(:?' + availableColors.join('|') + ')'
39+
40+
function getCoverageRe() {
41+
// note, Shields.io escaped '-' with '--'
42+
const coverageRe = new RegExp(
43+
`https://img\\.shields\\.io/badge/code--coverage-\\d+%25-${availableColorsReStr}`,
44+
)
45+
return coverageRe
46+
}
47+
48+
function getColor(coveredPercent) {
49+
if (coveredPercent < 60) {
50+
return 'red'
51+
}
52+
if (coveredPercent < 80) {
53+
return 'yellow'
54+
}
55+
if (coveredPercent < 90) {
56+
return 'green'
57+
}
58+
return 'brightgreen'
59+
}
60+
61+
function getCoverageBadge(pct) {
62+
const color = getColor(pct) || 'lightgrey'
63+
debug('for coverage %d% badge color "%s"', pct, color)
64+
65+
const coverageBadge = `https://img.shields.io/badge/code--coverage-${pct}%25-${color}`
66+
return coverageBadge
67+
}
68+
69+
function getCoverageFromReadme() {
70+
const readmeFilename = path.join(process.cwd(), 'README.md')
71+
const readmeText = fs.readFileSync(readmeFilename, 'utf8')
72+
73+
const coverageRe = new RegExp(
74+
`https://img\\.shields\\.io/badge/code--coverage-(\\d+)%25-${availableColorsReStr}`,
75+
)
76+
const matches = coverageRe.exec(readmeText)
77+
78+
if (!matches) {
79+
console.log('Could not find coverage badge in README')
80+
return
81+
}
82+
debug('coverage badge "%s" percentage "%s"', matches[0], matches[1])
83+
const pct = toPercent(parseFloat(matches[1]))
84+
debug('parsed percentage: %d', pct)
85+
return pct
86+
}
87+
3588
module.exports = {
3689
toPercent,
37-
readCoverage
90+
readCoverage,
91+
badge: {
92+
availableColors,
93+
availableColorsReStr,
94+
getCoverageFromReadme,
95+
getCoverageRe,
96+
getCoverageBadge
97+
}
3898
}

0 commit comments

Comments
 (0)