#!/usr/bin/env pwsh # derive-gitlab.ps1 — Windows 对应版(PowerShell 5.1+ 原生可跑,无外部依赖) # 与同目录 derive-gitlab.sh 严格等价 — 改动任一份必须同步对方。 # # 用法: powershell -NoProfile -ExecutionPolicy Bypass -File derive-gitlab.ps1 [.env.local 路径] # # 派生字段、退出码、回填策略 见 derive-gitlab.sh 头部注释。 [CmdletBinding()] param([string]$EnvFile = '.env.local') # 让 Chinese 输出在 cmd / PowerShell 控制台正常显示 try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} if (-not (Test-Path -LiteralPath $EnvFile -PathType Leaf)) { [Console]::Error.WriteLine("derive-gitlab.ps1: env file not found: $EnvFile") exit 2 } # ---- 取 origin URL ---- $url = '' try { $raw = & git remote get-url origin 2>$null if ($LASTEXITCODE -eq 0 -and $raw) { $url = ($raw -join '').Trim() } } catch { $url = '' } if ([string]::IsNullOrEmpty($url)) { [Console]::Error.WriteLine("derive-gitlab.ps1: 未配置 origin 远程,GITLAB_* 留给用户手填") exit 0 } # ---- 仅支持 http(s) ---- $scheme = $null if ($url.StartsWith('https://')) { $scheme = 'https' } elseif ($url.StartsWith('http://')) { $scheme = 'http' } else { [Console]::Error.WriteLine("derive-gitlab.ps1: origin 不是 http(s) URL ($url),跳过派生") exit 0 } $rest = $url.Substring("${scheme}://".Length) $slashIdx = $rest.IndexOf('/') if ($slashIdx -lt 0) { [Console]::Error.WriteLine("derive-gitlab.ps1: origin URL 缺少路径段 ($url),跳过派生") exit 0 } $gitlabHost = $rest.Substring(0, $slashIdx) $pathRaw = $rest.Substring($slashIdx + 1) if ($pathRaw.EndsWith('.git')) { $pathRaw = $pathRaw.Substring(0, $pathRaw.Length - 4) } $repoName = Split-Path -Leaf $pathRaw $apiUrl = "${scheme}://${gitlabHost}/api/v3" # ---- 读 .env.local 全文,按原行尾切分 ---- $rawBytes = [System.IO.File]::ReadAllBytes($EnvFile) $hasBom = ($rawBytes.Length -ge 3 -and $rawBytes[0] -eq 0xEF -and $rawBytes[1] -eq 0xBB -and $rawBytes[2] -eq 0xBF) $rawText = if ($hasBom) { [System.Text.Encoding]::UTF8.GetString($rawBytes, 3, $rawBytes.Length - 3) } else { [System.Text.Encoding]::UTF8.GetString($rawBytes) } $eol = if ($rawText -match "`r`n") { "`r`n" } else { "`n" } $hadTrailingNewline = $rawText.EndsWith("`n") $lines = [System.Collections.Generic.List[string]]::new() foreach ($l in ($rawText -split "`r?`n")) { $lines.Add($l) | Out-Null } if ($hadTrailingNewline -and $lines.Count -gt 0 -and $lines[$lines.Count - 1] -eq '') { $lines.RemoveAt($lines.Count - 1) } function Get-FieldStripped { param([string]$Key) for ($i = 0; $i -lt $script:lines.Count; $i++) { $ln = $script:lines[$i] if ($ln.StartsWith("$Key=")) { $val = $ln.Substring($Key.Length + 1) # 剥外层单/双引号 if ($val.Length -ge 2) { $f = $val[0]; $l = $val[$val.Length - 1] if (($f -eq "'" -and $l -eq "'") -or ($f -eq '"' -and $l -eq '"')) { $val = $val.Substring(1, $val.Length - 2) } } return @{ Found = $true; Index = $i; Stripped = $val } } } return @{ Found = $false } } function Update-Field { param([string]$Key, [string]$NewVal) $info = Get-FieldStripped -Key $Key if (-not $info.Found) { [Console]::Error.WriteLine(" $Key = (.env.local 中无此行,跳过)") return } $stripped = $info.Stripped if ([string]::IsNullOrEmpty($stripped) -or $stripped -eq 'TBD(A5 自动补)') { $script:lines[$info.Index] = "$Key=$NewVal" $script:modified = $true Write-Host " $Key = $NewVal [已派生填入]" } elseif ($stripped -eq $NewVal) { Write-Host " $Key = $NewVal [已是派生值,无需更新]" } else { Write-Host " $Key = $stripped [保留用户手填,未覆盖派生值 $NewVal]" } } function Report-Token { $info = Get-FieldStripped -Key 'GITLAB_TOKEN' if (-not $info.Found) { [Console]::Error.WriteLine(" GITLAB_TOKEN = (.env.local 中无此行,跳过)") return } $stripped = $info.Stripped if ([string]::IsNullOrEmpty($stripped) -or $stripped.StartsWith('【人工填写:') -or $stripped.StartsWith('TBD')) { Write-Host " GITLAB_TOKEN = $stripped [待人工填写:GitLab Profile → Account → Private token]" } else { $masked = if ($stripped.Length -le 8) { ('*' * $stripped.Length) } else { $stripped.Substring(0, 4) + ('*' * ($stripped.Length - 8)) + $stripped.Substring($stripped.Length - 4) } Write-Host " GITLAB_TOKEN = $masked [已填入,长度 $($stripped.Length)]" } } $script:modified = $false Write-Host "derive-gitlab.ps1: 从 origin ($url) 派生 GitLab 凭据:" Update-Field -Key 'GITLAB_API_URL' -NewVal $apiUrl # ---- GITLAB_PROJECT_ID:经 GitLab API 解析数字 ID ---- $tokenInfo = Get-FieldStripped -Key 'GITLAB_TOKEN' $tokenVal = if ($tokenInfo.Found) { $tokenInfo.Stripped } else { '' } $tokenUsable = $true if ([string]::IsNullOrEmpty($tokenVal) -or $tokenVal.StartsWith('【人工填写:') -or $tokenVal.StartsWith('TBD')) { Write-Host " GITLAB_PROJECT_ID = TBD [token 未填,跳过 API 解析;填完 token 后重跑此脚本]" $tokenUsable = $false } if ($tokenUsable) { # PS 5.1 默认 TLS1.0/1.1,自建 GitLab 通常需要 TLS1.2 try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 } catch {} $headers = @{ 'PRIVATE-TOKEN' = $tokenVal } $userOk = $false try { $userResp = Invoke-WebRequest -Uri "$apiUrl/user" -Headers $headers -UseBasicParsing -ErrorAction Stop if ([int]$userResp.StatusCode -eq 200) { $userOk = $true } else { Write-Host " GITLAB_PROJECT_ID = TBD [token 验证失败 HTTP $([int]$userResp.StatusCode),留 TBD 待人工确认]" } } catch { $code = 0 if ($_.Exception.Response) { try { $code = [int]$_.Exception.Response.StatusCode } catch {} } if ($code -eq 0) { $code = '000' } Write-Host " GITLAB_PROJECT_ID = TBD [token 验证失败 HTTP $code,留 TBD 待人工确认]" } if ($userOk) { try { $searchUri = "$apiUrl/projects?search=$([uri]::EscapeDataString($repoName))&simple=true&per_page=50" $projects = Invoke-RestMethod -Uri $searchUri -Headers $headers -UseBasicParsing -ErrorAction Stop $match = $projects | Where-Object { $_.path_with_namespace -eq $pathRaw } | Select-Object -First 1 if ($match) { Update-Field -Key 'GITLAB_PROJECT_ID' -NewVal "$($match.id)" } else { Write-Host " GITLAB_PROJECT_ID = TBD [API 未匹配 path_with_namespace=$pathRaw,请到 GitLab 项目设置页查数字 ID 后填入]" } } catch { Write-Host " GITLAB_PROJECT_ID = TBD [API 调用失败:$($_.Exception.Message)]" } } } Report-Token # ---- 回写 .env.local(保持 EOL / BOM 状态)---- if ($script:modified) { $newText = [string]::Join($eol, $lines.ToArray()) if ($hadTrailingNewline) { $newText += $eol } $encoding = New-Object System.Text.UTF8Encoding($hasBom) [System.IO.File]::WriteAllText($EnvFile, $newText, $encoding) }