|
|
|
@@ -203,6 +203,31 @@ function Get-LatestDevBackup {
|
|
|
|
|
return $latest.FullName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Assert-ProdDbTarget {
|
|
|
|
|
$prodEnvPath = Join-Path $RepoRoot 'infra\docker\.env.prod'
|
|
|
|
|
if (-not (Test-Path $prodEnvPath)) {
|
|
|
|
|
throw "Missing production env file: $prodEnvPath"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$prodDbName = $null
|
|
|
|
|
foreach ($line in Get-Content -LiteralPath $prodEnvPath) {
|
|
|
|
|
if ($line -match '^\s*DB_NAME\s*=\s*(.+?)\s*$') {
|
|
|
|
|
$prodDbName = $Matches[1].Trim().Trim('"', "'")
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not $prodDbName) {
|
|
|
|
|
throw "DB_NAME is not set in $prodEnvPath"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($prodDbName -ne 'gallery_prod') {
|
|
|
|
|
throw "Prod DB target is '$prodDbName' in .env.prod. Set DB_NAME=gallery_prod before dev -> prod migration."
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "Prod DB target confirmed: $prodDbName" -ForegroundColor DarkGray
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Get-DevApiPort {
|
|
|
|
|
$envFile = Join-Path $RepoRoot '.env'
|
|
|
|
|
$port = '3451'
|
|
|
|
@@ -217,6 +242,72 @@ function Get-DevApiPort {
|
|
|
|
|
return $port
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Test-TcpPortListening {
|
|
|
|
|
param([int]$Port)
|
|
|
|
|
|
|
|
|
|
$matches = netstat -ano | Select-String ":$Port\s"
|
|
|
|
|
foreach ($line in $matches) {
|
|
|
|
|
if ($line -match 'LISTENING') {
|
|
|
|
|
return $true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Start-DevNpmProcess {
|
|
|
|
|
param([string]$ScriptName)
|
|
|
|
|
|
|
|
|
|
$logPath = Join-Path $RepoRoot "infra\deploy\dev-autostart-$ScriptName.log"
|
|
|
|
|
Write-Host "Starting background process: npm run $ScriptName (log: $logPath)" -ForegroundColor Yellow
|
|
|
|
|
Start-Process -FilePath 'cmd.exe' -ArgumentList @(
|
|
|
|
|
'/c',
|
|
|
|
|
"npm run $ScriptName > `"$logPath`" 2>&1"
|
|
|
|
|
) -WorkingDirectory $RepoRoot -WindowStyle Hidden | Out-Null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Ensure-DevApiReady {
|
|
|
|
|
param(
|
|
|
|
|
[int]$ApiPort,
|
|
|
|
|
[bool]$AutoStart = $true,
|
|
|
|
|
[int]$WaitSeconds = 90
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
$apiUrl = "http://127.0.0.1:$ApiPort/api/bounds"
|
|
|
|
|
$detail = ''
|
|
|
|
|
if (Test-BoundsJson -Url $apiUrl -MaxTimeSeconds 5 -Retries 1 -Detail ([ref]$detail)) {
|
|
|
|
|
Write-Host "Dev API already running: $apiUrl"
|
|
|
|
|
return $true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (-not $AutoStart) {
|
|
|
|
|
return $false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$viteUp = Test-TcpPortListening -Port 5173
|
|
|
|
|
$apiUp = Test-TcpPortListening -Port $ApiPort
|
|
|
|
|
|
|
|
|
|
if ($viteUp -and -not $apiUp) {
|
|
|
|
|
Write-Host "Vite is running on :5173 but API is down on :$ApiPort." -ForegroundColor Yellow
|
|
|
|
|
Start-DevNpmProcess -ScriptName 'dev:server'
|
|
|
|
|
} elseif (-not $viteUp -and -not $apiUp) {
|
|
|
|
|
Write-Host 'Dev stack is not running.' -ForegroundColor Yellow
|
|
|
|
|
Start-DevNpmProcess -ScriptName 'dev:web'
|
|
|
|
|
} else {
|
|
|
|
|
Write-Host "API port :$ApiPort is listening but /api/bounds is not ready yet. Waiting..." -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ($elapsed = 3; $elapsed -le $WaitSeconds; $elapsed += 3) {
|
|
|
|
|
Start-Sleep -Seconds 3
|
|
|
|
|
if (Test-BoundsJson -Url $apiUrl -MaxTimeSeconds 5 -Retries 1 -Detail ([ref]$detail)) {
|
|
|
|
|
Write-Host "Dev API ready: $apiUrl"
|
|
|
|
|
return $true
|
|
|
|
|
}
|
|
|
|
|
Write-Host "Waiting for dev API... (${elapsed}s, $detail)" -ForegroundColor DarkGray
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Test-BoundsJson {
|
|
|
|
|
param(
|
|
|
|
|
[string]$Url,
|
|
|
|
@@ -312,6 +403,14 @@ if (Get-StepEnabled $steps 'validateBuild') {
|
|
|
|
|
|
|
|
|
|
# Dev-only endpoints. Do not use verify.lanUrl here (that is prod on TrueNAS).
|
|
|
|
|
$devApiPort = Get-DevApiPort
|
|
|
|
|
$autoStartDev = if ($null -ne $cfg.autoStartDevStack) { [bool]$cfg.autoStartDevStack } else { $true }
|
|
|
|
|
if (-not (Ensure-DevApiReady -ApiPort $devApiPort -AutoStart:$autoStartDev)) {
|
|
|
|
|
Write-Host "Dev API check failed on all URLs." -ForegroundColor Red
|
|
|
|
|
Write-Host "Start the full dev stack before release: npm run dev:web" -ForegroundColor Yellow
|
|
|
|
|
Write-Host "Vite on :5173 alone is not enough - the API must listen on :$devApiPort (PORT in .env)." -ForegroundColor Yellow
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$devCandidates = @(
|
|
|
|
|
"http://127.0.0.1:$devApiPort/api/bounds"
|
|
|
|
|
'http://localhost:5173/api/bounds'
|
|
|
|
@@ -395,6 +494,7 @@ if (Get-StepEnabled $steps 'backupDev') {
|
|
|
|
|
|
|
|
|
|
if (Get-StepEnabled $steps 'backupProd') {
|
|
|
|
|
$ok = Invoke-Step 'backupProd' {
|
|
|
|
|
Assert-ProdDbTarget
|
|
|
|
|
$code = Invoke-Npm 'prod:db:backup'
|
|
|
|
|
return $code
|
|
|
|
|
}
|
|
|
|
@@ -403,6 +503,7 @@ if (Get-StepEnabled $steps 'backupProd') {
|
|
|
|
|
|
|
|
|
|
if (Get-StepEnabled $steps 'migrateProdSchema') {
|
|
|
|
|
$ok = Invoke-Step 'migrateProdSchema' {
|
|
|
|
|
Assert-ProdDbTarget
|
|
|
|
|
$env:DB_NAME = 'gallery_prod'
|
|
|
|
|
try {
|
|
|
|
|
$code = Invoke-Npm 'dev:migrate'
|
|
|
|
@@ -416,6 +517,7 @@ if (Get-StepEnabled $steps 'migrateProdSchema') {
|
|
|
|
|
|
|
|
|
|
if (Get-StepEnabled $steps 'restoreProd') {
|
|
|
|
|
$ok = Invoke-Step 'restoreProd' {
|
|
|
|
|
Assert-ProdDbTarget
|
|
|
|
|
if (-not $script:backupFile) {
|
|
|
|
|
if ($cfg.backupFile -and $cfg.backupFile.Trim()) {
|
|
|
|
|
$script:backupFile = (Resolve-Path (Join-Path $RepoRoot $cfg.backupFile)).Path
|
|
|
|
@@ -446,7 +548,9 @@ if (Get-StepEnabled $steps 'syncImages') {
|
|
|
|
|
|
|
|
|
|
if (Get-StepEnabled $steps 'dockerPublish') {
|
|
|
|
|
$ok = Invoke-Step 'dockerPublish' {
|
|
|
|
|
$code = Invoke-Npm 'prod:docker:publish'
|
|
|
|
|
$tag = if ($cfg.release.imageTag) { $cfg.release.imageTag } else { 'latest' }
|
|
|
|
|
Write-Host "Docker release tag: $tag"
|
|
|
|
|
$code = Invoke-Npm 'prod:docker:publish' @('-Tag', $tag)
|
|
|
|
|
return $code
|
|
|
|
|
}
|
|
|
|
|
if (-not $ok) { Write-ReleaseBanner -Success $false -FailedAt $FailedStep; exit 1 }
|
|
|
|
|