From 882c5d5a5f9a0864ca3a78301325bfcdd008f1fd Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 9 Oct 2023 22:49:54 +0200 Subject: [PATCH 1/6] chore: create build plugins report script Signed-off-by: Camila Belo --- .gitignore | 5 +- package.json | 2 + scripts/build-plugins-report.js | 150 ++++++++++++++++++++++++++++++++ yarn.lock | 3 +- 4 files changed, 158 insertions(+), 2 deletions(-) create mode 100755 scripts/build-plugins-report.js diff --git a/.gitignore b/.gitignore index 7149dbabc1..47e76b6d37 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,7 @@ e2e-test-report/ # VS Code backing up svg files *svg.bkp -*svg.dtmp \ No newline at end of file +*svg.dtmp + +# Scripts +plugins-report.html \ No newline at end of file diff --git a/package.json b/package.json index bad49ab55f..595a6ff78f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "backstage-repo-tools api-reports --allow-warnings 'packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-wrong-input-file-type --validate-release-tags", "build:api-docs": "LANG=en_EN yarn build:api-reports --docs", + "build:plugins-report": "node ./scripts/build-plugins-report", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false", "clean": "backstage-cli repo clean", @@ -83,6 +84,7 @@ "lint-staged": "^13.0.0", "minimist": "^1.2.5", "node-gyp": "^9.4.0", + "open": "^7.2.1", "prettier": "^2.2.1", "semver": "^7.5.3", "shx": "^0.3.2", diff --git a/scripts/build-plugins-report.js b/scripts/build-plugins-report.js new file mode 100755 index 0000000000..185562cda8 --- /dev/null +++ b/scripts/build-plugins-report.js @@ -0,0 +1,150 @@ +#!/usr/bin/env node +/* + * Copyright 2023 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 path = require('path'); +const open = require('open'); +const { execFile: execFileCb } = require('child_process'); +const { promisify } = require('util'); + +const execFile = promisify(execFileCb); + +const rootDirectory = path.resolve(__dirname, '..'); + +async function run(command, ...args) { + const { stdout, stderr } = await execFile(command, args, { + cwd: rootDirectory, + }); + + if (stderr) { + console.error(stderr); + } + + return stdout.trim(); +} + +function generateHtmlReport(rows) { + const thead = `${rows[0].map(cell => `${cell}`).join('')}`; + + const tbody = rows + .slice(1) + .map(row => `${row.map(cell => `${cell}`).join('')}`) + .join(''); + + return ` + + + Backstage Plugins Report + + + + + ${thead} + ${tbody} +
+ + + `; +} + +const PLUGIN_ROLES = ['frontend-plugin', 'backend-plugin']; + +async function main() { + const pluginsDirectory = path.resolve(rootDirectory, 'plugins'); + + const directoryNames = fs + .readdirSync(pluginsDirectory, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name); + + const tableRows = [['Plugin', 'Author', 'Message', 'Date']]; + + for (const directoryName of directoryNames) { + console.log(`πŸ”Ž Reading data for ${directoryName}`); + + const directoryPath = path.resolve(pluginsDirectory, directoryName); + + const packageJson = await fs.readJson( + path.resolve(directoryPath, 'package.json'), + ); + + const plugin = PLUGIN_ROLES.includes(packageJson?.backstage?.role ?? ''); + + if (!plugin) continue; + + let data; + let skip = 0; + const maxCount = 10; + while (!data && skip <= 100) { + const output = await run( + 'git', + 'log', + 'origin/master', + `--skip=${skip}`, + `--max-count=${maxCount}`, + '--format=%an;%s;%as', + directoryPath, + ); + + data = output.split('\n').find(commit => { + // exclude merge commits + if (commit.includes('Merge pull request #')) { + return false; + } + + // exclude commits authored by a bot + if ( + commit.startsWith('renovate[bot]') || + commit.startsWith('github-actions[bot]') + ) { + return false; + } + + return true; + }); + + skip += 10; + } + + if (data) { + tableRows.push([directoryName, ...data.split(';')]); + } else { + console.log(`⚠️ No data found for ${directoryName}`); + } + } + + const file = 'plugins-report.html'; + + console.log(`πŸ“Š Generating plugins report...`); + + fs.writeFile(file, generateHtmlReport(tableRows), err => { + if (err) throw err; + }); + + console.log(`πŸ“„ Opening ${file} file...`); + + open(file); +} + +main(process.argv.slice(2)).catch(error => { + console.error(error.stack || error); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index 3659fce669..2d03125f54 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34349,7 +34349,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^7.4.2": +"open@npm:^7.2.1, open@npm:^7.4.2": version: 7.4.2 resolution: "open@npm:7.4.2" dependencies: @@ -38468,6 +38468,7 @@ __metadata: lint-staged: ^13.0.0 minimist: ^1.2.5 node-gyp: ^9.4.0 + open: ^7.2.1 prettier: ^2.2.1 semver: ^7.5.3 shx: ^0.3.2 From 565dff3dac4502dcef42e62aff13c1ee29c90e3f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 11 Oct 2023 10:11:22 +0200 Subject: [PATCH 2/6] refactor: apply review suggestions Signed-off-by: Camila Belo --- .gitignore | 2 +- scripts/build-plugins-report.js | 104 ++++++++++++++++---------------- 2 files changed, 52 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 47e76b6d37..c0ef0d2001 100644 --- a/.gitignore +++ b/.gitignore @@ -162,4 +162,4 @@ e2e-test-report/ *svg.dtmp # Scripts -plugins-report.html \ No newline at end of file +plugins-report.csv \ No newline at end of file diff --git a/scripts/build-plugins-report.js b/scripts/build-plugins-report.js index 185562cda8..2b7b267faa 100755 --- a/scripts/build-plugins-report.js +++ b/scripts/build-plugins-report.js @@ -37,35 +37,12 @@ async function run(command, ...args) { return stdout.trim(); } -function generateHtmlReport(rows) { - const thead = `${rows[0].map(cell => `${cell}`).join('')}`; - - const tbody = rows - .slice(1) - .map(row => `${row.map(cell => `${cell}`).join('')}`) - .join(''); - - return ` - - - Backstage Plugins Report - - - - - ${thead} - ${tbody} -
- - - `; -} - -const PLUGIN_ROLES = ['frontend-plugin', 'backend-plugin']; +const PLUGIN_AND_MODULE_ROLES = [ + 'frontend-plugin', + 'backend-plugin', + 'frontend-plugin-module', + 'frontend-plugin-module', +]; async function main() { const pluginsDirectory = path.resolve(rootDirectory, 'plugins'); @@ -75,7 +52,7 @@ async function main() { .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); - const tableRows = [['Plugin', 'Author', 'Message', 'Date']]; + const content = ['Plugin;Author;Message;Hash;Date']; for (const directoryName of directoryNames) { console.log(`πŸ”Ž Reading data for ${directoryName}`); @@ -86,9 +63,11 @@ async function main() { path.resolve(directoryPath, 'package.json'), ); - const plugin = PLUGIN_ROLES.includes(packageJson?.backstage?.role ?? ''); + const isPluginOrModule = PLUGIN_AND_MODULE_ROLES.includes( + packageJson?.backstage?.role ?? '', + ); - if (!plugin) continue; + if (!isPluginOrModule) continue; let data; let skip = 0; @@ -100,42 +79,61 @@ async function main() { 'origin/master', `--skip=${skip}`, `--max-count=${maxCount}`, - '--format=%an;%s;%as', - directoryPath, + '--format=%an <%ae>;%s;%h;%as', + '--', + // ignore changes on README and package.json files + path.resolve(directoryPath, 'src', '**', '*.ts'), + path.resolve(directoryPath, 'src', '**', '*.tsx'), ); - data = output.split('\n').find(commit => { - // exclude merge commits - if (commit.includes('Merge pull request #')) { - return false; - } + data = output + .trim() + .split('\n') + .find(commit => { + if (!commit) return false; - // exclude commits authored by a bot - if ( - commit.startsWith('renovate[bot]') || - commit.startsWith('github-actions[bot]') - ) { - return false; - } + const [author, message] = commit.split(';'); - return true; - }); + // exclude merge commits + if (message.startsWith('Merge pull request #')) { + return false; + } - skip += 10; + // exclude commits authored by a bot + if (author.includes('[bot]')) { + return false; + } + + // exclude core maintainers' commits + if ( + [ + 'Johan Haals ', + 'Patrik Oldsberg ', + 'Fredrik AdelΓΆw ', + 'blam ', + ].includes(author) + ) { + return false; + } + + return true; + }); + + skip += maxCount; } if (data) { - tableRows.push([directoryName, ...data.split(';')]); + content.push(`${directoryName};${data}`); } else { - console.log(`⚠️ No data found for ${directoryName}`); + console.log(` 🚩 No data found for ${directoryName}`); } } - const file = 'plugins-report.html'; + const file = 'plugins-report.csv'; console.log(`πŸ“Š Generating plugins report...`); - fs.writeFile(file, generateHtmlReport(tableRows), err => { + fs.writeFile(file, content.join('\n'), err => { if (err) throw err; }); From ef26bd727a9d12cd1b16411f41d669a978871de9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 11 Oct 2023 11:02:44 +0200 Subject: [PATCH 3/6] scripts/build-plugins-report: don't open report at the end Signed-off-by: Patrik Oldsberg --- package.json | 1 - scripts/build-plugins-report.js | 5 +---- yarn.lock | 3 +-- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 595a6ff78f..b50e087aa7 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,6 @@ "lint-staged": "^13.0.0", "minimist": "^1.2.5", "node-gyp": "^9.4.0", - "open": "^7.2.1", "prettier": "^2.2.1", "semver": "^7.5.3", "shx": "^0.3.2", diff --git a/scripts/build-plugins-report.js b/scripts/build-plugins-report.js index 2b7b267faa..95e9592f18 100755 --- a/scripts/build-plugins-report.js +++ b/scripts/build-plugins-report.js @@ -17,7 +17,6 @@ const fs = require('fs-extra'); const path = require('path'); -const open = require('open'); const { execFile: execFileCb } = require('child_process'); const { promisify } = require('util'); @@ -137,9 +136,7 @@ async function main() { if (err) throw err; }); - console.log(`πŸ“„ Opening ${file} file...`); - - open(file); + console.log(`πŸ“„ Report generated at ${file}`); } main(process.argv.slice(2)).catch(error => { diff --git a/yarn.lock b/yarn.lock index 2d03125f54..3659fce669 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34349,7 +34349,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^7.2.1, open@npm:^7.4.2": +"open@npm:^7.4.2": version: 7.4.2 resolution: "open@npm:7.4.2" dependencies: @@ -38468,7 +38468,6 @@ __metadata: lint-staged: ^13.0.0 minimist: ^1.2.5 node-gyp: ^9.4.0 - open: ^7.2.1 prettier: ^2.2.1 semver: ^7.5.3 shx: ^0.3.2 From 409e24c2ea8633b558f29358ca64c00e25c29fac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 11 Oct 2023 11:03:34 +0200 Subject: [PATCH 4/6] scripts/build-plugins-report: exclude changes to tests Signed-off-by: Patrik Oldsberg --- scripts/build-plugins-report.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/build-plugins-report.js b/scripts/build-plugins-report.js index 95e9592f18..7172d9dc48 100755 --- a/scripts/build-plugins-report.js +++ b/scripts/build-plugins-report.js @@ -81,8 +81,13 @@ async function main() { '--format=%an <%ae>;%s;%h;%as', '--', // ignore changes on README and package.json files - path.resolve(directoryPath, 'src', '**', '*.ts'), - path.resolve(directoryPath, 'src', '**', '*.tsx'), + path.posix.resolve(directoryPath, 'src'), + `:(exclude)${path.posix.resolve( + directoryPath, + 'src', + '**', + '*.test.*', + )}`, ); data = output From d749e50595e51dc9b132f42561ea331cccbd16bc Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 11 Oct 2023 11:27:44 +0200 Subject: [PATCH 5/6] fix: core maintainers commit check Signed-off-by: Camila Belo --- scripts/build-plugins-report.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/build-plugins-report.js b/scripts/build-plugins-report.js index 7172d9dc48..f3f1f9fa32 100755 --- a/scripts/build-plugins-report.js +++ b/scripts/build-plugins-report.js @@ -43,6 +43,13 @@ const PLUGIN_AND_MODULE_ROLES = [ 'frontend-plugin-module', ]; +const CORE_MAINTAINER_EMAILS = [ + 'ben@blam.sh', + 'freben@gmail.com', + 'poldsberg@gmail.com', + 'johan.haals@gmail.com', +]; + async function main() { const pluginsDirectory = path.resolve(rootDirectory, 'plugins'); @@ -109,14 +116,7 @@ async function main() { } // exclude core maintainers' commits - if ( - [ - 'Johan Haals ', - 'Patrik Oldsberg ', - 'Fredrik AdelΓΆw ', - 'blam ', - ].includes(author) - ) { + if (CORE_MAINTAINER_EMAILS.some(email => author.includes(email))) { return false; } From 85a51734ec29893b7d9470f0d04aa3cd7ba5a4a3 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 12 Oct 2023 08:52:30 +0200 Subject: [PATCH 6/6] refactor: optimize plugins report script Signed-off-by: Camila Belo --- scripts/build-plugins-report.js | 211 ++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 89 deletions(-) diff --git a/scripts/build-plugins-report.js b/scripts/build-plugins-report.js index f3f1f9fa32..2caaba4530 100755 --- a/scripts/build-plugins-report.js +++ b/scripts/build-plugins-report.js @@ -23,6 +23,7 @@ const { promisify } = require('util'); const execFile = promisify(execFileCb); const rootDirectory = path.resolve(__dirname, '..'); +const pluginsDirectory = path.resolve(rootDirectory, 'plugins'); async function run(command, ...args) { const { stdout, stderr } = await execFile(command, args, { @@ -36,112 +37,144 @@ async function run(command, ...args) { return stdout.trim(); } -const PLUGIN_AND_MODULE_ROLES = [ - 'frontend-plugin', - 'backend-plugin', - 'frontend-plugin-module', - 'frontend-plugin-module', -]; +function findLatestValidCommit(commits, directoryPath) { + return commits.find(commit => { + const { author, message, files } = commit; -const CORE_MAINTAINER_EMAILS = [ - 'ben@blam.sh', - 'freben@gmail.com', - 'poldsberg@gmail.com', - 'johan.haals@gmail.com', -]; + // exclude merge commits + if (message.startsWith('Merge pull request #')) { + return false; + } + + // exclude commits authored by a bot + if (author.includes('[bot]')) { + return false; + } + + // exclude core maintainers' commits + if ( + [ + 'ben@blam.sh', + 'freben@gmail.com', + 'poldsberg@gmail.com', + 'johan.haals@gmail.com', + ].some(email => author.includes(email)) + ) { + return false; + } + + // ignore multiple plugins changes + if ( + files.some(file => { + const fileFullPath = path.resolve(rootDirectory, file); + if (!fileFullPath.startsWith(pluginsDirectory)) { + return false; + } + const pluginBasePath = directoryPath.replace( + /-(backend|common|react|node).*$/, + '', + ); + return !fileFullPath.startsWith(pluginBasePath); + }) + ) { + return false; + } + + return true; + }); +} + +async function getPluginDirectory(directoryName) { + const directoryPath = path.resolve(pluginsDirectory, directoryName); + const packageJson = await fs.readJson( + path.resolve(directoryPath, 'package.json'), + ); + return { directoryName, directoryPath, packageJson }; +} + +function parseCommitsLog(log) { + const lines = log.split('\n'); + return lines.reduce((commits, line) => { + if (!line) return commits; + if (line.includes(';')) { + const [author, message, date] = line.split(';'); + return [...commits, { author, message, date, files: [] }]; + } + const { files, ...commit } = commits.pop(); + return [...commits, { ...commit, files: [...files, line] }]; + }, []); +} + +async function readDirectoryCommits(directoryName) { + const maxCount = 100; + const directoryPath = path.resolve(pluginsDirectory, directoryName); + + const logOutput = await run( + 'git', + 'log', + 'origin/master', + '--name-only', + `--max-count=${maxCount}`, + '--pretty=format:%an <%ae>;%s;%as', + '--', + // ignore changes on README and package.json files + path.posix.resolve(directoryPath, 'src'), + `:(exclude)${path.posix.resolve(directoryPath, 'src', '**', '*.test.*')}`, + ); + + return parseCommitsLog(logOutput); +} + +async function getLatestDirectoryCommit({ directoryName, directoryPath }) { + console.log(`πŸ”Ž Reading data for ${directoryName}`); + + const commits = await readDirectoryCommits(directoryName); + + const commit = findLatestValidCommit(commits, directoryPath) ?? { + author: '-', + message: '-', + date: '-', + }; + + return { plugin: directoryName, ...commit }; +} + +function isValidDirectory({ packageJson }) { + const roles = [ + 'frontend-plugin', + 'frontend-plugin-module', + 'backend-plugin', + 'backend-plugin-module', + ]; + return roles.includes(packageJson?.backstage?.role ?? ''); +} async function main() { - const pluginsDirectory = path.resolve(rootDirectory, 'plugins'); - const directoryNames = fs .readdirSync(pluginsDirectory, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); - const content = ['Plugin;Author;Message;Hash;Date']; + const directories = await Promise.all(directoryNames.map(getPluginDirectory)); - for (const directoryName of directoryNames) { - console.log(`πŸ”Ž Reading data for ${directoryName}`); + const commits = await Promise.all( + directories.filter(isValidDirectory).map(getLatestDirectoryCommit), + ); - const directoryPath = path.resolve(pluginsDirectory, directoryName); + const fileName = 'plugins-report.csv'; - const packageJson = await fs.readJson( - path.resolve(directoryPath, 'package.json'), - ); - - const isPluginOrModule = PLUGIN_AND_MODULE_ROLES.includes( - packageJson?.backstage?.role ?? '', - ); - - if (!isPluginOrModule) continue; - - let data; - let skip = 0; - const maxCount = 10; - while (!data && skip <= 100) { - const output = await run( - 'git', - 'log', - 'origin/master', - `--skip=${skip}`, - `--max-count=${maxCount}`, - '--format=%an <%ae>;%s;%h;%as', - '--', - // ignore changes on README and package.json files - path.posix.resolve(directoryPath, 'src'), - `:(exclude)${path.posix.resolve( - directoryPath, - 'src', - '**', - '*.test.*', - )}`, - ); - - data = output - .trim() - .split('\n') - .find(commit => { - if (!commit) return false; - - const [author, message] = commit.split(';'); - - // exclude merge commits - if (message.startsWith('Merge pull request #')) { - return false; - } - - // exclude commits authored by a bot - if (author.includes('[bot]')) { - return false; - } - - // exclude core maintainers' commits - if (CORE_MAINTAINER_EMAILS.some(email => author.includes(email))) { - return false; - } - - return true; - }); - - skip += maxCount; - } - - if (data) { - content.push(`${directoryName};${data}`); - } else { - console.log(` 🚩 No data found for ${directoryName}`); - } - } - - const file = 'plugins-report.csv'; + const fileContent = [ + 'Plugin;Author;Message;Date', + ...commits.map(c => `${c.plugin};${c.author};${c.message};${c.date}`), + ]; console.log(`πŸ“Š Generating plugins report...`); - fs.writeFile(file, content.join('\n'), err => { + fs.writeFile(fileName, fileContent.join('\n'), err => { if (err) throw err; }); - console.log(`πŸ“„ Report generated at ${file}`); + console.log(`πŸ“„ Report generated at ${fileName}`); } main(process.argv.slice(2)).catch(error => {