276 lines
7.6 KiB
PowerShell
276 lines
7.6 KiB
PowerShell
param(
|
|
[string]$CommitMessage,
|
|
[switch]$SkipCommit,
|
|
[switch]$SkipDiscord
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = Resolve-Path "$PSScriptRoot\.."
|
|
Set-Location $repoRoot
|
|
|
|
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
|
$script:CurrentStep = "initializing"
|
|
|
|
function Write-TextFile {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[Parameter(Mandatory = $true)][string]$Content
|
|
)
|
|
|
|
$fullPath = Join-Path $repoRoot $Path
|
|
$normalized = $Content -replace "`r`n", "`n"
|
|
[System.IO.File]::WriteAllText($fullPath, $normalized, $Utf8NoBom)
|
|
}
|
|
|
|
function Read-TextFile {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
|
|
$fullPath = Join-Path $repoRoot $Path
|
|
return [System.IO.File]::ReadAllText($fullPath)
|
|
}
|
|
|
|
function Get-EnvFileValue {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[Parameter(Mandatory = $true)][string]$Name
|
|
)
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
return $null
|
|
}
|
|
|
|
$match = Select-String -Path $Path -Pattern "^$([regex]::Escape($Name))=(.*)$" | Select-Object -First 1
|
|
if (-not $match) {
|
|
return $null
|
|
}
|
|
|
|
return $match.Matches[0].Groups[1].Value.Trim()
|
|
}
|
|
|
|
function Get-DiscordWebhookUrl {
|
|
$candidateNames = @(
|
|
"PROCESS_DISCORD_WEBHOOK_URL",
|
|
"MAGENT_NOTIFY_DISCORD_WEBHOOK_URL",
|
|
"DISCORD_WEBHOOK_URL"
|
|
)
|
|
|
|
foreach ($name in $candidateNames) {
|
|
$value = [System.Environment]::GetEnvironmentVariable($name)
|
|
if (-not [string]::IsNullOrWhiteSpace($value)) {
|
|
return $value.Trim()
|
|
}
|
|
}
|
|
|
|
foreach ($name in $candidateNames) {
|
|
$value = Get-EnvFileValue -Path ".env" -Name $name
|
|
if (-not [string]::IsNullOrWhiteSpace($value)) {
|
|
return $value.Trim()
|
|
}
|
|
}
|
|
|
|
$configPath = Join-Path $repoRoot "backend/app/config.py"
|
|
if (Test-Path $configPath) {
|
|
$configContent = Read-TextFile -Path "backend/app/config.py"
|
|
$match = [regex]::Match(
|
|
$configContent,
|
|
'discord_webhook_url:\s*Optional\[str\]\s*=\s*Field\(\s*default="([^"]+)"',
|
|
[System.Text.RegularExpressions.RegexOptions]::Singleline
|
|
)
|
|
if ($match.Success) {
|
|
return $match.Groups[1].Value.Trim()
|
|
}
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Send-DiscordUpdate {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Title,
|
|
[Parameter(Mandatory = $true)][string]$Body
|
|
)
|
|
|
|
if ($SkipDiscord) {
|
|
Write-Host "Skipping Discord notification."
|
|
return
|
|
}
|
|
|
|
$webhookUrl = Get-DiscordWebhookUrl
|
|
if ([string]::IsNullOrWhiteSpace($webhookUrl)) {
|
|
Write-Warning "Discord webhook not configured for Process 1."
|
|
return
|
|
}
|
|
|
|
$content = "**$Title**`n$Body"
|
|
Invoke-RestMethod -Method Post -Uri $webhookUrl -ContentType "application/json" -Body (@{ content = $content } | ConvertTo-Json -Compress) | Out-Null
|
|
}
|
|
|
|
function Get-BuildNumber {
|
|
$current = ""
|
|
if (Test-Path ".build_number") {
|
|
$current = (Get-Content ".build_number" -Raw).Trim()
|
|
}
|
|
|
|
$candidate = Get-Date
|
|
$buildNumber = $candidate.ToString("ddMMyyHHmm")
|
|
if ($buildNumber -eq $current) {
|
|
$buildNumber = $candidate.AddMinutes(1).ToString("ddMMyyHHmm")
|
|
}
|
|
|
|
return $buildNumber
|
|
}
|
|
|
|
function Wait-ForHttp {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Url,
|
|
[int]$Attempts = 30,
|
|
[int]$DelaySeconds = 2
|
|
)
|
|
|
|
$lastError = $null
|
|
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
|
|
try {
|
|
return Invoke-RestMethod -Uri $Url -TimeoutSec 10
|
|
} catch {
|
|
$lastError = $_
|
|
Start-Sleep -Seconds $DelaySeconds
|
|
}
|
|
}
|
|
|
|
throw $lastError
|
|
}
|
|
|
|
function Update-BuildFiles {
|
|
param([Parameter(Mandatory = $true)][string]$BuildNumber)
|
|
|
|
Write-TextFile -Path ".build_number" -Content "$BuildNumber`n"
|
|
|
|
$buildInfo = Read-TextFile -Path "backend/app/build_info.py"
|
|
$updatedBuildInfo = [regex]::Replace(
|
|
$buildInfo,
|
|
'^BUILD_NUMBER = "\d+"$',
|
|
"BUILD_NUMBER = `"$BuildNumber`"",
|
|
[System.Text.RegularExpressions.RegexOptions]::Multiline
|
|
)
|
|
Write-TextFile -Path "backend/app/build_info.py" -Content $updatedBuildInfo
|
|
|
|
$envPath = Join-Path $repoRoot ".env"
|
|
if (Test-Path $envPath) {
|
|
$envContent = Read-TextFile -Path ".env"
|
|
if ($envContent -match '^BUILD_NUMBER=.*$') {
|
|
$updatedEnv = [regex]::Replace(
|
|
$envContent,
|
|
'^BUILD_NUMBER=.*$',
|
|
"BUILD_NUMBER=$BuildNumber",
|
|
[System.Text.RegularExpressions.RegexOptions]::Multiline
|
|
)
|
|
} else {
|
|
$updatedEnv = "BUILD_NUMBER=$BuildNumber`n$envContent"
|
|
}
|
|
Write-TextFile -Path ".env" -Content $updatedEnv
|
|
}
|
|
|
|
Push-Location frontend
|
|
try {
|
|
npm version $BuildNumber --no-git-tag-version --allow-same-version | Out-Null
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
function Get-ChangedFilesSummary {
|
|
$files = git diff --cached --name-only
|
|
if (-not $files) {
|
|
return "No staged files"
|
|
}
|
|
|
|
$count = ($files | Measure-Object).Count
|
|
$sample = $files | Select-Object -First 8
|
|
$summary = ($sample -join ", ")
|
|
if ($count -gt $sample.Count) {
|
|
$summary = "$summary, +$($count - $sample.Count) more"
|
|
}
|
|
|
|
return "$count files: $summary"
|
|
}
|
|
|
|
$buildNumber = $null
|
|
$branch = $null
|
|
$commit = $null
|
|
$publicInfo = $null
|
|
$changedFiles = "No staged files"
|
|
|
|
try {
|
|
$branch = (git rev-parse --abbrev-ref HEAD).Trim()
|
|
$buildNumber = Get-BuildNumber
|
|
Write-Host "Process 1 build number: $buildNumber"
|
|
|
|
$script:CurrentStep = "updating build metadata"
|
|
Update-BuildFiles -BuildNumber $buildNumber
|
|
|
|
$script:CurrentStep = "rebuilding local docker stack"
|
|
docker compose up -d --build
|
|
|
|
$script:CurrentStep = "verifying backend health"
|
|
$health = Wait-ForHttp -Url "http://127.0.0.1:8000/health"
|
|
if ($health.status -ne "ok") {
|
|
throw "Health endpoint returned unexpected payload: $($health | ConvertTo-Json -Compress)"
|
|
}
|
|
|
|
$script:CurrentStep = "verifying public build metadata"
|
|
$publicInfo = Wait-ForHttp -Url "http://127.0.0.1:8000/site/public"
|
|
if ($publicInfo.buildNumber -ne $buildNumber) {
|
|
throw "Public build number mismatch. Expected $buildNumber but got $($publicInfo.buildNumber)."
|
|
}
|
|
|
|
$script:CurrentStep = "committing changes"
|
|
git add -A
|
|
$changedFiles = Get-ChangedFilesSummary
|
|
if ((git status --short).Trim()) {
|
|
if (-not $SkipCommit) {
|
|
if ([string]::IsNullOrWhiteSpace($CommitMessage)) {
|
|
$CommitMessage = "Process 1 build $buildNumber"
|
|
}
|
|
git commit -m $CommitMessage
|
|
}
|
|
}
|
|
|
|
$commit = (git rev-parse --short HEAD).Trim()
|
|
|
|
$body = @(
|
|
"Build: $buildNumber"
|
|
"Branch: $branch"
|
|
"Commit: $commit"
|
|
"Health: ok"
|
|
"Public build: $($publicInfo.buildNumber)"
|
|
"Changes: $changedFiles"
|
|
) -join "`n"
|
|
Send-DiscordUpdate -Title "Process 1 complete" -Body $body
|
|
|
|
Write-Host "Process 1 completed successfully."
|
|
} catch {
|
|
$failureCommit = ""
|
|
try {
|
|
$failureCommit = (git rev-parse --short HEAD).Trim()
|
|
} catch {
|
|
$failureCommit = "unknown"
|
|
}
|
|
|
|
$failureBody = @(
|
|
"Build: $buildNumber"
|
|
"Branch: $branch"
|
|
"Commit: $failureCommit"
|
|
"Step: $script:CurrentStep"
|
|
"Error: $($_.Exception.Message)"
|
|
) -join "`n"
|
|
|
|
try {
|
|
Send-DiscordUpdate -Title "Process 1 failed" -Body $failureBody
|
|
} catch {
|
|
Write-Warning "Failed to send Discord failure notification: $($_.Exception.Message)"
|
|
}
|
|
|
|
throw
|
|
}
|