295 lines
No EOL
12 KiB
JavaScript
295 lines
No EOL
12 KiB
JavaScript
const fs = require('fs');
|
||
const path = require('path');
|
||
require('dotenv').config({ path: path.resolve(__dirname, '../../src/.env') });
|
||
|
||
/**
|
||
* @fileoverview Automatisierungsskript für den Multi-Repository SAP-Deprecation Scan.
|
||
* Durchsucht lokale Unterordner (Repositories) rekursiv nach SAP- und REST-Endpunkten,
|
||
* gleicht diese über die FastAPI ab und generiert einen zusammenfassenden Report.
|
||
* @author MBT
|
||
* @version 1.3 (Zeigt in Details nur veränderte/abgekündigte Endpunkte)
|
||
*/
|
||
|
||
|
||
// --- KONFIGURATION ---
|
||
|
||
const BASE_PATH = './';
|
||
const FASTAPI_URL = 'http://127.0.0.1:8000/informations/bulk';
|
||
const OUTPUT_FILE = './sap_api_scan_report.txt';
|
||
const IGNORED_DIRS = ['node_modules', '.git', '__pycache__', 'venv', 'dist', 'build', '.idea', '.vscode'];
|
||
|
||
const headers = {
|
||
"X-API-Key": process.env.READONLY_APIKEY
|
||
};
|
||
|
||
// Optimierte Regex (Case-Insensitive)
|
||
const ENDPOINT_REGEX = /\b(?:\/?)(?:v\d+|sap)\/[a-zA-Z0-9_\-\/{}]*(?:\/[a-zA-Z0-9_\-\/{}]+)*/i;
|
||
|
||
|
||
// --- FUNKTIONEN ---
|
||
|
||
function getRepositories(baseDir) {
|
||
return fs.readdirSync(baseDir).filter(file => {
|
||
const fullPath = path.join(baseDir, file);
|
||
return fs.statSync(fullPath).isDirectory() && !IGNORED_DIRS.includes(file);
|
||
});
|
||
}
|
||
|
||
function walkSync(dir, filelist = []) {
|
||
const files = fs.readdirSync(dir);
|
||
files.forEach(file => {
|
||
const filepath = path.join(dir, file);
|
||
const stat = fs.statSync(filepath);
|
||
|
||
if (stat.isDirectory()) {
|
||
if (!IGNORED_DIRS.includes(file)) {
|
||
walkSync(filepath, filelist);
|
||
}
|
||
} else {
|
||
filelist.push(filepath);
|
||
}
|
||
});
|
||
return filelist;
|
||
}
|
||
|
||
function scanRepositoryForEndpoints(repoPath, repoName) {
|
||
console.log(`\n🔍 [${repoName}] Starte rekursiven Scan im Ordner: ${path.resolve(repoPath)}...`);
|
||
const files = walkSync(repoPath);
|
||
const endpointLocations = {};
|
||
let checkedFilesCount = 0;
|
||
|
||
files.forEach(file => {
|
||
if (
|
||
file.endsWith('scan_repo.js') ||
|
||
file.endsWith('scan_all_repos.js') ||
|
||
file.endsWith('.txt') ||
|
||
file.endsWith('.json') ||
|
||
file.endsWith('.md') ||
|
||
file.includes(path.basename(OUTPUT_FILE))
|
||
) {
|
||
return;
|
||
}
|
||
|
||
checkedFilesCount++;
|
||
|
||
try {
|
||
const content = fs.readFileSync(file, 'utf8');
|
||
|
||
if (!/sap|v\d+/i.test(content)) {
|
||
return;
|
||
}
|
||
|
||
const lines = content.split(/\r?\n/);
|
||
|
||
lines.forEach((lineText, index) => {
|
||
const lineNumber = index + 1;
|
||
const match = lineText.match(ENDPOINT_REGEX);
|
||
|
||
if (match) {
|
||
const endpoint = match[0].trim();
|
||
if (endpoint.length >= 4) {
|
||
if (!endpointLocations[endpoint]) {
|
||
endpointLocations[endpoint] = [];
|
||
}
|
||
|
||
const relativePath = path.relative(BASE_PATH, file);
|
||
endpointLocations[endpoint].push({
|
||
file: relativePath,
|
||
line: lineNumber
|
||
});
|
||
|
||
console.log(` ✨ Treffer in [${repoName}]: "${endpoint}" gefunden in ${relativePath}:${lineNumber}`);
|
||
}
|
||
}
|
||
});
|
||
} catch (err) {
|
||
if (err.code !== 'EISDIR' && !file.match(/\.(png|jpg|jpeg|gif|ico|zip|tar|gz|pdf|exe|dll)$/i)) {
|
||
console.warn(`⚠️ Konnte Datei nicht lesen: ${file} (${err.message})`);
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log(`🏁 [${repoName}] Scan beendet. ${checkedFilesCount} Dateien durchsucht. ${Object.keys(endpointLocations).length} eindeutige Endpunkte gefunden.`);
|
||
return endpointLocations;
|
||
}
|
||
|
||
async function checkEndpoints(endpoints, batchSize = 25) {
|
||
const consolidatedResult = {
|
||
total_checked: 0,
|
||
results: []
|
||
};
|
||
|
||
for (let i = 0; i < endpoints.length; i += batchSize) {
|
||
const batch = endpoints.slice(i, i + batchSize);
|
||
console.log(` ⏳ Sende Batch ${Math.floor(i / batchSize) + 1} (${batch.length} Endpunkte)...`);
|
||
|
||
const params = new URLSearchParams();
|
||
batch.forEach(ep => params.append("endpoints", ep));
|
||
|
||
try {
|
||
const response = await fetch(`${FASTAPI_URL}?${params.toString()}`, { method: "GET", headers: headers });
|
||
if (!response.ok) {
|
||
throw new Error(`Server meldete Status ${response.status}`);
|
||
}
|
||
|
||
const batchJson = await response.json();
|
||
|
||
consolidatedResult.total_checked += batchJson.total_checked || batch.length;
|
||
if (batchJson.results && Array.isArray(batchJson.results)) {
|
||
consolidatedResult.results.push(...batchJson.results);
|
||
}
|
||
} catch (error) {
|
||
throw new Error(`Fehler in Batch ab Index ${i}: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
return consolidatedResult;
|
||
}
|
||
|
||
/**
|
||
* Steuert den Scan-Workflow und baut die aggregierte Zusammenfassung sowie den gefilterten Detailbericht.
|
||
*/
|
||
async function runMultiRepoScan() {
|
||
const repos = getRepositories(BASE_PATH);
|
||
|
||
if (repos.length === 0) {
|
||
console.log("⚠️ Keine Scan-fähigen Unterordner (Repositories) gefunden!");
|
||
return;
|
||
}
|
||
|
||
console.log(`📂 ${repos.length} Repositories für den Scan gefunden.`);
|
||
|
||
// Statistik-Zähler für die Zusammenfassung
|
||
const stats = {
|
||
totalRepos: repos.length,
|
||
reposWithEndpoints: 0,
|
||
totalEndpointsFound: 0,
|
||
statusActive: 0,
|
||
statusDeprecated: 0,
|
||
statusChanged: 0,
|
||
statusUnknown: 0
|
||
};
|
||
|
||
let detailedReportBody = '';
|
||
|
||
// Schleife über alle gefundenen Repository-Ordner
|
||
for (const repoName of repos) {
|
||
const repoPath = path.join(BASE_PATH, repoName);
|
||
console.log(`\n--------------------------------------------------`);
|
||
console.log(`📦 Verarbeite Repository: ${repoName.toUpperCase()}`);
|
||
console.log(`--------------------------------------------------`);
|
||
|
||
const endpointLocations = scanRepositoryForEndpoints(repoPath, repoName);
|
||
const uniqueEndpoints = Object.keys(endpointLocations);
|
||
|
||
if (uniqueEndpoints.length === 0) {
|
||
console.log(`ℹ️ Keine SAP-Endpunkte in [${repoName}] gefunden.`);
|
||
continue;
|
||
}
|
||
|
||
stats.reposWithEndpoints++;
|
||
stats.totalEndpointsFound += uniqueEndpoints.length;
|
||
|
||
console.log(`🚀 ${uniqueEndpoints.length} Endpunkte in [${repoName}] gefunden. Sende Abfrage an FastAPI...`);
|
||
|
||
try {
|
||
const apiResult = await checkEndpoints(uniqueEndpoints);
|
||
let repoIssuesContent = ''; // Puffer für problematische Endpunkte in diesem spezifischen Repo
|
||
|
||
apiResult.results.forEach(item => {
|
||
const messageLower = item.message.toLowerCase();
|
||
const statusLower = (item.status || '').toLowerCase();
|
||
|
||
// Status für die Statistik auswerten
|
||
const isNoHit = messageLower.includes("keine sap-änderungen oder deprecations gefunden");
|
||
let isIssue = false;
|
||
|
||
if (isNoHit || statusLower === 'active' || statusLower === 'ok') {
|
||
stats.statusActive++;
|
||
} else if (statusLower.includes('deprecat')) {
|
||
stats.statusDeprecated++;
|
||
isIssue = true;
|
||
} else if (statusLower.includes('chang') || statusLower.includes('modif')) {
|
||
stats.statusChanged++;
|
||
isIssue = true;
|
||
} else {
|
||
stats.statusUnknown++;
|
||
isIssue = true; // Unbekannte Stati sicherheitshalber auch auflisten
|
||
}
|
||
|
||
// Nur wenn ein Problem/eine Änderung vorliegt, wird der Endpunkt im Detailbericht aufgelistet
|
||
if (isIssue) {
|
||
const locations = endpointLocations[item.endpoint] || [];
|
||
const locationString = locations
|
||
.map(loc => `${loc.file}:${loc.line}`)
|
||
.join(', ');
|
||
|
||
let logLine = `[❗Warning] Endpoint: ${item.endpoint}\n`;
|
||
logLine += ` Files: ${locationString}\n`;
|
||
logLine += ` Status: ${item.status}\n`;
|
||
logLine += ` Details: ${item.message}\n`;
|
||
logLine += ` Deprecated in Quality: ${item.deprecated[0]}\n`;
|
||
logLine += ` Deprecated in Production: ${item.deprecated[1]}\n`;
|
||
logLine += ` Decommissioned in Quality: ${item.decommissioned[0]}\n`;
|
||
logLine += ` Decommissioned in Production: ${item.decommissioned[1]}\n`;
|
||
|
||
console.log(logLine);
|
||
repoIssuesContent += logLine + "\n";
|
||
}
|
||
});
|
||
|
||
// Falls dieses Repo veränderte/abgekündigte Endpunkte hatte, hänge sie an den Report an
|
||
if (repoIssuesContent.length > 0) {
|
||
detailedReportBody += `📦 REPOSITORY: ${repoName}\n`;
|
||
detailedReportBody += `--------------------------------------------------\n`;
|
||
detailedReportBody += repoIssuesContent;
|
||
detailedReportBody += `\n`;
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Fehler bei API-Abfrage für [${repoName}]:`, error.message);
|
||
detailedReportBody += `📦 REPOSITORY: ${repoName}\n -> API-Fehler bei der Abfrage: ${error.message}\n\n`;
|
||
}
|
||
}
|
||
|
||
// Zusammenbau des finalen Berichts (Zusammenfassung ganz oben!)
|
||
let finalReportContent = `======================================================================\n`;
|
||
finalReportContent += ` GLOBAL SAP DEPRECATION SCAN REPORT - ${new Date().toLocaleString()}\n`;
|
||
finalReportContent += `======================================================================\n\n`;
|
||
|
||
finalReportContent += `📊 SUMMARY:\n`;
|
||
finalReportContent += `----------------------------------------------------------------------\n`;
|
||
finalReportContent += ` Total Repositories Scanned: ${stats.totalRepos}\n`;
|
||
finalReportContent += ` Repositories with Findings: ${stats.reposWithEndpoints}\n`;
|
||
finalReportContent += ` Total Endpoints Detected: ${stats.totalEndpointsFound}\n\n`;
|
||
finalReportContent += ` ENDPOINT STATUS OVERVIEW:\n`;
|
||
finalReportContent += ` 🟢 Active / Unchanged Endpoints: ${stats.statusActive}\n`;
|
||
finalReportContent += ` 🟡 Modified Endpoints (Changed): ${stats.statusChanged}\n`;
|
||
finalReportContent += ` 🔴 Obsolete Endpoints (Deprecated): ${stats.statusDeprecated}\n`;
|
||
if (stats.statusUnknown > 0) {
|
||
finalReportContent += ` ⚪ Unknown / Unclear Status: ${stats.statusUnknown}\n`;
|
||
}
|
||
finalReportContent += `======================================================================\n\n\n`;
|
||
|
||
// Details (filtered issues only!) appended below
|
||
finalReportContent += `📝 ACTIONABLE DETAILS & DETECTED ISSUES (Filtered):\n`;
|
||
finalReportContent += ` (Only modified or deprecated endpoints are listed below)\n`;
|
||
finalReportContent += `======================================================================\n\n`;
|
||
|
||
if (detailedReportBody.length === 0) {
|
||
finalReportContent += `🎉 Excellent! No modified or deprecated endpoints were found in any of the repositories.\n`;
|
||
} else {
|
||
finalReportContent += detailedReportBody;
|
||
}
|
||
|
||
// Gesamtergebnis in Datei schreiben
|
||
try {
|
||
fs.writeFileSync(OUTPUT_FILE, finalReportContent, 'utf8');
|
||
console.log(`\n💾 Der globale Bericht wurde erfolgreich gespeichert: ${path.resolve(OUTPUT_FILE)}`);
|
||
} catch (err) {
|
||
console.error("❌ Bericht konnte nicht geschrieben werden:", err.message);
|
||
}
|
||
}
|
||
|
||
// --- START ---
|
||
runMultiRepoScan(); |