@toptal/davinci-monorepo 8.1.3 → 8.1.4-alpha-CRT-5891-create-coverage-command-9655bb3f.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toptal/davinci-monorepo",
3
- "version": "8.1.3",
3
+ "version": "8.1.4-alpha-CRT-5891-create-coverage-command-9655bb3f.1+9655bb3f",
4
4
  "description": "Monorepo utility tools",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -33,24 +33,25 @@
33
33
  "url": "https://github.com/toptal/davinci/issues"
34
34
  },
35
35
  "dependencies": {
36
+ "@nodelib/fs.walk": "^1.2.6",
36
37
  "@oclif/core": "^1.16.1",
37
- "@toptal/davinci-cli-shared": "2.3.0",
38
+ "@toptal/davinci-cli-shared": "2.3.1-alpha-CRT-5891-create-coverage-command-9655bb3f.33+9655bb3f",
38
39
  "chalk": "^4.1.2",
40
+ "codeowners": "5.1.1",
39
41
  "dependency-cruiser": "^12.5.0",
40
- "execa": "^5.1.1",
41
- "glob": "^8.0.3",
42
- "ramda": "^0.28.0",
43
- "@nodelib/fs.walk": "^1.2.6",
44
42
  "ervy": "^1.0.7",
43
+ "execa": "^5.1.1",
45
44
  "find-up": "5.0.0",
45
+ "glob": "^8.0.3",
46
46
  "ignore": "^5.2.0",
47
47
  "is-directory": "^0.3.1",
48
48
  "lodash": "4.17.21",
49
49
  "ora": "^5.4.1",
50
- "true-case-path": "^1.0.3",
51
- "codeowners": "5.1.1"
50
+ "ramda": "^0.28.0",
51
+ "true-case-path": "^1.0.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@jest/globals": "^29.4.2"
55
- }
55
+ },
56
+ "gitHead": "9655bb3fabd7a3cdc9ae9b16233797f9ac5d6260"
56
57
  }
@@ -1,17 +1,22 @@
1
- /* eslint-disable import/no-extraneous-dependencies */
2
1
  // @ts-check
3
- const fs = require('fs')
4
- const ignore = require('ignore')
2
+ import fs from 'fs'
3
+ import ignore from 'ignore'
5
4
 
6
- const getCodeownersFilePath = require('../utils/get-codeowners-file-path')
5
+ import getCodeownersFilePath from '../utils/get-codeowners-file-path.js'
7
6
 
7
+ /**
8
+ * @param {string} pathString
9
+ */
8
10
  const ownerMatcher = pathString => {
9
- // @ts-ignore
10
11
  const matcher = ignore({ ignorecase: false }).add(pathString)
11
12
 
12
13
  return matcher.ignores.bind(matcher)
13
14
  }
14
15
 
16
+ /**
17
+ * @param {string} currentPath
18
+ * @param {string} fileName
19
+ */
15
20
  // eslint-disable-next-line func-style
