@toptal/davinci-monorepo 8.1.4-alpha-CRT-5891-create-coverage-command-e6e5bd51.7 → 8.1.4-alpha-CRT-5891-create-coverage-command-1b2c7953.12

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.
@@ -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 { coverageCommand } from '../src/commands/coverage-command/index.js'
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
- coverageCommand,
18
+ createCoverageCommand,
19
19
  ],
20
20
  'davinci-monorepo'
21
21
  )
package/docs/coverage.md CHANGED
@@ -4,20 +4,14 @@ 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
- #### `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.
11
+ #### `generate-team-coverage`
18
12
 
19
13
  ```sh
20
- coverage generage-team-coverage [options]
14
+ coverage generate-team-coverage [options]
21
15
 
22
16
  Options:
23
17
  -r, --repository <repository_name> specify repository name
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-e6e5bd51.7+e6e5bd51",
3
+ "version": "8.1.4-alpha-CRT-5891-create-coverage-command-1b2c7953.12+1b2c7953",
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-e6e5bd51.39+e6e5bd51",
38
+ "@toptal/davinci-cli-shared": "2.3.1-alpha-CRT-5891-create-coverage-command-1b2c7953.44+1b2c7953",
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": "e6e5bd5114c669c12d4b245907649d617b9fdd96"
57
+ "gitHead": "1b2c79534c7559b73b39c067ab0480eadccf0ebc"
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
- const { default: CodeOwnersTool } = await import(
15
- 'codeowners/codeowners.js'
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
- // @ts-check
2
1
  import fs from 'fs'
3
2
  import ignore from 'ignore'
4
3
 
@@ -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
- try {
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 = {}
@@ -22,4 +22,6 @@ const codeownersCommand = program => {
22
22
  return codeowners
23
23
  }
24
24
 
25
+ export { asyncCodeownersImport } from './services/async-codeowners-import/async-codeowners-import.js'
26
+
25
27
  export default codeownersCommand
@@ -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,74 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`createGenerateTeamCoverageCommand has the correct command structure 1`] = `
4
+ "{
5
+ "_events": {},
6
+ "_eventsCount": 2,
7
+ "commands": [],
8
+ "options": [
9
+ {
10
+ "flags": "-r, --repository <repository_name>",
11
+ "description": "specify repository name",
12
+ "required": true,
13
+ "optional": false,
14
+ "variadic": false,
15
+ "mandatory": true,
16
+ "short": "-r",
17
+ "long": "--repository",
18
+ "negate": false,
19
+ "hidden": false,
20
+ "conflictsWith": []
21
+ },
22
+ {
23
+ "flags": "-o, --output-file-name <output_file_name>",
24
+ "description": "specify output file name",
25
+ "required": true,
26
+ "optional": false,
27
+ "variadic": false,
28
+ "mandatory": true,
29
+ "short": "-o",
30
+ "long": "--output-file-name",
31
+ "negate": false,
32
+ "hidden": false,
33
+ "conflictsWith": []
34
+ }
35
+ ],
36
+ "parent": null,
37
+ "_allowUnknownOption": false,
38
+ "_allowExcessArguments": true,
39
+ "_args": [],
40
+ "args": [],
41
+ "rawArgs": [],
42
+ "processedArgs": [],
43
+ "_scriptPath": null,
44
+ "_name": "generate-team-coverage",
45
+ "_optionValues": {},
46
+ "_optionValueSources": {},
47
+ "_storeOptionsAsProperties": false,
48
+ "_executableHandler": false,
49
+ "_executableFile": null,
50
+ "_executableDir": null,
51
+ "_defaultCommandName": null,
52
+ "_exitCallback": null,
53
+ "_aliases": [],
54
+ "_combineFlagAndOptionalValue": true,
55
+ "_description": "Generates team-specific coverage reports by copying and processing coverage data from Jest and Cypress reports.\\nThe resulting coverage reports provide insights into the code coverage metrics for each team.",
56
+ "_summary": "",
57
+ "_enablePositionalOptions": false,
58
+ "_passThroughOptions": false,
59
+ "_lifeCycleHooks": {},
60
+ "_showHelpAfterError": false,
61
+ "_showSuggestionAfterError": true,
62
+ "_outputConfiguration": {},
63
+ "_hidden": false,
64
+ "_hasHelpOption": true,
65
+ "_helpFlags": "-h, --help",
66
+ "_helpDescription": "display help for command",
67
+ "_helpShortFlag": "-h",
68
+ "_helpLongFlag": "--help",
69
+ "_helpCommandName": "help",
70
+ "_helpCommandnameAndArgs": "help [command]",
71
+ "_helpCommandDescription": "display help for command",
72
+ "_helpConfiguration": {}
73
+ }"
74
+ `;
@@ -0,0 +1,47 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`createGroupCoverageByCodeownersCommand has the correct command structure 1`] = `
4
+ "{
5
+ "_events": {},
6
+ "_eventsCount": 0,
7
+ "commands": [],
8
+ "options": [],
9
+ "parent": null,
10
+ "_allowUnknownOption": false,
11
+ "_allowExcessArguments": true,
12
+ "_args": [],
13
+ "args": [],
14
+ "rawArgs": [],
15
+ "processedArgs": [],
16
+ "_scriptPath": null,
17
+ "_name": "group-coverage-by-codeowners",
18
+ "_optionValues": {},
19
+ "_optionValueSources": {},
20
+ "_storeOptionsAsProperties": false,
21
+ "_executableHandler": false,
22
+ "_executableFile": null,
23
+ "_executableDir": null,
24
+ "_defaultCommandName": null,
25
+ "_exitCallback": null,
26
+ "_aliases": [],
27
+ "_combineFlagAndOptionalValue": true,
28
+ "_description": "Reads code coverage reports for Jest and Cypress, groups the coverage data based on code owners, and writes separate coverage reports for each team.\\nThe command utilizes the Codeowners class, which is responsible for identifying the code owners associated with each file.",
29
+ "_summary": "",
30
+ "_enablePositionalOptions": false,
31
+ "_passThroughOptions": false,
32
+ "_lifeCycleHooks": {},
33
+ "_showHelpAfterError": false,
34
+ "_showSuggestionAfterError": true,
35
+ "_outputConfiguration": {},
36
+ "_hidden": false,
37
+ "_hasHelpOption": true,
38
+ "_helpFlags": "-h, --help",
39
+ "_helpDescription": "display help for command",
40
+ "_helpShortFlag": "-h",
41
+ "_helpLongFlag": "--help",
42
+ "_helpCommandName": "help",
43
+ "_helpCommandnameAndArgs": "help [command]",
44
+ "_helpCommandDescription": "display help for command",
45
+ "_helpConfiguration": {}
46
+ }"
47
+ `;
@@ -0,0 +1,141 @@
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
+ import { existsSync } from 'fs'
6
+
7
+ import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
8
+ import { TEAMS_COVERAGE_PATH } from './config.js'
9
+ import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
10
+ import { asyncCodeownersImport } from '../codeowners/index.js'
11
+
12
+ const sanitizeNycPercentage = percentage =>
13
+ percentage === 'Unknown' ? 0 : percentage
14
+
15
+ const generateNycReport = async ({
16
+ type,
17
+ sanitizedTeamPath,
18
+ sanitizedTeam,
19
+ }) => {
20
+ const sanitizedTeamPathWithType = path.join(sanitizedTeamPath, type)
21
+
22
+ await mkdir(sanitizedTeamPathWithType)
23
+
24
+ const fullCoverageTeamPath = path.join(
25
+ 'coverage',
26
+ type,
27
+ `coverage-final-${sanitizedTeam}.json`
28
+ )
29
+
30
+ if (!existsSync(fullCoverageTeamPath)) {
31
+ print.red(`Make sure that '${fullCoverageTeamPath}' exists`)
32
+ process.exit(1)
33
+ }
34
+
35
+ await cp(
36
+ path.join(fullCoverageTeamPath),
37
+ path.join(sanitizedTeamPathWithType, 'coverage-final.json')
38
+ )
39
+
40
+ await execa.command(
41
+ `yarn nyc report --temp-dir ${sanitizedTeamPathWithType} --reporter json-summary --reporter html --report-dir ${sanitizedTeamPathWithType}`
42
+ )
43
+ }
44
+
45
+ const getCoverageSummary = async (type, teamPath) => {
46
+ const result = {}
47
+
48
+ const coverageSummary = JSON.parse(
49
+ await readFile(path.join(teamPath, type, 'coverage-summary.json'), 'utf8')
50
+ )
51
+
52
+ result[`${type}_lines_coverage`] = sanitizeNycPercentage(
53
+ coverageSummary.total.lines.pct
54
+ )
55
+ result[`${type}_statements_coverage`] = sanitizeNycPercentage(
56
+ coverageSummary.total.statements.pct
57
+ )
58
+ result[`${type}_functions_coverage`] = sanitizeNycPercentage(
59
+ coverageSummary.total.functions.pct
60
+ )
61
+ result[`${type}_branches_coverage`] = sanitizeNycPercentage(
62
+ coverageSummary.total.branches.pct
63
+ )
64
+
65
+ return result
66
+ }
67
+
68
+ const generateTeamCoverage = async options => {
69
+ print.header('Generating team coverage')
70
+
71
+ const codeowners = await asyncCodeownersImport()
72
+
73
+ const teams = getUniqueTeams(codeowners.ownerEntries)
74
+
75
+ print.grey('Teams: ', teams)
76
+
77
+ await rm(TEAMS_COVERAGE_PATH, { recursive: true, force: true })
78
+ await mkdir(TEAMS_COVERAGE_PATH, { recursive: true })
79
+
80
+ for await (const team of teams) {
81
+ print.grey('Team coverage: ', team)
82
+
83
+ const sanitizedTeam = sanitizeTeamName(team)
84
+ const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
85
+
86
+ await mkdir(sanitizedTeamPath, { recursive: true })
87
+
88
+ await Promise.all([
89
+ generateNycReport({
90
+ type: 'jest',
91
+ sanitizedTeamPath,
92
+ sanitizedTeam,
93
+ }),
94
+ generateNycReport({
95
+ type: 'cypress',
96
+ sanitizedTeamPath,
97
+ sanitizedTeam,
98
+ }),
99
+ ])
100
+ }
101
+
102
+ const results = []
103
+ const created_at = new Date().toISOString()
104
+
105
+ for await (const team of teams) {
106
+ const sanitizedTeam = sanitizeTeamName(team)
107
+ const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
108
+
109
+ const [jestCoverageSummary, cypressCoverageSummary] = await Promise.all([
110
+ getCoverageSummary('jest', sanitizedTeamPath),
111
+ getCoverageSummary('cypress', sanitizedTeamPath),
112
+ ])
113
+
114
+ results.push({
115
+ created_at,
116
+ team,
117
+ repo: options.repository,
118
+ ...jestCoverageSummary,
119
+ ...cypressCoverageSummary,
120
+ })
121
+ }
122
+
123
+ return writeFile(options.outputFileName, JSON.stringify(results, null, 2))
124
+ }
125
+
126
+ export const createGenerateTeamCoverageCommand = program =>
127
+ program
128
+ .createCommand('generate-team-coverage')
129
+ .description(
130
+ `Generates team-specific coverage reports by copying and processing coverage data from Jest and Cypress reports.
131
+ The resulting coverage reports provide insights into the code coverage metrics for each team.`
132
+ )
133
+ .requiredOption(
134
+ '-r, --repository <repository_name>',
135
+ 'specify repository name'
136
+ )
137
+ .requiredOption(
138
+ '-o, --output-file-name <output_file_name>',
139
+ 'specify output file name'
140
+ )
141
+ .action(generateTeamCoverage)
@@ -0,0 +1,25 @@
1
+ import { jest } from '@jest/globals'
2
+ // eslint-disable-next-line import/no-extraneous-dependencies
3
+ import { Command } from 'commander'
4
+
5
+ const { createGenerateTeamCoverageCommand } = await import(
6
+ './create-generate-team-coverage-command.js'
7
+ )
8
+
9
+ describe('createGenerateTeamCoverageCommand', () => {
10
+ let program
11
+
12
+ beforeEach(() => {
13
+ program = new Command()
14
+ })
15
+
16
+ afterEach(() => {
17
+ jest.clearAllMocks()
18
+ })
19
+
20
+ it('has the correct command structure', () => {
21
+ const command = createGenerateTeamCoverageCommand(program)
22
+
23
+ expect(JSON.stringify(command, null, 2)).toMatchSnapshot()
24
+ })
25
+ })
@@ -0,0 +1,103 @@
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
+ import { existsSync } from 'fs'
6
+
7
+ import { CYPRESS_COVERAGE_PATH, JEST_COVERAGE_PATH } from './config.js'
8
+ import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
9
+ import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
10
+ import { asyncCodeownersImport } from '../codeowners/index.js'
11
+
12
+ const workspaceRoot = getWorkspaceRoot()
13
+
14
+ const readCoverageReport = async reportPathName => {
15
+ const fullPath = path.join(workspaceRoot, reportPathName)
16
+
17
+ if (!existsSync(fullPath)) {
18
+ print.red(`Make sure that '${fullPath}' exists`)
19
+ process.exit(1)
20
+ }
21
+
22
+ const data = await readFile(fullPath, 'utf8')
23
+
24
+ return JSON.parse(data)
25
+ }
26
+
27
+ const splitReportIntoTeams = async ({
28
+ report,
29
+ teams,
30
+ codeowners,
31
+ coveragePath,
32
+ }) => {
33
+ const teamsReport = {}
34
+
35
+ for (const [reportPathKey, reportValue] of Object.entries(report)) {
36
+ const filePath = reportValue.path
37
+ const relativeWorkspacePath = path.relative(workspaceRoot, filePath)
38
+ const owners = codeowners.getOwner(relativeWorkspacePath)
39
+
40
+ owners.forEach(owner => {
41
+ teamsReport[owner] = {
42
+ ...teamsReport[owner],
43
+ [reportPathKey]: reportValue,
44
+ }
45
+ })
46
+ }
47
+
48
+ for await (const team of teams) {
49
+ const teamReport = teamsReport[team] || {}
50
+ const sanitizedTeamPath = sanitizeTeamName(team)
51
+
52
+ const reportPath = path.join(
53
+ workspaceRoot,
54
+ coveragePath,
55
+ `coverage-final-${sanitizedTeamPath}.json`
56
+ )
57
+
58
+ return writeFile(reportPath, JSON.stringify(teamReport, null, 2))
59
+ }
60
+ }
61
+
62
+ const groupCoverageByCodeowners = async () => {
63
+ print.header('Grouping coverage by codeowners')
64
+
65
+ const [jestReport, cypressReport] = await Promise.all([
66
+ readCoverageReport(
67
+ path.join(JEST_COVERAGE_PATH, 'coverage-final-jest.json')
68
+ ),
69
+ readCoverageReport(
70
+ path.join(CYPRESS_COVERAGE_PATH, 'coverage-final-cypress.json')
71
+ ),
72
+ ])
73
+
74
+ const codeowners = await asyncCodeownersImport()
75
+
76
+ const teams = getUniqueTeams(codeowners.ownerEntries)
77
+
78
+ print.grey('Teams: ', teams)
79
+
80
+ return Promise.all([
81
+ splitReportIntoTeams({
82
+ report: jestReport,
83
+ teams,
84
+ codeowners,
85
+ coveragePath: JEST_COVERAGE_PATH,
86
+ }),
87
+ splitReportIntoTeams({
88
+ report: cypressReport,
89
+ teams,
90
+ codeowners,
91
+ coveragePath: CYPRESS_COVERAGE_PATH,
92
+ }),
93
+ ])
94
+ }
95
+
96
+ export const createGroupCoverageByCodeownersCommand = program =>
97
+ program
98
+ .createCommand('group-coverage-by-codeowners')
99
+ .description(
100
+ `Reads code coverage reports for Jest and Cypress, groups the coverage data based on code owners, and writes separate coverage reports for each team.
101
+ The command utilizes the Codeowners class, which is responsible for identifying the code owners associated with each file.`
102
+ )
103
+ .action(groupCoverageByCodeowners)
@@ -0,0 +1,25 @@
1
+ import { jest } from '@jest/globals'
2
+ // eslint-disable-next-line import/no-extraneous-dependencies
3
+ import { Command } from 'commander'
4
+
5
+ const { createGroupCoverageByCodeownersCommand } = await import(
6
+ './create-group-coverage-by-codeowners-command.js'
7
+ )
8
+
9
+ describe('createGroupCoverageByCodeownersCommand', () => {
10
+ let program
11
+
12
+ beforeEach(() => {
13
+ program = new Command()
14
+ })
15
+
16
+ afterEach(() => {
17
+ jest.clearAllMocks()
18
+ })
19
+
20
+ it('has the correct command structure', () => {
21
+ const command = createGroupCoverageByCodeownersCommand(program)
22
+
23
+ expect(JSON.stringify(command, null, 2)).toMatchSnapshot()
24
+ })
25
+ })
@@ -1,15 +1,11 @@
1
- import { generateTeamCoverageCommand } from './generate-team-coverage.js'
2
- import { groupCoverageByCodeownersCommand } from './group-coverage-by-codeowners.js'
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 coverageCommand = program => {
5
- const coverage = program
4
+ export const createCoverageCommand = program =>
5
+ 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
-
11
- groupCoverageByCodeownersCommand(coverage)
12
- generateTeamCoverageCommand(coverage)
13
-
14
- return coverage
15
- }
10
+ .addCommand(createGroupCoverageByCodeownersCommand(program))
11
+ .addCommand(createGenerateTeamCoverageCommand(program))
@@ -1,4 +1,5 @@
1
- export const getUniqueTeams = teams =>
2
- teams
3
- .flatMap(entry => entry.usernames)
4
- .filter((teamName, index, allTeams) => allTeams.indexOf(teamName) === index)
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 => team.replace('/', '-').replace('@', '')
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 { coverageCommand } from './commands/coverage-command/index.js'
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
- coverageCommand,
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)