Files
Orchestrator/bahn/project-audit/scripts/gitlab-inventory.ps1
T
ankn a5f8fb49ab Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
      Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
      Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)

Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
2026-06-30 20:39:52 +02:00

269 lines
12 KiB
PowerShell

<#
.SYNOPSIS
GitLab Projekt-Inventar fuer pathOS / Bestellsystem
.DESCRIPTION
Zieht alle Projekte aus der GitLab-Gruppe und erstellt eine strukturierte Uebersicht.
Kompatibel mit PowerShell 5.1 — keine zusaetzlichen Installationen noetig.
#>
$GitLabUrl = "https://git.tech.rz.db.de"
$GroupPath = "bestellsystem1"
$ApiBase = "$GitLabUrl/api/v4"
$PerPage = 100
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$OutputDir = Join-Path (Split-Path -Parent $ScriptDir) "data\gitlab-inventory"
# SSL-Handling (auskommentieren falls selbstsigniertes Zertifikat)
# [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
# TLS 1.2 erzwingen
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Token
$Token = Read-Host -Prompt "GitLab Personal Access Token (read_api)"
$Headers = @{ "PRIVATE-TOKEN" = $Token }
function Safe-String($val) {
if ($null -eq $val) { return "" }
return [string]$val
}
function Invoke-GitLabApi {
param([string]$Endpoint, [hashtable]$Params = @{})
$Params["per_page"] = $PerPage
$AllResults = @()
$Page = 1
do {
$Params["page"] = $Page
$QS = ($Params.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join "&"
$Uri = "$ApiBase$Endpoint`?$QS"
try {
$Resp = Invoke-WebRequest -Uri $Uri -Headers $Headers -UseBasicParsing -ErrorAction Stop
$Data = $Resp.Content | ConvertFrom-Json
if ($null -eq $Data) { break }
if ($Data -is [array]) {
if ($Data.Count -eq 0) { break }
$AllResults += $Data
} else {
return $Data
}
$TP = 1
if ($Resp.Headers["X-Total-Pages"]) { $TP = [int]$Resp.Headers["X-Total-Pages"] }
Write-Host " Seite $Page/$TP ($($Data.Count) Eintraege)" -ForegroundColor DarkGray
$Page++
} catch {
Write-Host " FEHLER: $($_.Exception.Message)" -ForegroundColor Red
throw
}
} while ($Page -le $TP)
return $AllResults
}
function Get-ProjectLanguages($ProjectId) {
try {
$Uri = "$ApiBase/projects/$ProjectId/languages"
$R = Invoke-WebRequest -Uri $Uri -Headers $Headers -UseBasicParsing -ErrorAction Stop
return ($R.Content | ConvertFrom-Json)
} catch { return $null }
}
function Get-ProjectReadme($ProjectId) {
foreach ($Branch in @("main", "master", "develop")) {
try {
$Uri = "$ApiBase/projects/$ProjectId/repository/files/README.md/raw?ref=$Branch"
$R = Invoke-WebRequest -Uri $Uri -Headers $Headers -UseBasicParsing -ErrorAction Stop
$C = $R.Content
if ($C.Length -gt 3000) { $C = $C.Substring(0, 3000) + "`n`n... (gekuerzt)" }
return $C
} catch { continue }
}
return $null
}
function Get-RepoTree($ProjectId) {
foreach ($Branch in @("main", "master", "develop")) {
try {
$Uri = "$ApiBase/projects/$ProjectId/repository/tree?ref=$Branch&per_page=100"
$R = Invoke-WebRequest -Uri $Uri -Headers $Headers -UseBasicParsing -ErrorAction Stop
return ($R.Content | ConvertFrom-Json)
} catch { continue }
}
return @()
}
function Get-ProjectTags($Project, $Languages, $Tree) {
$Tags = @()
$Name = (Safe-String $Project.name).ToLower()
$Desc = (Safe-String $Project.description).ToLower()
$Files = @($Tree | ForEach-Object { $_.name.ToLower() })
if ($Languages) {
$LangProps = $Languages | Get-Member -MemberType NoteProperty
foreach ($lp in $LangProps) {
$lang = $lp.Name.ToLower()
if ($lang -eq "java") { $Tags += "java" }
if ($lang -eq "kotlin") { $Tags += "kotlin" }
if ($lang -match "typescript|javascript") { $Tags += "frontend" }
if ($lang -eq "python") { $Tags += "python" }
}
}
if ("pom.xml" -in $Files) { $Tags += "maven" }
if ("build.gradle" -in $Files -or "build.gradle.kts" -in $Files) { $Tags += "gradle" }
if ("package.json" -in $Files) { $Tags += "npm" }
if ("Dockerfile" -in $Files -or "dockerfile" -in $Files) { $Tags += "docker" }
if ($Name -match "lib|library|common|shared|util" -or $Desc -match "lib|library|common|shared|util") { $Tags += "library" }
if ($Name -match "service|api|backend" -or $Desc -match "service|api|backend") { $Tags += "service" }
if ($Name -match "ui|frontend|web|app" -or $Desc -match "ui|frontend|web|app") { $Tags += "frontend-app" }
if ($Name -match "test|e2e|integration" -or $Desc -match "test|e2e|integration") { $Tags += "testing" }
if ($Name -match "config|infra|deploy|pipeline|ci" -or $Desc -match "config|infra|deploy|pipeline|ci") { $Tags += "infrastructure" }
if ($Project.last_activity_at) {
try {
$Last = [DateTime]::Parse($Project.last_activity_at)
$Days = ((Get-Date) - $Last).Days
if ($Days -gt 365) { $Tags += "inactive-1y+" }
elseif ($Days -gt 180) { $Tags += "inactive-6m+" }
} catch {}
}
if ($Project.archived) { $Tags += "archived" }
return ($Tags | Select-Object -Unique)
}
# === MAIN ===
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " pathOS / Bestellsystem - GitLab Projekt-Inventar" -ForegroundColor Cyan
Write-Host "============================================================" -ForegroundColor Cyan
Write-Host " GitLab: $GitLabUrl"
Write-Host " Gruppe: $GroupPath"
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
New-Item -ItemType Directory -Path "$OutputDir\readmes" -Force | Out-Null
try {
Write-Host "`n>> Lade Gruppeninfo..." -ForegroundColor Yellow
$EncodedGroup = [Uri]::EscapeDataString($GroupPath)
$GroupInfo = Invoke-GitLabApi -Endpoint "/groups/$EncodedGroup"
Write-Host " Gruppe: $($GroupInfo.name) (ID: $($GroupInfo.id))" -ForegroundColor Green
Write-Host "`n>> Lade Untergruppen..." -ForegroundColor Yellow
$Subgroups = @(Invoke-GitLabApi -Endpoint "/groups/$EncodedGroup/subgroups" -Params @{ all_available = "true" })
Write-Host " $($Subgroups.Count) Untergruppen" -ForegroundColor Green
Write-Host "`n>> Lade alle Projekte..." -ForegroundColor Yellow
$Projects = @(Invoke-GitLabApi -Endpoint "/groups/$EncodedGroup/projects" -Params @{
include_subgroups = "true"; with_shared = "false"; order_by = "name"; sort = "asc"
})
Write-Host " $($Projects.Count) Projekte" -ForegroundColor Green
Write-Host "`n>> Reichere Projekte an..." -ForegroundColor Yellow
$Enriched = @()
$Total = $Projects.Count
$i = 0
foreach ($P in $Projects) {
$i++
Write-Host " [$i/$Total] $($P.name)..." -NoNewline
$Langs = Get-ProjectLanguages $P.id
$Readme = Get-ProjectReadme $P.id
$Tree = @(Get-RepoTree $P.id)
$Tags = @(Get-ProjectTags $P $Langs $Tree)
$Obj = [PSCustomObject]@{
id = $P.id
name = $P.name
path = $P.path_with_namespace
description = Safe-String $P.description
branch = $P.default_branch
archived = $P.archived
created = $P.created_at
last_active = $P.last_activity_at
namespace = $P.namespace.full_path
web_url = $P.web_url
languages = $Langs
tags = $Tags
files = @($Tree | ForEach-Object { $_.name })
readme = $Readme
}
$Enriched += $Obj
if ($Readme) {
$SafeName = $P.path_with_namespace -replace "/", "__"
"# $($P.name)`n`n> Quelle: $($P.web_url)`n`n$Readme" | Out-File "$OutputDir\readmes\$SafeName.md" -Encoding UTF8
}
Write-Host " OK" -ForegroundColor Green
}
# JSON
$Enriched | ConvertTo-Json -Depth 10 | Out-File "$OutputDir\projects-full.json" -Encoding UTF8
Write-Host "`n JSON gespeichert" -ForegroundColor DarkGray
# CSV
$CsvRows = $Enriched | ForEach-Object {
$LS = ""
if ($_.languages) {
$LP = $_.languages | Get-Member -MemberType NoteProperty
$LS = ($LP | ForEach-Object { $_.Name }) -join ", "
}
[PSCustomObject]@{
Name=$_.name; Pfad=$_.path; Beschreibung=$_.description;
Sprachen=$LS; Tags=($_.tags -join ", "); LetzteAktivitaet=$_.last_active;
Archiviert=$_.archived; Branch=$_.branch; Namespace=$_.namespace
}
}
$CsvRows | Export-Csv "$OutputDir\projects-overview.csv" -Delimiter ";" -Encoding UTF8 -NoTypeInformation
Write-Host " CSV gespeichert" -ForegroundColor DarkGray
# Markdown
$Md = @("# GitLab Projektuebersicht - $(Safe-String $GroupInfo.name)", "",
"> Generiert: $(Get-Date -Format 'yyyy-MM-dd HH:mm')",
"> Projekte: $($Enriched.Count)", "")
if ($Subgroups.Count -gt 0) {
$Md += "## Untergruppen"; $Md += ""
foreach ($sg in $Subgroups) { $Md += "- **$($sg.name)** (``$($sg.full_path)``)" }
$Md += ""
}
$ByNs = $Enriched | Group-Object -Property namespace
foreach ($G in ($ByNs | Sort-Object Name)) {
$Md += "## $($G.Name)"; $Md += ""
$Md += "| Projekt | Beschreibung | Sprachen | Tags | Aktiv |"
$Md += "|---------|-------------|----------|------|-------|"
foreach ($p in $G.Group) {
$LS = ""; if ($p.languages) { $LP = $p.languages | Get-Member -MemberType NoteProperty; $LS = ($LP | ForEach-Object { $_.Name }) -join ", " }
$TS = ($p.tags -join ", ")
$DS = if ($p.description.Length -gt 60) { $p.description.Substring(0,60) + "..." } else { $p.description }
if (-not $DS) { $DS = "-" }
$LA = if ($p.last_active) { $p.last_active.Substring(0,10) } else { "-" }
$Md += "| $($p.name) | $DS | $LS | $TS | $LA |"
}
$Md += ""
}
$Md -join "`n" | Out-File "$OutputDir\projects-overview.md" -Encoding UTF8
Write-Host " Markdown gespeichert" -ForegroundColor DarkGray
# Statistiken
$St = @("# Projektstatistiken", "", "- Projekte: $($Enriched.Count)", "- Untergruppen: $($Subgroups.Count)",
"- Archiviert: $(($Enriched | Where-Object { $_.archived }).Count)", "", "## Sprachen")
$LC = @{}; foreach ($p in $Enriched) { if ($p.languages) { $p.languages | Get-Member -MemberType NoteProperty | ForEach-Object { $LC[$_.Name] = ($LC[$_.Name]) + 1 } } }
foreach ($e in ($LC.GetEnumerator() | Sort-Object Value -Descending)) { $St += "- $($e.Key): $($e.Value)" }
$St += ""; $St += "## Tags"
$TC = @{}; foreach ($p in $Enriched) { foreach ($t in $p.tags) { $TC[$t] = ($TC[$t]) + 1 } }
foreach ($e in ($TC.GetEnumerator() | Sort-Object Value -Descending)) { $St += "- $($e.Key): $($e.Value)" }
$St -join "`n" | Out-File "$OutputDir\statistics.md" -Encoding UTF8
Write-Host " Statistiken gespeichert" -ForegroundColor DarkGray
Write-Host "`n============================================================" -ForegroundColor Green
Write-Host " FERTIG! $($Enriched.Count) Projekte inventarisiert." -ForegroundColor Green
Write-Host " Ergebnisse: $OutputDir" -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green
} catch {
Write-Host "`nFEHLER: $($_.Exception.Message)" -ForegroundColor Red
if ($_.Exception.Message -match "401") { Write-Host " Token pruefen (read_api Scope)." -ForegroundColor Yellow }
if ($_.Exception.Message -match "404") { Write-Host " Gruppe '$GroupPath' nicht gefunden." -ForegroundColor Yellow }
if ($_.Exception.Message -match "resolve|Remotename") { Write-Host " VPN aktiv?" -ForegroundColor Yellow }
exit 1
}