189 lines
5.8 KiB
PowerShell
189 lines
5.8 KiB
PowerShell
param(
|
|
[string]$ArtifactRoot = "",
|
|
[switch]$SkipFrontendBuild,
|
|
[switch]$SkipFrontendLint,
|
|
[switch]$SkipBackendTests,
|
|
[switch]$SkipDiffCheck
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
|
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
|
|
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
|
|
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\deployment-preflight"
|
|
}
|
|
if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
|
|
$ArtifactRoot = Join-Path $repoRoot $ArtifactRoot
|
|
}
|
|
|
|
$artifactDir = Join-Path $ArtifactRoot $timestamp
|
|
$summaryJson = Join-Path $artifactDir "deployment-preflight-summary.json"
|
|
$summaryMd = Join-Path $artifactDir "deployment-preflight-summary.md"
|
|
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
|
|
|
$checks = New-Object System.Collections.Generic.List[object]
|
|
|
|
function Add-Check {
|
|
param(
|
|
[string]$Key,
|
|
[string]$Label,
|
|
[ValidateSet("pass", "fail", "skip")]
|
|
[string]$Status,
|
|
[string]$Detail,
|
|
[bool]$Required = $true,
|
|
[hashtable]$Meta = @{}
|
|
)
|
|
|
|
$checks.Add([ordered]@{
|
|
key = $Key
|
|
label = $Label
|
|
status = $Status
|
|
required = $Required
|
|
detail = $Detail
|
|
meta = $Meta
|
|
}) | Out-Null
|
|
}
|
|
|
|
function Save-Json {
|
|
param(
|
|
[string]$Path,
|
|
[object]$Value
|
|
)
|
|
$Value | ConvertTo-Json -Depth 12 | Set-Content -Encoding UTF8 -Path $Path
|
|
}
|
|
|
|
function Invoke-PreflightStep {
|
|
param(
|
|
[string]$Key,
|
|
[string]$Label,
|
|
[string]$Command,
|
|
[string[]]$Arguments,
|
|
[string]$WorkingDirectory
|
|
)
|
|
|
|
$logPath = Join-Path $artifactDir "$Key.log"
|
|
$previousErrorActionPreference = $ErrorActionPreference
|
|
$ErrorActionPreference = "Continue"
|
|
try {
|
|
Push-Location -LiteralPath $WorkingDirectory
|
|
try {
|
|
$output = & $Command @Arguments 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
} finally {
|
|
$ErrorActionPreference = $previousErrorActionPreference
|
|
}
|
|
|
|
$outputText = ($output -join [Environment]::NewLine)
|
|
$outputText | Set-Content -Encoding UTF8 -Path $logPath
|
|
|
|
if ($exitCode -eq 0) {
|
|
Add-Check -Key $Key -Label $Label -Status "pass" -Detail "$Label passed" -Meta @{ log = $logPath }
|
|
} else {
|
|
Add-Check -Key $Key -Label $Label -Status "fail" -Detail "$Label failed with exit code $exitCode" -Meta @{
|
|
exit_code = $exitCode
|
|
log = $logPath
|
|
working_directory = $WorkingDirectory
|
|
}
|
|
}
|
|
}
|
|
|
|
$branch = (git -C $repoRoot rev-parse --abbrev-ref HEAD).Trim()
|
|
$commit = (git -C $repoRoot rev-parse HEAD).Trim()
|
|
|
|
if ($SkipBackendTests) {
|
|
Add-Check -Key "backend_tests" -Label "backend deployment tests" -Status "skip" -Required $false -Detail "Skipped by -SkipBackendTests"
|
|
} else {
|
|
Invoke-PreflightStep `
|
|
-Key "backend_tests" `
|
|
-Label "backend deployment tests" `
|
|
-Command "python" `
|
|
-Arguments @("-m", "pytest", "backend/tests/test_system_api.py", "backend/tests/test_contracts_api.py", "backend/tests/test_search_service.py", "backend/tests/test_search_api.py") `
|
|
-WorkingDirectory $repoRoot
|
|
}
|
|
|
|
if ($SkipFrontendLint) {
|
|
Add-Check -Key "frontend_lint" -Label "frontend lint" -Status "skip" -Required $false -Detail "Skipped by -SkipFrontendLint"
|
|
} else {
|
|
Invoke-PreflightStep `
|
|
-Key "frontend_lint" `
|
|
-Label "frontend lint" `
|
|
-Command "npm" `
|
|
-Arguments @("--prefix", "frontend", "run", "lint") `
|
|
-WorkingDirectory $repoRoot
|
|
}
|
|
|
|
if ($SkipFrontendBuild) {
|
|
Add-Check -Key "frontend_build" -Label "frontend production build" -Status "skip" -Required $false -Detail "Skipped by -SkipFrontendBuild"
|
|
} else {
|
|
Invoke-PreflightStep `
|
|
-Key "frontend_build" `
|
|
-Label "frontend production build" `
|
|
-Command "npm" `
|
|
-Arguments @("--prefix", "frontend", "run", "build") `
|
|
-WorkingDirectory $repoRoot
|
|
}
|
|
|
|
if ($SkipDiffCheck) {
|
|
Add-Check -Key "diff_check" -Label "git diff whitespace check" -Status "skip" -Required $false -Detail "Skipped by -SkipDiffCheck"
|
|
} else {
|
|
Invoke-PreflightStep `
|
|
-Key "diff_check" `
|
|
-Label "git diff whitespace check" `
|
|
-Command "git" `
|
|
-Arguments @("-C", $repoRoot, "diff", "--check") `
|
|
-WorkingDirectory $repoRoot
|
|
}
|
|
|
|
$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" })
|
|
$skippedList = @($checks | Where-Object { $_.status -eq "skip" })
|
|
$overallStatus = if ($failedRequired.Count -gt 0) { "FAIL" } else { "PASS" }
|
|
|
|
$summary = [ordered]@{
|
|
name = "deployment-preflight"
|
|
status = $overallStatus
|
|
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
|
artifact_dir = $artifactDir
|
|
branch = $branch
|
|
commit = $commit
|
|
checks = $checks
|
|
totals = [ordered]@{
|
|
total = $checks.Count
|
|
failed_required = $failedRequired.Count
|
|
skipped = $skippedList.Count
|
|
}
|
|
}
|
|
Save-Json -Path $summaryJson -Value $summary
|
|
|
|
$markdown = New-Object System.Collections.Generic.List[string]
|
|
$markdown.Add("# Deployment Preflight") | Out-Null
|
|
$markdown.Add("") | Out-Null
|
|
$markdown.Add("- Status: $overallStatus") | Out-Null
|
|
$markdown.Add("- Generated at: $($summary.generated_at)") | Out-Null
|
|
$markdown.Add("- Branch: $branch") | Out-Null
|
|
$markdown.Add("- Commit: $commit") | Out-Null
|
|
$markdown.Add("- Artifact dir: $artifactDir") | Out-Null
|
|
$markdown.Add("") | Out-Null
|
|
$markdown.Add("| Check | Required | Status | Detail |") | Out-Null
|
|
$markdown.Add("|---|---:|---|---|") | Out-Null
|
|
foreach ($check in $checks) {
|
|
$markdown.Add("| $($check.label) | $($check.required) | $($check.status.ToUpperInvariant()) | $($check.detail) |") | Out-Null
|
|
}
|
|
$markdown.Add("") | Out-Null
|
|
$markdown.Add("Logs:") | Out-Null
|
|
$markdown.Add("") | Out-Null
|
|
foreach ($check in $checks) {
|
|
if ($check.meta.log) {
|
|
$markdown.Add(('- {0}: `{1}`' -f $check.label, $check.meta.log)) | Out-Null
|
|
}
|
|
}
|
|
$markdown | Set-Content -Encoding UTF8 -Path $summaryMd
|
|
|
|
Write-Host "DEPLOYMENT_PREFLIGHT_$overallStatus $artifactDir"
|
|
if ($overallStatus -eq "FAIL") {
|
|
throw "Deployment preflight failed. See $summaryMd"
|
|
}
|