16
21
  function Codeowners(currentPath, fileName = 'CODEOWNERS') {
17
22
  const { codeownersDirectory, codeownersFilePath } = getCodeownersFilePath(
@@ -52,16 +57,21 @@ function Codeowners(currentPath, fileName = 'CODEOWNERS') {
52
57
  this.ignore = ignore
53
58
  }
54
59
 
55
- const EMPTY_ARRAY = []
60
+ Codeowners.prototype.getOwner =
61
+ /**
62
+ * @param {string} filePath
63
+ */
64
+ function getOwner(filePath) {
65
+ console.log({ filePath })
66
+ console.log({ entries: this.ownerEntries })
56
67
 
57
- Codeowners.prototype.getOwner = function getOwner(filePath) {
58
- for (const entry of this.ownerEntries) {
59
- if (entry.match(filePath)) {
60
- return entry.usernames
68
+ for (const entry of this.ownerEntries) {
69
+ if (entry.match(filePath)) {
70
+ return entry.usernames
71
+ }
61
72
  }
62
- }
63
73
 
64
- return EMPTY_ARRAY
65
- }
74
+ return []
75
+ }
66
76
 
67
- module.exports = Codeowners
77
+ export default Codeowners
@@ -0,0 +1,11 @@
1
+ import { generateTeamCoverageCommand } from './generate-team-coverage.js'
2
+ import { groupCoverageByCodeownersCommand } from './group-coverage-by-codeowners.js'
3
+
4
+ export const coverageCommand = program => {
5
+ const coverage = program.createCommand('coverage').description('TODO ..')
6
+
7
+ generateTeamCoverageCommand(coverageCommand)
8
+ groupCoverageByCodeownersCommand(coverageCommand)
9
+
10
+ return coverage
11
+ }
@@ -0,0 +1,138 @@
1
+ import path from 'path'
2
+ import * as url from 'url'
3
+ import execa from 'execa'
4
+ import fs from 'fs'
5
+
6
+ import Codeowners from './codeowners/core/codeowners/codeowners.js'
7
+
8
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
9
+ const rootProjectPath = path.join(__dirname, '../../')
10
+
11
+ const sanitizeTeamName = team => team.replace('/', '-').replace('@', '')
12
+
13
+ const sanitizeNycPercentage = percentage =>
14
+ percentage === 'Unknown' ? 0 : percentage
15
+
16
+ // eslint-disable-next-line max-statements
17
+ const generateTeamCoverage = async () => {
18
+ const codeowners = new Codeowners(rootProjectPath)
19
+
20
+ const ownerEntries = codeowners.ownerEntries
21
+ const uniqueTeams = ownerEntries
22
+ .flatMap(entry => entry.usernames)
23
+ // eslint-disable-next-line id-length
24
+ .filter((v, i, a) => a.indexOf(v) === i)
25
+
26
+ // eslint-disable-next-line no-console
27
+ console.log('Teams:', uniqueTeams)
28
+
29
+ const teams = uniqueTeams
30
+
31
+ await execa.command(`rm -rf coverage/teams`)
32
+ await execa.command(`mkdir coverage/teams`)
33
+
34
+ for await (const team of teams) {
35
+ const sanitizedTeam = sanitizeTeamName(team)
36
+
37
+ await execa.command(`mkdir coverage/teams/${sanitizedTeam}`)
38
+ await execa.command(`mkdir coverage/teams/${sanitizedTeam}/jest`)
39
+ await execa.command(`mkdir coverage/teams/${sanitizedTeam}/cypress`)
40
+ }
41
+
42
+ for await (const team of teams) {
43
+ // eslint-disable-next-line no-console
44
+ console.log('Team coverage:', team)
45
+
46
+ const sanitizedTeam = sanitizeTeamName(team)
47
+
48
+ await execa.command(
49
+ `cp coverage/jest/coverage-final-${sanitizedTeam}.json coverage/teams/${sanitizedTeam}/jest/coverage-final.json`
50
+ )
51
+
52
+ // TODO add nyc as deps?
53
+ await execa.command(
54
+ `yarn nyc report --temp-dir coverage/teams/${sanitizedTeam}/jest --reporter json-summary --reporter html --report-dir coverage/teams/${sanitizedTeam}/jest`
55
+ )
56
+
57
+ await execa.command(
58
+ `cp coverage/cypress/coverage-final-${sanitizedTeam}.json coverage/teams/${sanitizedTeam}/cypress/coverage-final.json`
59
+ )
60
+
61
+ await execa.command(
62
+ `yarn nyc report --temp-dir coverage/teams/${sanitizedTeam}/cypress --reporter json-summary --reporter html --report-dir coverage/teams/${sanitizedTeam}/cypress`
63
+ )
64
+ }
65
+
66
+ const results = []
67
+ const created_at = new Date().toISOString()
68
+
69
+ // TODO pass it as param
70
+ const repo = 'client-portal'
71
+
72
+ for await (const team of teams) {
73
+ const sanitizedTeam = sanitizeTeamName(team)
74
+ const result = {
75
+ created_at,
76
+ team,
77
+ repo,
78
+ }
79
+
80
+ const jestCoverageSummary = JSON.parse(
81
+ fs.readFileSync(
82
+ `coverage/teams/${sanitizedTeam}/jest/coverage-summary.json`,
83
+ 'utf8'
84
+ )
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
+ `coverage/teams/${sanitizedTeam}/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
+ results.push(result)
121
+ }
122
+
123
+ fs.writeFileSync(
124
+ 'team-tests-coverage-results.json',
125
+ JSON.stringify(results, null, 2)
126
+ )
127
+ }
128
+
129
+ export const generateTeamCoverageCommand = program => {
130
+ return program
131
+ .createCommand('generate-team-coverage')
132
+ .description('TODO: generate-team-coverage')
133
+ .requiredOption(
134
+ '-r, --repository <repository_name>',
135
+ 'specify repository name'
136
+ )
137
+ .action((_, options) => generateTeamCoverage(options))
138
+ }
@@ -0,0 +1,99 @@
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import * as url from 'url'
4
+
5
+ import Codeowners from './codeowners/core/codeowners/codeowners.js'
6
+
7
+ const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
8
+ const rootProjectPath = path.join(__dirname, '../../')
9
+
10
+ const readJestCoverageReport = () => {
11
+ const jestCoverageReportPath = path.join(
12
+ rootProjectPath,
13
+ 'coverage/jest/coverage-final-jest.json'
14
+ )
15
+
16
+ const data = fs.readFileSync(jestCoverageReportPath, 'utf8')
17
+
18
+ return JSON.parse(data)
19
+ }
20
+
21
+ const readCypressCoverageReport = () => {
22
+ const cypressCoverageReportPath = path.join(
23
+ rootProjectPath,
24
+ 'coverage/cypress/coverage-final-cypress.json'
25
+ )
26
+
27
+ const data = fs.readFileSync(cypressCoverageReportPath, 'utf8')
28
+
29
+ return JSON.parse(data)
30
+ }
31
+
32
+ const sanitizeTeamName = team => team.replace('/', '-').replace('@', '')
33
+
34
+ const splitReportIntoTeams = (reportObj, teams, codeowners, coveragePath) => {
35
+ const teamsReport = {}
36
+
37
+ Object.entries(reportObj).forEach(([reportPathKey, report]) => {
38
+ const file = report.path
39
+ const relativePath = path.relative(rootProjectPath, file)
40
+
41
+ const owners = codeowners.getOwner(relativePath)
42
+
43
+ owners.forEach(owner => {
44
+ teamsReport[owner] = {
45
+ ...teamsReport[owner],
46
+ [reportPathKey]: report,
47
+ }
48
+ })
49
+ })
50
+
51
+ teams.map(team => {
52
+ const teamReport = teamsReport[team] || {}
53
+ const sanitizedTeamPath = sanitizeTeamName(team)
54
+
55
+ const reportPath = path.join(
56
+ rootProjectPath,
57
+ `${coveragePath}/coverage-final-${sanitizedTeamPath}.json`
58
+ )
59
+
60
+ fs.writeFileSync(reportPath, JSON.stringify(teamReport, null, 2))
61
+ })
62
+ }
63
+
64
+ const getAllTeams = codeowners => {
65
+ const ownerEntries = codeowners.ownerEntries
66
+ const uniqueTeams = ownerEntries
67
+ .flatMap(entry => entry.usernames)
68
+ // eslint-disable-next-line id-length
69
+ .filter((v, i, a) => a.indexOf(v) === i)
70
+
71
+ // eslint-disable-next-line no-console
72
+ console.log('Teams:', uniqueTeams)
73
+
74
+ return uniqueTeams
75
+ }
76
+
77
+ const groupCoverageByCodeowners = async options => {
78
+ console.log(options)
79
+
80
+ const jestReport = readJestCoverageReport()
81
+ const cypressReport = readCypressCoverageReport()
82
+
83
+ const codeowners = new Codeowners(rootProjectPath)
84
+ const teams = getAllTeams(codeowners)
85
+
86
+ splitReportIntoTeams(jestReport, teams, codeowners, 'coverage/jest')
87
+ splitReportIntoTeams(cypressReport, teams, codeowners, 'coverage/cypress')
88
+ }
89
+
90
+ export const groupCoverageByCodeownersCommand = program => {
91
+ return program
92
+ .createCommand('group-coverage-by-codeowners')
93
+ .description('TODO: group-coverage-by-codeowners')
94
+ .requiredOption(
95
+ '-r, --repository <repository_name>',
96
+ 'specify repository name'
97
+ )
98
+ .action((_, options) => groupCoverageByCodeowners(options))
99
+ }
package/src/index.js CHANGED
@@ -2,6 +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 { coverageCommand } from './commands/coverage.js'
5
6
  import checkIfMonorepo from './utils/check-if-monorepo.js'
6
7
  import getPackages from './utils/get-packages.js'
7
8
 
@@ -15,6 +16,7 @@ export const commands = [
15
16
  metricsCommandCreator,
16
17
  createGraphGenerateCommand,
17
18
  codeownersCommand,
19
+ coverageCommand,
18
20
  ]
19
21
 
20
22
  export default {