community-plugins/scripts/generate-upgrade-dashboard.js

179 lines
5.3 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/*
* Copyright 2025 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.
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import semver from 'semver';
import { listWorkspaces } from './list-workspaces.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// get latest stable Backstage release
async function getLatestBackstageVersion() {
const response = await fetch(
'https://versions.backstage.io/v1/tags/main/manifest.json',
);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const manifest = await response.json();
return manifest.releaseVersion;
}
// get all workspace versions
async function getWorkspaceVersions() {
const workspaceNames = await listWorkspaces();
const workspacesDir = path.join(__dirname, '..', 'workspaces');
const workspaces = [];
for (const workspaceName of workspaceNames) {
const backstageJsonPath = path.join(
workspacesDir,
workspaceName,
'backstage.json',
);
if (fs.existsSync(backstageJsonPath)) {
try {
const backstageJson = JSON.parse(
fs.readFileSync(backstageJsonPath, 'utf8'),
);
if (backstageJson.version) {
workspaces.push({
name: workspaceName,
version: backstageJson.version,
});
}
} catch (error) {
console.warn(
`Warning: Could not read version from ${workspaceName}/backstage.json:`,
error.message,
);
}
}
}
return workspaces;
}
// calculate version difference
function getVersionDifference(currentVersion, latestVersion) {
const current = semver.clean(currentVersion);
const latest = semver.clean(latestVersion);
if (!current || !latest) return 0;
// if major versions differ, treat as significantly outdated
if (semver.major(current) !== semver.major(latest)) {
return semver.major(latest) > semver.major(current) ? 10 : 0;
}
return semver.minor(latest) - semver.minor(current);
}
// categorize workspaces by how outdated they are
function categorizeWorkspaces(workspaces, latestVersion) {
const tiers = { tier1: [], tier2: [], tier3: [] };
workspaces.forEach(workspace => {
const versionDiff = getVersionDifference(workspace.version, latestVersion);
if (versionDiff >= 3) tiers.tier1.push(workspace);
else if (versionDiff === 2) tiers.tier2.push(workspace);
else if (versionDiff === 1) tiers.tier3.push(workspace);
});
// sort each tier by workspace name
Object.values(tiers).forEach(tier =>
tier.sort((a, b) => a.name.localeCompare(b.name)),
);
return tiers;
}
// generate markdown table for a tier
function generateTierTable(workspaces, emoji, title, description) {
if (workspaces.length === 0) return '';
let output = `## ${emoji} ${title}${
description ? ` ${description}` : ''
}\n\n`;
output += '| Workspace | Current Version |\n';
output += '|-----------|----------------|\n';
workspaces.forEach(workspace => {
output += `| ${workspace.name} | ${workspace.version} |\n`;
});
return `${output}\n`;
}
// generate the dashboard output
function generateDashboard(tiers, latestVersion) {
let output =
'Tracking workspaces not on the latest Backstage minor version\n\n';
output += `**Latest Version:** ${latestVersion}\n\n`;
output += generateTierTable(
tiers.tier1,
'🔴',
'≥ 3 minor versions behind',
'',
);
output += generateTierTable(tiers.tier2, '🟠', '2 minor versions behind', '');
output += generateTierTable(tiers.tier3, '🟡', '1 minor version behind', '');
const totalOutdated =
tiers.tier1.length + tiers.tier2.length + tiers.tier3.length;
if (totalOutdated === 0) {
output += '## 🎉 All workspaces are up to date!\n\n';
}
output += '---\n\n### Summary\n\n';
output += `- **Total outdated workspaces:** ${totalOutdated}\n`;
output += `- **≥ 3 minor versions behind:** ${tiers.tier1.length}\n`;
output += `- **2 minor versions behind:** ${tiers.tier2.length}\n`;
output += `- **1 minor version behind:** ${tiers.tier3.length}\n\n`;
output += `*Dashboard generated on ${
new Date().toISOString().split('T')[0]
}*\n`;
return output.trim();
}
// main function
async function main() {
const latestVersion = await getLatestBackstageVersion();
const workspaces = await getWorkspaceVersions();
const tiers = categorizeWorkspaces(workspaces, latestVersion);
const dashboard = generateDashboard(tiers, latestVersion);
console.log(dashboard);
}
// run the script
main().catch(error => {
console.error('Error generating dashboard:', error);
process.exit(1);
});
export {
getLatestBackstageVersion,
getWorkspaceVersions,
categorizeWorkspaces,
generateDashboard,
};