@toptal/davinci-monorepo 8.1.4-alpha-CRT-5891-create-coverage-command-e6e5bd51.7 → 8.1.4-alpha-CRT-5891-create-coverage-command-5071adf7.9
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/bin/davinci-monorepo.js +2 -2
- package/docs/coverage.md +0 -6
- package/package.json +3 -3
- package/src/commands/codeowners/commands/list.js +6 -10
- package/src/commands/codeowners/core/codeowners/codeowners.js +0 -1
- package/src/commands/codeowners/core/process-codebase.js +2 -10
- package/src/commands/codeowners/index.js +2 -0
- package/src/commands/codeowners/services/async-codeowners-import/async-codeowners-import.js +22 -0
- package/src/commands/coverage-command/create-generate-team-coverage-command.js +127 -0
- package/src/commands/coverage-command/create-group-coverage-by-codeowners-command.js +95 -0
- package/src/commands/coverage-command/index.js +7 -7
- package/src/commands/coverage-command/services/get-unique-teams/get-unique-teams.js +5 -4
- package/src/commands/coverage-command/services/sanitize-team-name/sanitize-team-name.js +2 -1
- package/src/index.js +2 -2
- package/src/commands/coverage-command/generate-team-coverage.js +0 -131
- package/src/commands/coverage-command/group-coverage-by-codeowners.js +0 -94
package/bin/davinci-monorepo.js
CHANGED
|
@@ -7,7 +7,7 @@ import { createGraphGenerateCommand } from '../src/commands/graph-generate.js'
|
|
|
7
7
|
import metricsCommand from '../src/commands/metrics.js'
|
|
8
8
|
// eslint-disable-next-line no-restricted-syntax
|
|
9
9
|
import codeownersCommand from '../src/commands/codeowners/index.js'
|
|
10
|
-
import {
|
|
10
|
+
import { createCoverageCommand } from '../src/commands/coverage-command/index.js'
|
|
11
11
|
|
|
12
12
|
cliEngine.loadCommands(
|
|
13
13
|
[
|
|
@@ -15,7 +15,7 @@ cliEngine.loadCommands(
|
|
|
15
15
|
createGraphGenerateCommand,
|
|
16
16
|
metricsCommand,
|
|
17
17
|
codeownersCommand,
|
|
18
|
-
|
|
18
|
+
createCoverageCommand,
|
|
19
19
|
],
|
|
20
20
|
'davinci-monorepo'
|
|
21
21
|
)
|
package/docs/coverage.md
CHANGED
|
@@ -4,18 +4,12 @@ Streamline code coverage analysis and reporting by organizing coverage reports b
|
|
|
4
4
|
|
|
5
5
|
#### `group-coverage-by-codeowners`
|
|
6
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
7
|
```sh
|
|
12
8
|
codeowners group-coverage-by-codeowners
|
|
13
9
|
```
|
|
14
10
|
|
|
15
11
|
#### `generage-team-coverage`
|
|
16
12
|
|
|
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
13
|
```sh
|
|
20
14
|
coverage generage-team-coverage [options]
|
|
21
15
|
|
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-5071adf7.9+5071adf7",
|
|
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-5071adf7.41+5071adf7",
|
|
39
39
|
"chalk": "^4.1.2",
|
|
40
40
|
"codeowners": "5.1.1",
|
|
41
41
|
"dependency-cruiser": "^12.5.0",
|
|
@@ -54,5 +54,5 @@
|
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@jest/globals": "^29.4.2"
|
|
56
56
|
},
|
|
57
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "5071adf786fc0f9eaf34275fe751c75f403dacfa"
|
|
58
58
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { asyncCodeownersImport } from '../services/async-codeowners-import/async-codeowners-import.js'
|
|
2
|
+
|
|
1
3
|
const list = program => {
|
|
2
4
|
return program
|
|
3
5
|
.command('list <paths...>')
|
|
@@ -9,19 +11,13 @@ const list = program => {
|
|
|
9
11
|
'CODEOWNERS'
|
|
10
12
|
)
|
|
11
13
|
.action(async (pathsToCheck, options) => {
|
|
12
|
-
let codeowners
|
|
13
14
|
const rootPath = process.cwd()
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
const codeowners = await asyncCodeownersImport(
|
|
17
|
+
rootPath,
|
|
18
|
+
options.codeownersFilename
|
|
16
19
|
)
|
|
17
20
|
|
|
18
|
-
// instantiate new Codeowners obj
|
|
19
|
-
try {
|
|
20
|
-
codeowners = new CodeOwnersTool(rootPath, options.codeownersFilename)
|
|
21
|
-
} catch (e) {
|
|
22
|
-
console.error(e.message)
|
|
23
|
-
process.exit(1)
|
|
24
|
-
}
|
|
25
21
|
let owners = []
|
|
26
22
|
|
|
27
23
|
// iterates over all paths and returns owners
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
1
|
import path from 'path'
|
|
3
2
|
|
|
4
3
|
import { PackageInfo } from './package-info/package-info.js'
|
|
@@ -6,6 +5,7 @@ import readFiles from './read-files.js'
|
|
|
6
5
|
import { NO_OWNER } from '../config.js'
|
|
7
6
|
import fileNameToPackage from './utils/file-name-to-package.js'
|
|
8
7
|
import OwnerToPackages from './owners-info/owner-to-packages.js'
|
|
8
|
+
import { asyncCodeownersImport } from '../services/async-codeowners-import/async-codeowners-import.js'
|
|
9
9
|
|
|
10
10
|
const processGroupedByPackage = (
|
|
11
11
|
groupedByPackageName,
|
|
@@ -39,17 +39,9 @@ const processGroupedByOwner = (groupedByOwnerName, owners, packageName) => {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const processCodeBase = async options => {
|
|
42
|
-
let codeowners
|
|
43
|
-
|
|
44
42
|
const rootPath = process.cwd()
|
|
45
|
-
const { default: CodeOwnersTool } = await import('codeowners/codeowners.js')
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
codeowners = new CodeOwnersTool(rootPath, options.codeownersFilename)
|
|
49
|
-
} catch (e) {
|
|
50
|
-
console.error(e.message)
|
|
51
|
-
process.exit(1)
|
|
52
|
-
}
|
|
44
|
+
const codeowners = await asyncCodeownersImport()
|
|
53
45
|
|
|
54
46
|
const filesToTeams = new Map()
|
|
55
47
|
const groupedByPackageName = {}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import getWorkspaceRoot from 'find-yarn-workspace-root'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} cwd
|
|
5
|
+
* @param {string} fileName
|
|
6
|
+
* @return {Promise}
|
|
7
|
+
*/
|
|
8
|
+
export const asyncCodeownersImport = async (cwd, fileName) => {
|
|
9
|
+
const workspaceRoot = getWorkspaceRoot()
|
|
10
|
+
|
|
11
|
+
let codeowners
|
|
12
|
+
const { default: CodeOwnersTool } = await import('codeowners/codeowners.js')
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
codeowners = new CodeOwnersTool(cwd ?? workspaceRoot, fileName)
|
|
16
|
+
} catch (e) {
|
|
17
|
+
console.error(e.message)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return codeowners
|
|
22
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import execa from 'execa'
|
|
3
|
+
import { print } from '@toptal/davinci-cli-shared'
|
|
4
|
+
import { readFile, writeFile, mkdir, cp, rm } from 'fs/promises'
|
|
5
|
+
|
|
6
|
+
import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
|
|
7
|
+
import { JEST_COVERAGE_PATH, TEAMS_COVERAGE_PATH } from './config.js'
|
|
8
|
+
import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
|
|
9
|
+
import { asyncCodeownersImport } from '../codeowners/index.js'
|
|
10
|
+
|
|
11
|
+
const sanitizeNycPercentage = percentage =>
|
|
12
|
+
percentage === 'Unknown' ? 0 : percentage
|
|
13
|
+
|
|
14
|
+
const generateNycReport = async ({
|
|
15
|
+
type,
|
|
16
|
+
sanitizedTeamPath,
|
|
17
|
+
sanitizedTeam,
|
|
18
|
+
}) => {
|
|
19
|
+
await mkdir(`${sanitizedTeamPath}/${type}`)
|
|
20
|
+
|
|
21
|
+
await cp(
|
|
22
|
+
`${JEST_COVERAGE_PATH}/coverage-final-${sanitizedTeam}.json`,
|
|
23
|
+
`${sanitizedTeamPath}/${type}/coverage-final.json`
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
await execa.command(
|
|
27
|
+
`yarn nyc report --temp-dir ${sanitizedTeamPath}/${type} --reporter json-summary --reporter html --report-dir ${sanitizedTeamPath}/${type}`
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getCoverageSummary = async (type, teamPath) => {
|
|
32
|
+
const result = {}
|
|
33
|
+
|
|
34
|
+
const coverageSummary = JSON.parse(
|
|
35
|
+
await readFile(`${teamPath}/${type}/coverage-summary.json`, 'utf8')
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
result[`${type}_lines_coverage`] = sanitizeNycPercentage(
|
|
39
|
+
coverageSummary.total.lines.pct
|
|
40
|
+
)
|
|
41
|
+
result[`${type}_statements_coverage`] = sanitizeNycPercentage(
|
|
42
|
+
coverageSummary.total.statements.pct
|
|
43
|
+
)
|
|
44
|
+
result[`${type}_functions_coverage`] = sanitizeNycPercentage(
|
|
45
|
+
coverageSummary.total.functions.pct
|
|
46
|
+
)
|
|
47
|
+
result[`${type}_branches_coverage`] = sanitizeNycPercentage(
|
|
48
|
+
coverageSummary.total.branches.pct
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const generateTeamCoverage = async options => {
|
|
55
|
+
print.header('Generating team coverage')
|
|
56
|
+
|
|
57
|
+
const codeowners = await asyncCodeownersImport()
|
|
58
|
+
|
|
59
|
+
const teams = getUniqueTeams(codeowners.ownerEntries)
|
|
60
|
+
|
|
61
|
+
print.grey('Teams: ', teams)
|
|
62
|
+
|
|
63
|
+
await rm(TEAMS_COVERAGE_PATH, { recursive: true, force: true })
|
|
64
|
+
await mkdir(TEAMS_COVERAGE_PATH, { recursive: true })
|
|
65
|
+
|
|
66
|
+
for await (const team of teams) {
|
|
67
|
+
print.grey('Team coverage: ', team)
|
|
68
|
+
|
|
69
|
+
const sanitizedTeam = sanitizeTeamName(team)
|
|
70
|
+
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
71
|
+
|
|
72
|
+
await mkdir(sanitizedTeamPath, { recursive: true })
|
|
73
|
+
|
|
74
|
+
await Promise.all([
|
|
75
|
+
generateNycReport({
|
|
76
|
+
type: 'jest',
|
|
77
|
+
sanitizedTeamPath,
|
|
78
|
+
sanitizedTeam,
|
|
79
|
+
}),
|
|
80
|
+
generateNycReport({
|
|
81
|
+
type: 'cypress',
|
|
82
|
+
sanitizedTeamPath,
|
|
83
|
+
sanitizedTeam,
|
|
84
|
+
}),
|
|
85
|
+
])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const results = []
|
|
89
|
+
const created_at = new Date().toISOString()
|
|
90
|
+
|
|
91
|
+
for await (const team of teams) {
|
|
92
|
+
const sanitizedTeam = sanitizeTeamName(team)
|
|
93
|
+
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
94
|
+
|
|
95
|
+
const [jestCoverageSummary, cypressCoverageSummary] = await Promise.all([
|
|
96
|
+
getCoverageSummary('jest', sanitizedTeamPath),
|
|
97
|
+
getCoverageSummary('cypress', sanitizedTeamPath),
|
|
98
|
+
])
|
|
99
|
+
|
|
100
|
+
results.push({
|
|
101
|
+
created_at,
|
|
102
|
+
team,
|
|
103
|
+
repo: options.repository,
|
|
104
|
+
...jestCoverageSummary,
|
|
105
|
+
...cypressCoverageSummary,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return writeFile(options.outputFileName, JSON.stringify(results, null, 2))
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const createGenerateTeamCoverageCommand = program =>
|
|
113
|
+
program
|
|
114
|
+
.command('generate-team-coverage')
|
|
115
|
+
.description(
|
|
116
|
+
`Generates team-specific coverage reports by copying and processing coverage data from Jest and Cypress reports.
|
|
117
|
+
The resulting coverage reports provide insights into the code coverage metrics for each team.`
|
|
118
|
+
)
|
|
119
|
+
.requiredOption(
|
|
120
|
+
'-r, --repository <repository_name>',
|
|
121
|
+
'specify repository name'
|
|
122
|
+
)
|
|
123
|
+
.requiredOption(
|
|
124
|
+
'-o, --output-file-name <output_file_name>',
|
|
125
|
+
'specify output file name'
|
|
126
|
+
)
|
|
127
|
+
.action(generateTeamCoverage)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { print } from '@toptal/davinci-cli-shared'
|
|
3
|
+
import getWorkspaceRoot from 'find-yarn-workspace-root'
|
|
4
|
+
import { readFile, writeFile } from 'fs/promises'
|
|
5
|
+
|
|
6
|
+
import { CYPRESS_COVERAGE_PATH, JEST_COVERAGE_PATH } from './config.js'
|
|
7
|
+
import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
|
|
8
|
+
import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
|
|
9
|
+
import { asyncCodeownersImport } from '../codeowners/index.js'
|
|
10
|
+
|
|
11
|
+
const workspaceRoot = getWorkspaceRoot()
|
|
12
|
+
|
|
13
|
+
const readCoverageReport = async reportPathName => {
|
|
14
|
+
const data = await readFile(path.join(workspaceRoot, reportPathName), 'utf8')
|
|
15
|
+
|
|
16
|
+
return JSON.parse(data)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const splitReportIntoTeams = async ({
|
|
20
|
+
report,
|
|
21
|
+
teams,
|
|
22
|
+
codeowners,
|
|
23
|
+
coveragePath,
|
|
24
|
+
}) => {
|
|
25
|
+
const teamsReport = {}
|
|
26
|
+
|
|
27
|
+
for (const [reportPathKey, reportValue] of Object.entries(report)) {
|
|
28
|
+
const filePath = reportValue.path
|
|
29
|
+
const relativeWorkspacePath = path.relative(workspaceRoot, filePath)
|
|
30
|
+
const owners = codeowners.getOwner(relativeWorkspacePath)
|
|
31
|
+
|
|
32
|
+
owners.forEach(owner => {
|
|
33
|
+
teamsReport[owner] = {
|
|
34
|
+
...teamsReport[owner],
|
|
35
|
+
[reportPathKey]: reportValue,
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for await (const team of teams) {
|
|
41
|
+
const teamReport = teamsReport[team] || {}
|
|
42
|
+
const sanitizedTeamPath = sanitizeTeamName(team)
|
|
43
|
+
|
|
44
|
+
const reportPath = path.join(
|
|
45
|
+
workspaceRoot,
|
|
46
|
+
coveragePath,
|
|
47
|
+
`coverage-final-${sanitizedTeamPath}.json`
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
return writeFile(reportPath, JSON.stringify(teamReport, null, 2))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const groupCoverageByCodeowners = async () => {
|
|
55
|
+
print.header('Grouping coverage by codeowners')
|
|
56
|
+
|
|
57
|
+
const [jestReport, cypressReport] = await Promise.all([
|
|
58
|
+
readCoverageReport(
|
|
59
|
+
path.join(JEST_COVERAGE_PATH, 'coverage-final-jest.json')
|
|
60
|
+
),
|
|
61
|
+
readCoverageReport(
|
|
62
|
+
path.join(CYPRESS_COVERAGE_PATH, 'coverage-final-cypress.json')
|
|
63
|
+
),
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
const codeowners = await asyncCodeownersImport()
|
|
67
|
+
|
|
68
|
+
const teams = getUniqueTeams(codeowners.ownerEntries)
|
|
69
|
+
|
|
70
|
+
print.grey('Teams: ', teams)
|
|
71
|
+
|
|
72
|
+
return Promise.all([
|
|
73
|
+
splitReportIntoTeams({
|
|
74
|
+
report: jestReport,
|
|
75
|
+
teams,
|
|
76
|
+
codeowners,
|
|
77
|
+
coveragePath: JEST_COVERAGE_PATH,
|
|
78
|
+
}),
|
|
79
|
+
splitReportIntoTeams({
|
|
80
|
+
report: cypressReport,
|
|
81
|
+
teams,
|
|
82
|
+
codeowners,
|
|
83
|
+
coveragePath: CYPRESS_COVERAGE_PATH,
|
|
84
|
+
}),
|
|
85
|
+
])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const createGroupCoverageByCodeownersCommand = program =>
|
|
89
|
+
program
|
|
90
|
+
.command('group-coverage-by-codeowners')
|
|
91
|
+
.description(
|
|
92
|
+
`Reads code coverage reports for Jest and Cypress, groups the coverage data based on code owners, and writes separate coverage reports for each team.
|
|
93
|
+
The command utilizes the Codeowners class, which is responsible for identifying the code owners associated with each file.`
|
|
94
|
+
)
|
|
95
|
+
.action(groupCoverageByCodeowners)
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { createGroupCoverageByCodeownersCommand } from './create-group-coverage-by-codeowners-command.js'
|
|
2
|
+
import { createGenerateTeamCoverageCommand } from './create-generate-team-coverage-command.js'
|
|
3
3
|
|
|
4
|
-
export const
|
|
5
|
-
const
|
|
4
|
+
export const createCoverageCommand = program => {
|
|
5
|
+
const coverageCommand = program
|
|
6
6
|
.createCommand('coverage')
|
|
7
7
|
.description(
|
|
8
8
|
'Provides code coverage analysis, including organizing coverage reports by code owners and generating team-specific reports'
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
createGroupCoverageByCodeownersCommand(coverageCommand)
|
|
12
|
+
createGenerateTeamCoverageCommand(coverageCommand)
|
|
13
13
|
|
|
14
|
-
return
|
|
14
|
+
return coverageCommand
|
|
15
15
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export const getUniqueTeams = teams =>
|
|
2
|
-
teams
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export const getUniqueTeams = teams => {
|
|
2
|
+
const allTeams = teams.flatMap(entry => entry.usernames)
|
|
3
|
+
|
|
4
|
+
return [...new Set(allTeams)]
|
|
5
|
+
}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export const sanitizeTeamName = team =>
|
|
1
|
+
export const sanitizeTeamName = team =>
|
|
2
|
+
team.replace(/\//g, '-').replace(/@/g, '')
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import detectCircularityCommandCreator from './commands/detect-circularity.js'
|
|
|
2
2
|
import metricsCommandCreator from './commands/metrics.js'
|
|
3
3
|
import { createGraphGenerateCommand } from './commands/graph-generate.js'
|
|
4
4
|
import codeownersCommand from './commands/codeowners/index.js'
|
|
5
|
-
import {
|
|
5
|
+
import { createCoverageCommand } from './commands/coverage-command/index.js'
|
|
6
6
|
import checkIfMonorepo from './utils/check-if-monorepo.js'
|
|
7
7
|
import getPackages from './utils/get-packages.js'
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ export const commands = [
|
|
|
16
16
|
metricsCommandCreator,
|
|
17
17
|
createGraphGenerateCommand,
|
|
18
18
|
codeownersCommand,
|
|
19
|
-
|
|
19
|
+
createCoverageCommand,
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
export default {
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import execa from 'execa'
|
|
3
|
-
import fs from 'fs'
|
|
4
|
-
import { print } from '@toptal/davinci-cli-shared'
|
|
5
|
-
import getWorkspaceRoot from 'find-yarn-workspace-root'
|
|
6
|
-
|
|
7
|
-
import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
|
|
8
|
-
import {
|
|
9
|
-
CYPRESS_COVERAGE_PATH,
|
|
10
|
-
JEST_COVERAGE_PATH,
|
|
11
|
-
TEAMS_COVERAGE_PATH,
|
|
12
|
-
} from './config.js'
|
|
13
|
-
import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
|
|
14
|
-
|
|
15
|
-
const workspaceRoot = getWorkspaceRoot()
|
|
16
|
-
|
|
17
|
-
const sanitizeNycPercentage = percentage =>
|
|
18
|
-
percentage === 'Unknown' ? 0 : percentage
|
|
19
|
-
|
|
20
|
-
const getCoverageSummary = (type, teamPath) => {
|
|
21
|
-
const result = {}
|
|
22
|
-
|
|
23
|
-
const coverageSummary = JSON.parse(
|
|
24
|
-
fs.readFileSync(`${teamPath}/${type}/coverage-summary.json`, 'utf8')
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
result[`${type}_lines_coverage`] = sanitizeNycPercentage(
|
|
28
|
-
coverageSummary.total.lines.pct
|
|
29
|
-
)
|
|
30
|
-
result[`${type}_statements_coverage`] = sanitizeNycPercentage(
|
|
31
|
-
coverageSummary.total.statements.pct
|
|
32
|
-
)
|
|
33
|
-
result[`${type}_functions_coverage`] = sanitizeNycPercentage(
|
|
34
|
-
coverageSummary.total.functions.pct
|
|
35
|
-
)
|
|
36
|
-
result[`${type}_branches_coverage`] = sanitizeNycPercentage(
|
|
37
|
-
coverageSummary.total.branches.pct
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
return result
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// eslint-disable-next-line max-statements
|
|
44
|
-
const generateTeamCoverage = async options => {
|
|
45
|
-
print.header('Generating team coverage')
|
|
46
|
-
|
|
47
|
-
let codeowners
|
|
48
|
-
const { default: CodeOwnersTool } = await import(
|
|
49
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
50
|
-
'codeowners/codeowners.js'
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
codeowners = new CodeOwnersTool(workspaceRoot)
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.error(e.message)
|
|
57
|
-
process.exit(1)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const teams = getUniqueTeams(codeowners.ownerEntries)
|
|
61
|
-
|
|
62
|
-
print.grey('Teams: ', teams)
|
|
63
|
-
|
|
64
|
-
await execa.command(`rm -rf ${TEAMS_COVERAGE_PATH}`)
|
|
65
|
-
await execa.command(`mkdir ${TEAMS_COVERAGE_PATH}`)
|
|
66
|
-
|
|
67
|
-
for await (const team of teams) {
|
|
68
|
-
print.grey('Team coverage: ', team)
|
|
69
|
-
|
|
70
|
-
const sanitizedTeam = sanitizeTeamName(team)
|
|
71
|
-
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
72
|
-
|
|
73
|
-
await execa.command(`mkdir ${sanitizedTeamPath}`)
|
|
74
|
-
await execa.command(`mkdir ${sanitizedTeamPath}/jest`)
|
|
75
|
-
await execa.command(`mkdir ${sanitizedTeamPath}/cypress`)
|
|
76
|
-
|
|
77
|
-
await execa.command(
|
|
78
|
-
`cp ${JEST_COVERAGE_PATH}/coverage-final-${sanitizedTeam}.json ${sanitizedTeamPath}/jest/coverage-final.json`
|
|
79
|
-
)
|
|
80
|
-
await execa.command(
|
|
81
|
-
`yarn nyc report --temp-dir ${sanitizedTeamPath}/jest --reporter json-summary --reporter html --report-dir ${sanitizedTeamPath}/jest`
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
await execa.command(
|
|
85
|
-
`cp ${CYPRESS_COVERAGE_PATH}/coverage-final-${sanitizedTeam}.json ${sanitizedTeamPath}/cypress/coverage-final.json`
|
|
86
|
-
)
|
|
87
|
-
await execa.command(
|
|
88
|
-
`yarn nyc report --temp-dir ${sanitizedTeamPath}/cypress --reporter json-summary --reporter html --report-dir ${sanitizedTeamPath}/cypress`
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const results = []
|
|
93
|
-
const created_at = new Date().toISOString()
|
|
94
|
-
|
|
95
|
-
for await (const team of teams) {
|
|
96
|
-
const sanitizedTeam = sanitizeTeamName(team)
|
|
97
|
-
const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
|
|
98
|
-
|
|
99
|
-
const jestCoverageSummary = getCoverageSummary('jest', sanitizedTeamPath)
|
|
100
|
-
const cypressCoverageSummary = getCoverageSummary(
|
|
101
|
-
'cypress',
|
|
102
|
-
sanitizedTeamPath
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
const result = {
|
|
106
|
-
created_at,
|
|
107
|
-
team,
|
|
108
|
-
repo: options.repository,
|
|
109
|
-
...jestCoverageSummary,
|
|
110
|
-
...cypressCoverageSummary,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
results.push(result)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
fs.writeFileSync(options.outputFileName, JSON.stringify(results, null, 2))
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export const generateTeamCoverageCommand = program =>
|
|
120
|
-
program
|
|
121
|
-
.command('generate-team-coverage')
|
|
122
|
-
.description('Generate team-specific coverage reports')
|
|
123
|
-
.requiredOption(
|
|
124
|
-
'-r, --repository <repository_name>',
|
|
125
|
-
'specify repository name'
|
|
126
|
-
)
|
|
127
|
-
.requiredOption(
|
|
128
|
-
'-o, --output-file-name <output_file_name>',
|
|
129
|
-
'specify output file name'
|
|
130
|
-
)
|
|
131
|
-
.action(generateTeamCoverage)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import { print } from '@toptal/davinci-cli-shared'
|
|
4
|
-
import getWorkspaceRoot from 'find-yarn-workspace-root'
|
|
5
|
-
|
|
6
|
-
import { CYPRESS_COVERAGE_PATH, JEST_COVERAGE_PATH } from './config.js'
|
|
7
|
-
import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
|
|
8
|
-
import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
|
|
9
|
-
|
|
10
|
-
const workspaceRoot = getWorkspaceRoot()
|
|
11
|
-
|
|
12
|
-
const readCoverageReport = reportPathName => {
|
|
13
|
-
const jestCoverageReportPath = path.join(workspaceRoot, reportPathName)
|
|
14
|
-
const data = fs.readFileSync(jestCoverageReportPath, 'utf8')
|
|
15
|
-
|
|
16
|
-
return JSON.parse(data)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const splitReportIntoTeams = ({ report, teams, codeowners, coveragePath }) => {
|
|
20
|
-
const teamsReport = {}
|
|
21
|
-
|
|
22
|
-
Object.entries(report).forEach(([reportPathKey, reportValue]) => {
|
|
23
|
-
const filePath = reportValue.path
|
|
24
|
-
const relativeWorkspacePath = path.relative(workspaceRoot, filePath)
|
|
25
|
-
const owners = codeowners.getOwner(relativeWorkspacePath)
|
|
26
|
-
|
|
27
|
-
owners.forEach(owner => {
|
|
28
|
-
teamsReport[owner] = {
|
|
29
|
-
...teamsReport[owner],
|
|
30
|
-
[reportPathKey]: reportValue,
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
teams.map(team => {
|
|
36
|
-
const teamReport = teamsReport[team] || {}
|
|
37
|
-
const sanitizedTeamPath = sanitizeTeamName(team)
|
|
38
|
-
|
|
39
|
-
const reportPath = path.join(
|
|
40
|
-
workspaceRoot,
|
|
41
|
-
coveragePath,
|
|
42
|
-
`coverage-final-${sanitizedTeamPath}.json`
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
fs.writeFileSync(reportPath, JSON.stringify(teamReport, null, 2))
|
|
46
|
-
})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const groupCoverageByCodeowners = async () => {
|
|
50
|
-
print.header('Grouping coverage by codeowners')
|
|
51
|
-
|
|
52
|
-
const jestReport = readCoverageReport(
|
|
53
|
-
path.join(JEST_COVERAGE_PATH, 'coverage-final-jest.json')
|
|
54
|
-
)
|
|
55
|
-
const cypressReport = readCoverageReport(
|
|
56
|
-
path.join(CYPRESS_COVERAGE_PATH, 'coverage-final-cypress.json')
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
let codeowners
|
|
60
|
-
const { default: CodeOwnersTool } = await import(
|
|
61
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
62
|
-
'codeowners/codeowners.js'
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
codeowners = new CodeOwnersTool(workspaceRoot)
|
|
67
|
-
} catch (e) {
|
|
68
|
-
console.error(e.message)
|
|
69
|
-
process.exit(1)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const teams = getUniqueTeams(codeowners.ownerEntries)
|
|
73
|
-
|
|
74
|
-
print.grey('Teams: ', teams)
|
|
75
|
-
|
|
76
|
-
splitReportIntoTeams({
|
|
77
|
-
report: jestReport,
|
|
78
|
-
teams,
|
|
79
|
-
codeowners,
|
|
80
|
-
coveragePath: JEST_COVERAGE_PATH,
|
|
81
|
-
})
|
|
82
|
-
splitReportIntoTeams({
|
|
83
|
-
report: cypressReport,
|
|
84
|
-
teams,
|
|
85
|
-
codeowners,
|
|
86
|
-
coveragePath: CYPRESS_COVERAGE_PATH,
|
|
87
|
-
})
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export const groupCoverageByCodeownersCommand = program =>
|
|
91
|
-
program
|
|
92
|
-
.command('group-coverage-by-codeowners')
|
|
93
|
-
.description('Group the coverage data based on code owners')
|
|
94
|
-
.action(groupCoverageByCodeowners)
|