diff --git a/package.json b/package.json index fbe47ec8b4..ac1ad79abe 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "prettier": "^2.2.1", "semver": "^7.5.3", "shx": "^0.3.2", + "sloc": "^0.3.1", "ts-node": "^10.4.0", "typescript": "~5.1.0" }, diff --git a/scripts/list-ownership.js b/scripts/list-ownership.js new file mode 100755 index 0000000000..7f338ceed4 --- /dev/null +++ b/scripts/list-ownership.js @@ -0,0 +1,139 @@ +#!/usr/bin/env node +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('fs-extra'); +const globby = require('globby'); +const sloc = require('sloc'); +const codeownersUtils = require('codeowners-utils'); +const { resolve: resolvePath } = require('path'); + +async function loadOwners(rootDir) { + const codeowners = await codeownersUtils.loadOwners(rootDir); + + return function getOwners(path) { + const { owners } = codeownersUtils.matchFile(path, codeowners); + if ( + owners.includes('@backstage/maintainers') && + owners.includes('@backstage/reviewers') + ) { + return owners.filter(owner => owner !== '@backstage/reviewers'); + } + return owners; + }; +} + +function createLocCounter(rootDir) { + return async path => { + const content = await fs.readFile(resolvePath(rootDir, path), 'utf-8'); + const stats = sloc(content, 'ts'); + return stats.source; + }; +} + +async function printOwnerDirectories(allFiles, getOwners, getLoc, onlyOwner) { + const countByPath = new Map(); + + let total = 0; + let totalShare = 0; + for (const file of allFiles) { + const owners = getOwners(file); + const loc = await getLoc(file); + + if (owners.includes(onlyOwner)) { + const share = loc / owners.length; + total += loc; + totalShare += share; + + const path = file.split('/').slice(0, 2).join('/'); + if ((await fs.stat(path)).isDirectory()) { + countByPath.set(path, (countByPath.get(path) || 0) + share); + } + } + } + + const sortedPaths = Array.from(countByPath) + .map(([path, loc]) => ({ path, loc: Math.round(loc) })) + .sort((a, b) => b.loc - a.loc); + + const maxPathLen = Math.max(...sortedPaths.map(({ path }) => path.length)); + for (const { path, loc } of sortedPaths) { + console.log(`${path.padEnd(maxPathLen)} ${loc}`); + } + console.log(); + console.log('Total share:', Math.round(totalShare)); + console.log('Total lines of code:', total); +} + +async function printAllOwners(allFiles, getOwners, getLoc) { + const countByOwners = new Map(); + + let total = 0; + for (const file of allFiles) { + const owners = getOwners(file); + const loc = await getLoc(file); + + total += loc; + const share = loc / owners.length; + for (const owner of owners) { + countByOwners.set(owner, (countByOwners.get(owner) || 0) + share); + } + } + + const sortedOwners = Array.from(countByOwners) + .map(([owner, loc]) => ({ owner, loc: Math.round(loc) })) + .sort((a, b) => b.loc - a.loc); + + const maxOwnerLen = Math.max( + ...sortedOwners.map(({ owner }) => owner.length), + ); + for (const { owner, loc } of sortedOwners) { + console.log(`${owner.padEnd(maxOwnerLen)} ${loc}`); + } + console.log(); + console.log('Total lines of code:', total); +} + +async function main(onlyOwner) { + const rootDir = resolvePath(__dirname, '..'); + + const allFiles = await globby( + [ + '**/*.{js,jsx,ts,tsx,mjs,cjs}', + '!**/*.{generated,test}.*', + '!**/{__fixtures__,fixtures}', + ], + { + cwd: rootDir, + gitignore: true, + followSymbolicLinks: false, + }, + ); + + const getOwners = await loadOwners(rootDir); + const getLoc = createLocCounter(rootDir); + + if (onlyOwner) { + await printOwnerDirectories(allFiles, getOwners, getLoc, onlyOwner); + } else { + await printAllOwners(allFiles, getOwners, getLoc); + } +} + +main(...process.argv.slice(2)).catch(err => { + console.error(err.stack); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index 2220b18fef..33d6c2602f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22833,12 +22833,12 @@ __metadata: languageName: node linkType: hard -"cli-table@npm:^0.3.1": - version: 0.3.6 - resolution: "cli-table@npm:0.3.6" +"cli-table@npm:^0.3.1, cli-table@npm:^0.3.11": + version: 0.3.11 + resolution: "cli-table@npm:0.3.11" dependencies: colors: 1.0.3 - checksum: b0cd08578c810240920438cc2b3ffb4b4f5106b29f3362707f1d8cfc0c0440ad2afb70b96e30ce37f72f0ffe1e844ae7341dde4df17d51ad345eb186a5903af2 + checksum: 59fb61f992ac9bc8610ed98c72bf7f5d396c5afb42926b6747b46b0f8bb98a0dfa097998e77542ac334c1eb7c18dbf4f104d5783493273c5ec4c34084aa7c663 languageName: node linkType: hard @@ -39499,7 +39499,7 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:~3.6.0": +"readdirp@npm:^3.3.0, readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" dependencies: @@ -40423,6 +40423,7 @@ __metadata: prettier: ^2.2.1 semver: ^7.5.3 shx: ^0.3.2 + sloc: ^0.3.1 ts-node: ^10.4.0 typescript: ~5.1.0 languageName: unknown @@ -41206,6 +41207,20 @@ __metadata: languageName: node linkType: hard +"sloc@npm:^0.3.1": + version: 0.3.1 + resolution: "sloc@npm:0.3.1" + dependencies: + async: ^3.2.4 + cli-table: ^0.3.11 + commander: ^11.0.0 + readdirp: ^3.3.0 + bin: + sloc: bin/sloc + checksum: 3075150912b4fbb90167c446b155fcfffa20351245d7ae2a7139b2c3ba5dd9fd01fe97fc11a917d5f6a66778c3a9ab4c6ceff0bcd234338f38942fb7238ab927 + languageName: node + linkType: hard + "slugify@npm:^1.6.4": version: 1.6.6 resolution: "slugify@npm:1.6.6"