@toptal/davinci-monorepo 8.1.4-alpha-CRT-5891-create-coverage-command-0a4adac5.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.
@@ -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,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-0a4adac5.7+0a4adac5",
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,10 +35,9 @@
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-0a4adac5.39+0a4adac5",
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
- "commander": "^10.0.0",
42
41
  "dependency-cruiser": "^12.5.0",
43
42
  "ervy": "^1.0.7",
44
43
  "execa": "^5.1.1",
@@ -55,5 +54,5 @@
55
54
  "devDependencies": {
56
55
  "@jest/globals": "^29.4.2"
57
56
  },
58
- "gitHead": "0a4adac5650c666f07b97522a76b2f30dd604738"
57
+ "gitHead": "5071adf786fc0f9eaf34275fe751c75f403dacfa"
59
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
+ }
@@ -1,29 +1,38 @@
1
1
  import path from 'path'
2
2
  import execa from 'execa'
3
- import fs from 'fs'
4
3
  import { print } from '@toptal/davinci-cli-shared'
5
- import getWorkspaceRoot from 'find-yarn-workspace-root'
4
+ import { readFile, writeFile, mkdir, cp, rm } from 'fs/promises'
6
5
 
7
- // eslint-disable-next-line no-restricted-syntax
8
- import Codeowners from '../codeowners/core/codeowners/codeowners.js'
9
6
  import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
10
- import {
11
- CYPRESS_COVERAGE_PATH,
12
- JEST_COVERAGE_PATH,
13
- TEAMS_COVERAGE_PATH,
14
- } from './config.js'
7
+ import { JEST_COVERAGE_PATH, TEAMS_COVERAGE_PATH } from './config.js'
15
8
  import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
16
-
17
- const workspaceRoot = getWorkspaceRoot()
9
+ import { asyncCodeownersImport } from '../codeowners/index.js'
18
10
 
19
11
  const sanitizeNycPercentage = percentage =>
20
12
  percentage === 'Unknown' ? 0 : percentage
21
13
 
22
- const getCoverageSummary = (type, teamPath) => {
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) => {
23
32
  const result = {}
24
33
 
25
34
  const coverageSummary = JSON.parse(
26
- fs.readFileSync(`${teamPath}/${type}/coverage-summary.json`, 'utf8')
35
+ await readFile(`${teamPath}/${type}/coverage-summary.json`, 'utf8')
27
36
  )
28
37
 
29
38
  result[`${type}_lines_coverage`] = sanitizeNycPercentage(
@@ -42,15 +51,17 @@ const getCoverageSummary = (type, teamPath) => {
42
51
  return result
43
52
  }
44
53
 
45
- // eslint-disable-next-line max-statements
46
54
  const generateTeamCoverage = async options => {
47
55
  print.header('Generating team coverage')
48
56
 
49
- const codeowners = new Codeowners(workspaceRoot)
57
+ const codeowners = await asyncCodeownersImport()
58
+
50
59
  const teams = getUniqueTeams(codeowners.ownerEntries)
51
60
 
52
- await execa.command(`rm -rf ${TEAMS_COVERAGE_PATH}`)
53
- await execa.command(`mkdir ${TEAMS_COVERAGE_PATH}`)
61
+ print.grey('Teams: ', teams)
62
+
63
+ await rm(TEAMS_COVERAGE_PATH, { recursive: true, force: true })
64
+ await mkdir(TEAMS_COVERAGE_PATH, { recursive: true })
54
65
 
55
66
  for await (const team of teams) {
56
67
  print.grey('Team coverage: ', team)
@@ -58,23 +69,20 @@ const generateTeamCoverage = async options => {
58
69
  const sanitizedTeam = sanitizeTeamName(team)
59
70
  const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
60
71
 
61
- await execa.command(`mkdir ${sanitizedTeamPath}`)
62
- await execa.command(`mkdir ${sanitizedTeamPath}/jest`)
63
- await execa.command(`mkdir ${sanitizedTeamPath}/cypress`)
64
-
65
- await execa.command(
66
- `cp ${JEST_COVERAGE_PATH}/coverage-final-${sanitizedTeam}.json ${sanitizedTeamPath}/jest/coverage-final.json`
67
- )
68
- await execa.command(
69
- `yarn nyc report --temp-dir ${sanitizedTeamPath}/jest --reporter json-summary --reporter html --report-dir ${sanitizedTeamPath}/jest`
70
- )
71
-
72
- await execa.command(
73
- `cp ${CYPRESS_COVERAGE_PATH}/coverage-final-${sanitizedTeam}.json ${sanitizedTeamPath}/cypress/coverage-final.json`
74
- )
75
- await execa.command(
76
- `yarn nyc report --temp-dir ${sanitizedTeamPath}/cypress --reporter json-summary --reporter html --report-dir ${sanitizedTeamPath}/cypress`
77
- )
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
+ ])
78
86
  }
79
87
 
80
88
  const results = []
@@ -84,30 +92,30 @@ const generateTeamCoverage = async options => {
84
92
  const sanitizedTeam = sanitizeTeamName(team)
85
93
  const sanitizedTeamPath = path.join(TEAMS_COVERAGE_PATH, sanitizedTeam)
86
94
 
87
- const jestCoverageSummary = getCoverageSummary('jest', sanitizedTeamPath)
88
- const cypressCoverageSummary = getCoverageSummary(
89
- 'cypress',
90
- sanitizedTeamPath
91
- )
95
+ const [jestCoverageSummary, cypressCoverageSummary] = await Promise.all([
96
+ getCoverageSummary('jest', sanitizedTeamPath),
97
+ getCoverageSummary('cypress', sanitizedTeamPath),
98
+ ])
92
99
 
93
- const result = {
100
+ results.push({
94
101
  created_at,
95
102
  team,
96
103
  repo: options.repository,
97
104
  ...jestCoverageSummary,
98
105
  ...cypressCoverageSummary,
99
- }
100
-
101
- results.push(result)
106
+ })
102
107
  }
103
108
 
104
- fs.writeFileSync(options.outputFileName, JSON.stringify(results, null, 2))
109
+ return writeFile(options.outputFileName, JSON.stringify(results, null, 2))
105
110
  }
106
111
 
107
- export const generateTeamCoverageCommand = program =>
112
+ export const createGenerateTeamCoverageCommand = program =>
108
113
  program
109
114
  .command('generate-team-coverage')
110
- .description('Generate team-specific coverage reports')
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
+ )
111
119
  .requiredOption(
112
120
  '-r, --repository <repository_name>',
113
121
  'specify repository name'
@@ -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 { 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
+ 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
- groupCoverageByCodeownersCommand(coverage)
12
- generateTeamCoverageCommand(coverage)
11
+ createGroupCoverageByCodeownersCommand(coverageCommand)
12
+ createGenerateTeamCoverageCommand(coverageCommand)
13
13
 
14
- return coverage
14
+ return coverageCommand
15
15
  }
@@ -1,11 +1,5 @@
1
- import { print } from '@toptal/davinci-cli-shared'
2
-
3
1
  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)
