@toptal/davinci-monorepo 8.1.4-alpha-CRT-5891-create-coverage-command-39ac7ea2.4 → 8.1.4-alpha-CRT-5891-create-coverage-command-0a4adac5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/docs/coverage.md +25 -0
- package/package.json +3 -3
- package/src/commands/codeowners/core/owners-info/get-owners-summary.js +1 -1
- package/src/commands/codeowners/core/utils/get-percentage.js +2 -0
- package/src/commands/codeowners/index.js +2 -2
- package/src/commands/coverage-command/generate-team-coverage.js +36 -55
- package/src/commands/coverage-command/group-coverage-by-codeowners.js +11 -36
- package/src/commands/coverage-command/index.js +5 -1
- package/src/commands/coverage-command/services/get-unique-teams/get-unique-teams.js +11 -0
- package/src/commands/coverage-command/services/get-unique-teams/get-unique-teams.test.js +40 -0
- package/src/commands/coverage-command/services/sanitize-team-name/sanitize-team-name.test.js +13 -0
- package/src/commands/codeowners/core/utils/get-persentage.js +0 -5
package/README.md
CHANGED
|
@@ -12,3 +12,4 @@ Use it by installing `yarn add @toptal/davinci-monorepo` in your project.
|
|
|
12
12
|
- [`metrics -m, --metric package-afferent-relations`](./docs/metrics.md): calculate package Afferent relations
|
|
13
13
|
- [`graph generate`](./docs/graph-generate.md): generate dependency graph into `monorepo-graph.svg` file
|
|
14
14
|
- [`codeowners`](./docs/codeowners.md): show list of tools available for code ownership analysis
|
|
15
|
+
- [`coverage`](./docs/coverage.md): show list of code coverage commands
|
package/docs/coverage.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
### Code coverage commands
|
|
2
|
+
|
|
3
|
+
Streamline code coverage analysis and reporting by organizing coverage reports based on code owners and generating team-specific coverage reports, enabling teams to track and assess code quality and testing coverage more effectively.
|
|
4
|
+
|
|
5
|
+
#### `group-coverage-by-codeowners`
|
|
6
|
+
|
|
7
|
+
Reads code coverage reports for Jest and Cypress, groups the coverage data based on code owners, and writes separate coverage reports for each team
|
|
8
|
+
|
|
9
|
+
The command utilizes the Codeowners class, which is responsible for identifying the code owners associated with each file.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
codeowners group-coverage-by-codeowners
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
#### `generage-team-coverage`
|
|
16
|
+
|
|
17
|
+
Generates team-specific coverage reports by copying and processing coverage data from Jest and Cypress reports. The resulting coverage reports provide insights into the code coverage metrics for each team.
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
coverage generage-team-coverage [options]
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-r, --repository <repository_name> specify repository name
|
|
24
|
+
-o, --output-file-name <output_file_name> specify output file name
|
|
25
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toptal/davinci-monorepo",
|
|
3
|
-
"version": "8.1.4-alpha-CRT-5891-create-coverage-command-
|
|
3
|
+
"version": "8.1.4-alpha-CRT-5891-create-coverage-command-0a4adac5.7+0a4adac5",
|
|
4
4
|
"description": "Monorepo utility tools",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@nodelib/fs.walk": "^1.2.6",
|
|
37
37
|
"@oclif/core": "^1.16.1",
|
|
38
|
-
"@toptal/davinci-cli-shared": "2.3.1-alpha-CRT-5891-create-coverage-command-
|
|
38
|
+
"@toptal/davinci-cli-shared": "2.3.1-alpha-CRT-5891-create-coverage-command-0a4adac5.39+0a4adac5",
|
|
39
39
|
"chalk": "^4.1.2",
|
|
40
40
|
"codeowners": "5.1.1",
|
|
41
41
|
"commander": "^10.0.0",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@jest/globals": "^29.4.2"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "0a4adac5650c666f07b97522a76b2f30dd604738"
|
|
59
59
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign, id-length*/
|
|
2
2
|
import { NO_OWNER } from '../../config.js'
|
|
3
3
|
import formatOwnerName from '../utils/format-owner-name.js'
|
|
4
|
-
import getPercentage from '../utils/get-
|
|
4
|
+
import { getPercentage } from '../utils/get-percentage.js'
|
|
5
5
|
import totalFilesComparator from '../utils/total-files-comparator.js'
|
|
6
6
|
|
|
7
7
|
export const getSingleOwnerSummary = (
|
|
@@ -5,7 +5,7 @@ import listCommand from './commands/list.js'
|
|
|
5
5
|
import ownersInformationCommand from './commands/owners-information.js'
|
|
6
6
|
import packagesInformationCommand from './commands/packages-information.js'
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const codeownersCommand = program => {
|
|
9
9
|
const codeowners = program
|
|
10
10
|
.createCommand('codeowners')
|
|
11
11
|
.description(
|
|
@@ -22,4 +22,4 @@ const codeowners = program => {
|
|
|
22
22
|
return codeowners
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export default
|
|
25
|
+
export default codeownersCommand
|
|
@@ -12,45 +12,55 @@ import {
|
|
|
12
12
|
JEST_COVERAGE_PATH,
|
|
13
13
|
TEAMS_COVERAGE_PATH,
|
|
14
14
|
} from './config.js'
|
|
15
|
+
import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
|
|
15
16
|
|
|
16
17
|
const workspaceRoot = getWorkspaceRoot()
|
|
17
18
|
|
|
18
19
|
const sanitizeNycPercentage = percentage =>
|
|
19
20
|
percentage === 'Unknown' ? 0 : percentage
|
|
20
21
|
|
|
22
|
+
const getCoverageSummary = (type, teamPath) => {
|
|
23
|
+
const result = {}
|
|
24
|
+
|
|
25
|
+
const coverageSummary = JSON.parse(
|
|
26
|
+
fs.readFileSync(`${teamPath}/${type}/coverage-summary.json`, 'utf8')
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
result[`${type}_lines_coverage`] = sanitizeNycPercentage(
|
|
30
|
+
coverageSummary.total.lines.pct
|
|
31
|
+
)
|
|
32
|
+
result[`${type}_statements_coverage`] = sanitizeNycPercentage(
|
|
33
|
+
coverageSummary.total.statements.pct
|
|
34
|
+
)
|
|
35
|
+
result[`${type}_functions_coverage`] = sanitizeNycPercentage(
|
|
36
|
+
coverageSummary.total.functions.pct
|
|
37
|
+
)
|
|
38
|
+
result[`${type}_branches_coverage`] = sanitizeNycPercentage(
|
|
39
|
+
coverageSummary.total.branches.pct
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
21
45
|
// eslint-disable-next-line max-statements
|
|
22
46
|
const generateTeamCoverage = async options => {
|
|
23
47
|
print.header('Generating team coverage')
|
|
24
48
|
|
|
25
49
|
const codeowners = new Codeowners(workspaceRoot)
|
|
26
|
-
|
|
27
|
-
const ownerEntries = codeowners.ownerEntries
|
|
28
|
-
const uniqueTeams = ownerEntries
|
|
29
|
-
.flatMap(entry => entry.usernames)
|
|
30
|
-
// eslint-disable-next-line id-length
|
|
31
|
-
.filter((v, i, a) => a.indexOf(v) === i)
|
|
32
|
-
|
|
33
|
-
print.grey('Teams: ', uniqueTeams)
|
|
34
|
-
|
|
35
|
-
const teams = uniqueTeams
|
|
50
|
+
const teams = getUniqueTeams(codeowners.ownerEntries)
|
|
36
51
|
|
|
37
52
|
await execa.command(`rm -rf ${TEAMS_COVERAGE_PATH}`)
|
|
38
53
|
await execa.command(`mkdir ${TEAMS_COVERAGE_PATH}`)
|
|
39
54
|
|
|
40
55
|
for await (const team of teams) {
|
|
56
|
+
print.grey('Team coverage: ', team)
|
|
57
|
+
|
|
41
58
|
const sanitizedTeam = sanitizeTeamName(team)
|
|
42
59
|
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
43
60
|
|
|
44
61
|
await execa.command(`mkdir ${sanitizedTeamPath}`)
|
|
45
62
|
await execa.command(`mkdir ${sanitizedTeamPath}/jest`)
|
|
46
63
|
await execa.command(`mkdir ${sanitizedTeamPath}/cypress`)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
for await (const team of teams) {
|
|
50
|
-
print.grey('Team coverage: ', team)
|
|
51
|
-
|
|
52
|
-
const sanitizedTeam = sanitizeTeamName(team)
|
|
53
|
-
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
54
64
|
|
|
55
65
|
await execa.command(
|
|
56
66
|
`cp ${JEST_COVERAGE_PATH}/coverage-final-${sanitizedTeam}.json ${sanitizedTeamPath}/jest/coverage-final.json`
|
|
@@ -74,49 +84,20 @@ const generateTeamCoverage = async options => {
|
|
|
74
84
|
const sanitizedTeam = sanitizeTeamName(team)
|
|
75
85
|
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
76
86
|
|
|
87
|
+
const jestCoverageSummary = getCoverageSummary('jest', sanitizedTeamPath)
|
|
88
|
+
const cypressCoverageSummary = getCoverageSummary(
|
|
89
|
+
'cypress',
|
|
90
|
+
sanitizedTeamPath
|
|
91
|
+
)
|
|
92
|
+
|
|
77
93
|
const result = {
|
|
78
94
|
created_at,
|
|
79
95
|
team,
|
|
80
96
|
repo: options.repository,
|
|
97
|
+
...jestCoverageSummary,
|
|
98
|
+
...cypressCoverageSummary,
|
|
81
99
|
}
|
|
82
100
|
|
|
83
|
-
const jestCoverageSummary = JSON.parse(
|
|
84
|
-
fs.readFileSync(`${sanitizedTeamPath}/jest/coverage-summary.json`, 'utf8')
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
result.jest_lines_coverage = sanitizeNycPercentage(
|
|
88
|
-
jestCoverageSummary.total.lines.pct
|
|
89
|
-
)
|
|
90
|
-
result.jest_statements_coverage = sanitizeNycPercentage(
|
|
91
|
-
jestCoverageSummary.total.statements.pct
|
|
92
|
-
)
|
|
93
|
-
result.jest_functions_coverage = sanitizeNycPercentage(
|
|
94
|
-
jestCoverageSummary.total.functions.pct
|
|
95
|
-
)
|
|
96
|
-
result.jest_branches_coverage = sanitizeNycPercentage(
|
|
97
|
-
jestCoverageSummary.total.branches.pct
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
const cypressCoverageSummary = JSON.parse(
|
|
101
|
-
fs.readFileSync(
|
|
102
|
-
`${sanitizedTeamPath}/cypress/coverage-summary.json`,
|
|
103
|
-
'utf8'
|
|
104
|
-
)
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
result.cypress_lines_coverage = sanitizeNycPercentage(
|
|
108
|
-
cypressCoverageSummary.total.lines.pct
|
|
109
|
-
)
|
|
110
|
-
result.cypress_statements_coverage = sanitizeNycPercentage(
|
|
111
|
-
cypressCoverageSummary.total.statements.pct
|
|
112
|
-
)
|
|
113
|
-
result.cypress_functions_coverage = sanitizeNycPercentage(
|
|
114
|
-
cypressCoverageSummary.total.functions.pct
|
|
115
|
-
)
|
|
116
|
-
result.cypress_branches_coverage = sanitizeNycPercentage(
|
|
117
|
-
cypressCoverageSummary.total.branches.pct
|
|
118
|
-
)
|
|
119
|
-
|
|
120
101
|
results.push(result)
|
|
121
102
|
}
|
|
122
103
|
|
|
@@ -126,7 +107,7 @@ const generateTeamCoverage = async options => {
|
|
|
126
107
|
export const generateTeamCoverageCommand = program =>
|
|
127
108
|
program
|
|
128
109
|
.command('generate-team-coverage')
|
|
129
|
-
.description('
|
|
110
|
+
.description('Generate team-specific coverage reports')
|
|
130
111
|
.requiredOption(
|
|
131
112
|
'-r, --repository <repository_name>',
|
|
132
113
|
'specify repository name'
|
|
@@ -7,40 +7,23 @@ import getWorkspaceRoot from 'find-yarn-workspace-root'
|
|
|
7
7
|
import Codeowners from '../codeowners/core/codeowners/codeowners.js'
|
|
8
8
|
import { CYPRESS_COVERAGE_PATH, JEST_COVERAGE_PATH } from './config.js'
|
|
9
9
|
import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
|
|
10
|
+
import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
|
|
10
11
|
|
|
11
12
|
const workspaceRoot = getWorkspaceRoot()
|
|
12
13
|
|
|
13
|
-
const
|
|
14
|
-
const jestCoverageReportPath = path.join(
|
|
15
|
-
workspaceRoot,
|
|
16
|
-
JEST_COVERAGE_PATH,
|
|
17
|
-
'coverage-final-jest.json'
|
|
18
|
-
)
|
|
19
|
-
|
|
14
|
+
const readCoverageReport = reportPathName => {
|
|
15
|
+
const jestCoverageReportPath = path.join(workspaceRoot, reportPathName)
|
|
20
16
|
const data = fs.readFileSync(jestCoverageReportPath, 'utf8')
|
|
21
17
|
|
|
22
18
|
return JSON.parse(data)
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
const readCypressCoverageReport = () => {
|
|
26
|
-
const cypressCoverageReportPath = path.join(
|
|
27
|
-
workspaceRoot,
|
|
28
|
-
CYPRESS_COVERAGE_PATH,
|
|
29
|
-
'coverage-final-cypress.json'
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
const data = fs.readFileSync(cypressCoverageReportPath, 'utf8')
|
|
33
|
-
|
|
34
|
-
return JSON.parse(data)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
21
|
const splitReportIntoTeams = ({ report, teams, codeowners, coveragePath }) => {
|
|
38
22
|
const teamsReport = {}
|
|
39
23
|
|
|
40
24
|
Object.entries(report).forEach(([reportPathKey, reportValue]) => {
|
|
41
25
|
const filePath = reportValue.path
|
|
42
26
|
const relativeWorkspacePath = path.relative(workspaceRoot, filePath)
|
|
43
|
-
|
|
44
27
|
const owners = codeowners.getOwner(relativeWorkspacePath)
|
|
45
28
|
|
|
46
29
|
owners.forEach(owner => {
|
|
@@ -65,26 +48,18 @@ const splitReportIntoTeams = ({ report, teams, codeowners, coveragePath }) => {
|
|
|
65
48
|
})
|
|
66
49
|
}
|
|
67
50
|
|
|
68
|
-
const getAllTeams = codeowners => {
|
|
69
|
-
const ownerEntries = codeowners.ownerEntries
|
|
70
|
-
const uniqueTeams = ownerEntries
|
|
71
|
-
.flatMap(entry => entry.usernames)
|
|
72
|
-
// eslint-disable-next-line id-length
|
|
73
|
-
.filter((v, i, a) => a.indexOf(v) === i)
|
|
74
|
-
|
|
75
|
-
print.grey('Teams: ', uniqueTeams)
|
|
76
|
-
|
|
77
|
-
return uniqueTeams
|
|
78
|
-
}
|
|
79
|
-
|
|
80
51
|
const groupCoverageByCodeowners = async () => {
|
|
81
52
|
print.header('Grouping coverage by codeowners')
|
|
82
53
|
|
|
83
|
-
const jestReport =
|
|
84
|
-
|
|
54
|
+
const jestReport = readCoverageReport(
|
|
55
|
+
path.join(JEST_COVERAGE_PATH, 'coverage-final-jest.json')
|
|
56
|
+
)
|
|
57
|
+
const cypressReport = readCoverageReport(
|
|
58
|
+
path.join(CYPRESS_COVERAGE_PATH, 'coverage-final-cypress.json')
|
|
59
|
+
)
|
|
85
60
|
|
|
86
61
|
const codeowners = new Codeowners(workspaceRoot)
|
|
87
|
-
const teams =
|
|
62
|
+
const teams = getUniqueTeams(codeowners.ownerEntries)
|
|
88
63
|
|
|
89
64
|
splitReportIntoTeams({
|
|
90
65
|
report: jestReport,
|
|
@@ -103,5 +78,5 @@ const groupCoverageByCodeowners = async () => {
|
|
|
103
78
|
export const groupCoverageByCodeownersCommand = program =>
|
|
104
79
|
program
|
|
105
80
|
.command('group-coverage-by-codeowners')
|
|
106
|
-
.description('
|
|
81
|
+
.description('Group the coverage data based on code owners')
|
|
107
82
|
.action(groupCoverageByCodeowners)
|
|
@@ -2,7 +2,11 @@ import { generateTeamCoverageCommand } from './generate-team-coverage.js'
|
|
|
2
2
|
import { groupCoverageByCodeownersCommand } from './group-coverage-by-codeowners.js'
|
|
3
3
|
|
|
4
4
|
export const coverageCommand = program => {
|
|
5
|
-
const coverage = program
|
|
5
|
+
const coverage = program
|
|
6
|
+
.createCommand('coverage')
|
|
7
|
+
.description(
|
|
8
|
+
'Provides code coverage analysis, including organizing coverage reports by code owners and generating team-specific reports'
|
|
9
|
+
)
|
|
6
10
|
|
|
7
11
|
groupCoverageByCodeownersCommand(coverage)
|
|
8
12
|
generateTeamCoverageCommand(coverage)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { print } from '@toptal/davinci-cli-shared'
|
|
2
|
+
|
|
3
|
+
export const getUniqueTeams = teams => {
|
|
4
|
+
const uniqueTeams = teams
|
|
5
|
+
.flatMap(entry => entry.usernames)
|
|
6
|
+
.filter((teamName, index, allTeams) => allTeams.indexOf(teamName) === index)
|
|
7
|
+
|
|
8
|
+
print.grey('Teams: ', uniqueTeams)
|
|
9
|
+
|
|
10
|
+
return uniqueTeams
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
|
+
import { jest } from '@jest/globals'
|
|
3
|
+
|
|
4
|
+
import { getUniqueTeams } from './get-unique-teams'
|
|
5
|
+
|
|
6
|
+
jest.unstable_mockModule('@toptal/davinci-cli-shared', () => ({
|
|
7
|
+
print: {
|
|
8
|
+
grey: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const { print } = await import('@toptal/davinci-cli-shared')
|
|
13
|
+
|
|
14
|
+
describe('getUniqueTeams', () => {
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
print.grey.mockImplementation(jest.fn())
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it.each([
|
|
20
|
+
[
|
|
21
|
+
[
|
|
22
|
+
{ usernames: ['team1', 'team2'] },
|
|
23
|
+
{ usernames: ['team3', 'team4'] },
|
|
24
|
+
{ usernames: ['team1', 'team5'] },
|
|
25
|
+
],
|
|
26
|
+
['team1', 'team2', 'team3', 'team4', 'team5'],
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
[
|
|
30
|
+
{ usernames: ['team1', 'team2', 'team2', 'team3'] },
|
|
31
|
+
{ usernames: ['team4', 'team1', 'team4', 'team5'] },
|
|
32
|
+
],
|
|
33
|
+
['team1', 'team2', 'team3', 'team4', 'team5'],
|
|
34
|
+
],
|
|
35
|
+
])('returns the unique teams for the given input', (teams, expected) => {
|
|
36
|
+
const result = getUniqueTeams(teams)
|
|
37
|
+
|
|
38
|
+
expect(result).toEqual(expected)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { sanitizeTeamName } from './sanitize-team-name'
|
|
2
|
+
|
|
3
|
+
describe('sanitizeTeamName', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
['@package/frontend-experience-eng', 'package-frontend-experience-eng'],
|
|
6
|
+
['@package/screening-ops-eng', 'package-screening-ops-eng'],
|
|
7
|
+
['@package/client-retention-eng', 'package-client-retention-eng'],
|
|
8
|
+
])('sanitizes the team name correctly', (input, expected) => {
|
|
9
|
+
const result = sanitizeTeamName(input)
|
|
10
|
+
|
|
11
|
+
expect(result).toBe(expected)
|
|
12
|
+
})
|
|
13
|
+
})
|