Files
ai-contract-platform/scripts/run-deployment-preflight.ps1
T

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"
}