SAP_DM_Deprecated_APIS/demo/scan_repo.js

295 lines
No EOL
12 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

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();