2
+ const allTeams = teams.flatMap(entry => entry.usernames)
9
3
 
10
- return uniqueTeams
4
+ return [...new Set(allTeams)]
11
5
  }
@@ -1,21 +1,6 @@
1
- // eslint-disable-next-line import/no-extraneous-dependencies
2
- import { jest } from '@jest/globals'
3
-
4
1
  import { getUniqueTeams } from './get-unique-teams'
5
2
 
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
3
  describe('getUniqueTeams', () => {
15
- beforeEach(() => {
16
- print.grey.mockImplementation(jest.fn())
17
- })
18
-
19
4
  it.each([
20
5
  [
21
6
  [
@@ -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,82 +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
- // eslint-disable-next-line no-restricted-syntax
7
- import Codeowners from '../codeowners/core/codeowners/codeowners.js'
8
- import { CYPRESS_COVERAGE_PATH, JEST_COVERAGE_PATH } from './config.js'
9
- import { sanitizeTeamName } from './services/sanitize-team-name/sanitize-team-name.js'
10
- import { getUniqueTeams } from './services/get-unique-teams/get-unique-teams.js'
11
-
12
- const workspaceRoot = getWorkspaceRoot()
13
-
14
- const readCoverageReport = reportPathName => {
15
- const jestCoverageReportPath = path.join(workspaceRoot, reportPathName)
16
- const data = fs.readFileSync(jestCoverageReportPath, 'utf8')
17
-
18
- return JSON.parse(data)
19
- }
20
-
21
- const splitReportIntoTeams = ({ report, teams, codeowners, coveragePath }) => {
22
- const teamsReport = {}
23
-
24
- Object.entries(report).forEach(([reportPathKey, reportValue]) => {
25
- const filePath = reportValue.path
26
- const relativeWorkspacePath = path.relative(workspaceRoot, filePath)
27
- const owners = codeowners.getOwner(relativeWorkspacePath)
28
-
29
- owners.forEach(owner => {
30
- teamsReport[owner] = {
31
- ...teamsReport[owner],
32
- [reportPathKey]: reportValue,
33
- }
34
- })
35
- })
36
-
37
- teams.map(team => {
38
- const teamReport = teamsReport[team] || {}
39
- const sanitizedTeamPath = sanitizeTeamName(team)
40
-
41
- const reportPath = path.join(
42
- workspaceRoot,
43
- coveragePath,
44
- `coverage-final-${sanitizedTeamPath}.json`
45
- )
46
-
47
- fs.writeFileSync(reportPath, JSON.stringify(teamReport, null, 2))
48
- })
49
- }
50
-
51
- const groupCoverageByCodeowners = async () => {
52
- print.header('Grouping coverage by codeowners')
53
-
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
- )
60
-
61
- const codeowners = new Codeowners(workspaceRoot)
62
- const teams = getUniqueTeams(codeowners.ownerEntries)
63
-
64
- splitReportIntoTeams({
65
- report: jestReport,
66
- teams,
67
- codeowners,
68
- coveragePath: JEST_COVERAGE_PATH,
69
- })
70
- splitReportIntoTeams({
71
- report: cypressReport,
72
- teams,
73
- codeowners,
74
- coveragePath: CYPRESS_COVERAGE_PATH,
75
- })
76
- }
77
-
78
- export const groupCoverageByCodeownersCommand = program =>
79
- program
80
- .command('group-coverage-by-codeowners')
81
- .description('Group the coverage data based on code owners')
82
- .action(groupCoverageByCodeowners)