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.
269 lines
12 KiB
PowerShell
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
|
|
}
|