v4.0.0.2
This commit is contained in:
+330
-49
@@ -1,4 +1,4 @@
|
||||
[CmdletBinding()]
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Force,
|
||||
[switch]$SkipSiteCheck,
|
||||
@@ -8,9 +8,94 @@ param(
|
||||
# postgres database, overwriting anything currently in admin_kv. After the
|
||||
# deploy completes the flag is automatically reset to 'auto' for subsequent
|
||||
# boots so we don't keep overwriting live data.
|
||||
[switch]$SeedAdminData
|
||||
[switch]$SeedAdminData,
|
||||
# Skip regenerating the legacy-clients-seed.json file. By default every
|
||||
# deploy refreshes it from data/legacy-{clients,onboarding,contracts}.json.
|
||||
# The mail-api merges this on boot, add-only — it never clobbers live data.
|
||||
[switch]$SkipLegacySeed
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# UI helpers — colored output, step headers, timing
|
||||
# ---------------------------------------------------------------------------
|
||||
$script:UseColor = $Host.UI.SupportsVirtualTerminal -or [bool]$env:WT_SESSION -or [bool]$env:TERM_PROGRAM
|
||||
$script:ESC = [char]27
|
||||
|
||||
# Goodwalk brand palette + status colors (24-bit ANSI where supported)
|
||||
$script:CGreen = '38;2;122;170;122' # softened Goodwalk green for legibility on dark terminals
|
||||
$script:CYellow = '38;2;255;209;0' # #FFD100
|
||||
$script:CCyan = '96'
|
||||
$script:CDim = '90'
|
||||
$script:CGrey = '37'
|
||||
$script:COk = '92'
|
||||
$script:CWarn = '93'
|
||||
$script:CErr = '91'
|
||||
|
||||
function Format-Color {
|
||||
param([string]$Text, [string]$Code)
|
||||
if (-not $script:UseColor) { return $Text }
|
||||
return "$script:ESC[${Code}m$Text$script:ESC[0m"
|
||||
}
|
||||
|
||||
function Show-DeployBanner {
|
||||
$border = '──────────────────────────────────────────────────────' # 54 chars
|
||||
$pad22 = ' ' * 22
|
||||
Write-Host ''
|
||||
Write-Host (Format-Color " ╭$border╮" $script:CGreen)
|
||||
Write-Host -NoNewline (Format-Color ' │' $script:CGreen)
|
||||
Write-Host -NoNewline (Format-Color ' GoodWalk' $script:CYellow)
|
||||
Write-Host -NoNewline (Format-Color ' · production deploy' $script:CDim)
|
||||
Write-Host (Format-Color ($pad22 + '│') $script:CGreen)
|
||||
Write-Host (Format-Color " ╰$border╯" $script:CGreen)
|
||||
Write-Host ''
|
||||
}
|
||||
|
||||
function Write-Info {
|
||||
param([string]$Text)
|
||||
Write-Host (Format-Color " $Text" $script:CDim)
|
||||
}
|
||||
|
||||
function Write-Note {
|
||||
param([string]$Text)
|
||||
Write-Host (" " + (Format-Color '·' $script:CYellow) + " " + (Format-Color $Text $script:CGrey))
|
||||
}
|
||||
|
||||
function Write-Field {
|
||||
param([string]$Label, [string]$Value)
|
||||
$padded = $Label.PadRight(22)
|
||||
Write-Host -NoNewline (Format-Color " $padded" $script:CDim)
|
||||
Write-Host (Format-Color $Value $script:CGrey)
|
||||
}
|
||||
|
||||
function Write-Section {
|
||||
param([string]$Text)
|
||||
Write-Host ''
|
||||
Write-Host (Format-Color "── $Text " $script:CCyan)
|
||||
}
|
||||
|
||||
function Write-StepHeader {
|
||||
param([string]$Text)
|
||||
Write-Host ''
|
||||
Write-Host -NoNewline (Format-Color '▶ ' $script:CCyan)
|
||||
Write-Host (Format-Color $Text $script:CGrey)
|
||||
}
|
||||
|
||||
function Write-Ok {
|
||||
param([string]$Text, [double]$Sec = -1)
|
||||
$suffix = if ($Sec -ge 0) { Format-Color (' ({0:N1}s)' -f $Sec) $script:CDim } else { '' }
|
||||
Write-Host (" " + (Format-Color '✓' $script:COk) + " " + (Format-Color $Text $script:CGrey) + $suffix)
|
||||
}
|
||||
|
||||
function Write-Fail {
|
||||
param([string]$Text)
|
||||
Write-Host (" " + (Format-Color '✗' $script:CErr) + " " + (Format-Color $Text $script:CErr))
|
||||
}
|
||||
|
||||
function Write-WarnLine {
|
||||
param([string]$Text)
|
||||
Write-Host (" " + (Format-Color '!' $script:CWarn) + " " + (Format-Color $Text $script:CWarn))
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Goodwalk production deployment settings
|
||||
# Update these values before the first real deployment.
|
||||
@@ -154,6 +239,32 @@ function Export-HomepageContent {
|
||||
}
|
||||
}
|
||||
|
||||
function Build-LegacySeed {
|
||||
param(
|
||||
[string]$ProjectPath
|
||||
)
|
||||
|
||||
$scriptPath = Join-Path $ProjectPath 'scripts\build-legacy-seed.mjs'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
||||
throw "Legacy seed builder not found: $scriptPath"
|
||||
}
|
||||
|
||||
Push-Location $ProjectPath
|
||||
try {
|
||||
Invoke-External -FilePath 'node' -Arguments @($scriptPath)
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
$outputPath = Join-Path $ProjectPath 'mail-api\legacy-clients-seed.json'
|
||||
if (-not (Test-Path -LiteralPath $outputPath)) {
|
||||
throw "Legacy seed builder did not produce expected output: $outputPath"
|
||||
}
|
||||
return $outputPath
|
||||
}
|
||||
|
||||
function New-UnixScriptCopy {
|
||||
param(
|
||||
[string]$SourcePath
|
||||
@@ -175,13 +286,13 @@ function New-UnixScriptCopy {
|
||||
function Invoke-SiteCheck {
|
||||
param([string]$Url)
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "[deploy] Checking production site: $Url"
|
||||
Write-StepHeader 'Production site check'
|
||||
Write-Info $Url
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $Url -MaximumRedirection 5 -TimeoutSec 30
|
||||
if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) {
|
||||
Write-Host "[deploy] Site responded with HTTP $($response.StatusCode)"
|
||||
Write-Ok "HTTP $($response.StatusCode) from production"
|
||||
return
|
||||
}
|
||||
throw "Unexpected HTTP $($response.StatusCode) from $Url"
|
||||
@@ -189,13 +300,136 @@ function Invoke-SiteCheck {
|
||||
catch {
|
||||
$message = "Post-deploy site check failed: $($_.Exception.Message). Verify URL: $Url"
|
||||
if ($SkipSiteCheck) {
|
||||
Write-Warning $message
|
||||
Write-WarnLine $message
|
||||
} else {
|
||||
Write-Fail $message
|
||||
throw $message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RemoteSmokeSecret {
|
||||
param(
|
||||
[string]$SshTarget,
|
||||
[string[]]$SshArgList,
|
||||
[string]$DeployPath
|
||||
)
|
||||
|
||||
# Pulls DEPLOY_SMOKE_SECRET from the live .env that was just merged by the
|
||||
# deploy. Single source of truth — no need to sync template and live.
|
||||
$remoteCmd = "grep -E '^DEPLOY_SMOKE_SECRET=' '$DeployPath/.env' | tail -n1 | cut -d= -f2-"
|
||||
$output = & ssh @SshArgList $SshTarget $remoteCmd 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { return $null }
|
||||
if ([string]::IsNullOrWhiteSpace($output)) { return $null }
|
||||
return ($output -join '').Trim()
|
||||
}
|
||||
|
||||
function Invoke-FormSmokeTest {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$Secret,
|
||||
[hashtable]$Payload,
|
||||
[string]$Label
|
||||
)
|
||||
|
||||
$json = $Payload | ConvertTo-Json -Compress -Depth 6
|
||||
$headers = @{
|
||||
'X-Deploy-Smoke' = $Secret
|
||||
'Content-Type' = 'application/json'
|
||||
'User-Agent' = 'goodwalk-deploy-smoke/1.0'
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $Url -Method Post -Headers $headers -Body $json -TimeoutSec 30 -MaximumRedirection 0
|
||||
}
|
||||
catch {
|
||||
throw "$Label smoke test failed: $($_.Exception.Message)"
|
||||
}
|
||||
|
||||
if ($response.StatusCode -ne 200) {
|
||||
throw "$Label smoke test returned HTTP $($response.StatusCode) (expected 200)"
|
||||
}
|
||||
|
||||
$body = $null
|
||||
try { $body = $response.Content | ConvertFrom-Json } catch { }
|
||||
if (-not $body -or -not $body.ok -or -not $body.smoke) {
|
||||
throw "$Label smoke test response did not include ok=true,smoke=true. Body: $($response.Content)"
|
||||
}
|
||||
|
||||
Write-Ok "$Label OK (request_id=$($body.request_id))"
|
||||
}
|
||||
|
||||
function Invoke-AllFormSmokeTests {
|
||||
param(
|
||||
[string]$SshTarget,
|
||||
[string[]]$SshArgList,
|
||||
[string]$DeployPath
|
||||
)
|
||||
|
||||
Write-StepHeader 'Production form smoke tests'
|
||||
|
||||
$secret = Get-RemoteSmokeSecret -SshTarget $SshTarget -SshArgList $SshArgList -DeployPath $DeployPath
|
||||
if ([string]::IsNullOrWhiteSpace($secret)) {
|
||||
$msg = "DEPLOY_SMOKE_SECRET not found in $DeployPath/.env on the server. Smoke tests cannot run."
|
||||
if ($SkipSiteCheck) {
|
||||
Write-WarnLine $msg
|
||||
return
|
||||
}
|
||||
Write-Fail $msg
|
||||
throw $msg
|
||||
}
|
||||
|
||||
# Payloads carry only the fields each Pydantic model requires. Validation,
|
||||
# rate limiting, honeypot, email send and DB writes are all bypassed by the
|
||||
# mail-api smoke shortcut, but Pydantic still parses the body — so this also
|
||||
# catches schema breakage.
|
||||
$smokeEmail = 'deploy-smoke@goodwalk.co.nz'
|
||||
$bookingPayload = @{
|
||||
fullName = 'Deploy Smoke'
|
||||
email = $smokeEmail
|
||||
phone = '+64-00-000-0000'
|
||||
enquiryType = 'booking'
|
||||
page = '/deploy-smoke'
|
||||
}
|
||||
$onboardingPayload = @{
|
||||
fullName = 'Deploy Smoke'
|
||||
email = $smokeEmail
|
||||
phone = '+64-00-000-0000'
|
||||
address = '1 Smoke St, Auckland'
|
||||
dogName = 'Smoke'
|
||||
dogBreed = 'Test'
|
||||
vetName = 'Dr Smoke'
|
||||
vetPhone = '+64-00-000-0000'
|
||||
emergencyContactName = 'Smoke Contact'
|
||||
emergencyContactPhone = '+64-00-000-0000'
|
||||
signatureDataUrl = 'data:image/png;base64,iVBORw0KGgo='
|
||||
page = '/deploy-smoke'
|
||||
}
|
||||
$contractPayload = @{
|
||||
fullName = 'Deploy Smoke'
|
||||
email = $smokeEmail
|
||||
phone = '+64-00-000-0000'
|
||||
address = '1 Smoke St, Auckland'
|
||||
dogName = 'Smoke'
|
||||
dogBreed = 'Test'
|
||||
serviceType = 'pack-walks'
|
||||
startDate = '2099-01-01'
|
||||
signatureDataUrl = 'data:image/png;base64,iVBORw0KGgo='
|
||||
page = '/deploy-smoke'
|
||||
}
|
||||
|
||||
# Each endpoint is exercised on the subdomain that actually hosts it
|
||||
# in production (see nginx/goodwalk.co.nz.svelte.conf.example):
|
||||
# /api/submit → www.goodwalk.co.nz
|
||||
# /api/onboarding-submit → clients.goodwalk.co.nz
|
||||
# /api/contract-submit → clients.goodwalk.co.nz
|
||||
Invoke-FormSmokeTest -Url 'https://www.goodwalk.co.nz/api/submit' -Secret $secret -Payload $bookingPayload -Label 'booking form (www /api/submit)'
|
||||
Invoke-FormSmokeTest -Url 'https://clients.goodwalk.co.nz/api/onboarding-submit' -Secret $secret -Payload $onboardingPayload -Label 'onboarding form (clients /api/onboarding-submit)'
|
||||
Invoke-FormSmokeTest -Url 'https://clients.goodwalk.co.nz/api/contract-submit' -Secret $secret -Payload $contractPayload -Label 'contract form (clients /api/contract-submit)'
|
||||
}
|
||||
|
||||
Show-DeployBanner
|
||||
|
||||
Assert-Command ssh
|
||||
Assert-Command scp
|
||||
Assert-Command tar
|
||||
@@ -243,75 +477,106 @@ $scpArchiveTarget = '{0}:{1}' -f $sshTarget, $RemoteArchivePath
|
||||
$scpHelperTarget = '{0}:{1}' -f $sshTarget, $RemoteHelperPath
|
||||
$sshArgs = Get-SshArgumentList
|
||||
|
||||
Write-Host '[deploy] Main Goodwalk website deployment'
|
||||
Write-Host "[deploy] Local project path: $LocalProjectPath"
|
||||
Write-Host "[deploy] Remote deployment path: $RemoteDeploymentPath"
|
||||
Write-Host "[deploy] Remote compose file: $ComposeFileName"
|
||||
Write-Host "[deploy] Docker project name: $DockerProjectName"
|
||||
Write-Host "[deploy] Shared nginx config: $NginxConfigTarget"
|
||||
Write-Host "[deploy] Shared nginx compose file: $NginxComposeFile"
|
||||
Write-Host "[deploy] Maintenance host dir: $MaintenanceHostDir (must be bind-mounted at /var/www/maintenance:ro)"
|
||||
Write-Host "[deploy] Maintenance flag path: $MaintenanceFlagPath"
|
||||
Write-Host "[deploy] SSH target: $sshTarget"
|
||||
Write-Host "[deploy] SSH config: $SshConfigPath"
|
||||
Write-Section 'Configuration'
|
||||
Write-Field 'Local project' $LocalProjectPath
|
||||
Write-Field 'Remote path' $RemoteDeploymentPath
|
||||
Write-Field 'Compose file' $ComposeFileName
|
||||
Write-Field 'Docker project' $DockerProjectName
|
||||
Write-Field 'Shared nginx conf' $NginxConfigTarget
|
||||
Write-Field 'Shared nginx compose' $NginxComposeFile
|
||||
Write-Field 'Maintenance dir' "$MaintenanceHostDir (mounted at /var/www/maintenance:ro)"
|
||||
Write-Field 'Maintenance flag' $MaintenanceFlagPath
|
||||
Write-Field 'SSH target' $sshTarget
|
||||
Write-Field 'SSH config' $SshConfigPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($Service)) {
|
||||
Write-Host "[deploy] Target service: $Service"
|
||||
Write-Field 'Target service' $Service
|
||||
}
|
||||
if ($SeedAdminData) {
|
||||
Write-Host '[deploy] Admin data: seeding postgres from JSON on next mail-api boot'
|
||||
Write-Field 'Admin data' 'seed postgres from JSON on next mail-api boot'
|
||||
}
|
||||
if ($SkipLegacySeed) {
|
||||
Write-Field 'Legacy clients seed' 'SKIPPED (existing mail-api/legacy-clients-seed.json reused)'
|
||||
} else {
|
||||
Write-Field 'Legacy clients seed' 'rebuild from data/legacy-*.json (add-only merge on boot)'
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($SshKeyPath)) {
|
||||
Write-Host '[deploy] SSH auth: interactive password prompt'
|
||||
Write-Field 'SSH auth' 'interactive password prompt'
|
||||
} else {
|
||||
Write-Host "[deploy] SSH auth: key file $SshKeyPath"
|
||||
Write-Field 'SSH auth' "key file $SshKeyPath"
|
||||
}
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Safety notes:'
|
||||
Write-Host ' - Only the top-level Goodwalk compose project will be updated.'
|
||||
Write-Host ' - Legacy WordPress/onboarding compose files are not used.'
|
||||
Write-Host ' - Remote .env files are preserved because they are not uploaded.'
|
||||
Write-Host ' - No global Docker prune/stop/delete commands are used.'
|
||||
Write-Host ' - Shared nginx will be updated and reloaded with the Docker-DNS-based config.'
|
||||
Write-Host ' - Subdomains served by this stack:'
|
||||
Write-Host ' goodwalk.co.nz / www.goodwalk.co.nz (marketing)'
|
||||
Write-Host ' clients.goodwalk.co.nz (client onboarding + contracts)'
|
||||
Write-Host ' cp.goodwalk.co.nz (owner admin dashboard)'
|
||||
Write-Host ' onboarding/admin remain legacy redirect aliases'
|
||||
|
||||
Write-Section 'Safety notes'
|
||||
Write-Note 'Only the top-level Goodwalk compose project will be updated.'
|
||||
Write-Note 'Legacy WordPress/onboarding compose files are not used.'
|
||||
Write-Note 'Remote .env files are preserved because they are not uploaded.'
|
||||
Write-Note 'No global Docker prune/stop/delete commands are used.'
|
||||
Write-Note 'Shared nginx will be updated and reloaded with the Docker-DNS-based config.'
|
||||
|
||||
Write-Section 'Subdomains served by this stack'
|
||||
Write-Note 'goodwalk.co.nz / www.goodwalk.co.nz — marketing'
|
||||
Write-Note 'clients.goodwalk.co.nz — client onboarding + contracts'
|
||||
Write-Note 'cp.goodwalk.co.nz — owner admin dashboard'
|
||||
Write-Note 'onboarding/admin remain legacy redirect aliases'
|
||||
if ($SeedAdminData) {
|
||||
Write-Host ' - Admin data seed: mail-api will OVERWRITE postgres admin_kv from the JSON volume.'
|
||||
Write-Host ''
|
||||
Write-WarnLine 'Admin data seed: mail-api will OVERWRITE postgres admin_kv from the JSON volume.'
|
||||
}
|
||||
|
||||
if (-not $Force) {
|
||||
$confirmation = Read-Host "Type DEPLOY to continue with the remote path '$RemoteDeploymentPath'"
|
||||
Write-Host ''
|
||||
Write-Host -NoNewline (" " + (Format-Color '?' $script:CYellow) + " ")
|
||||
Write-Host -NoNewline (Format-Color 'Type ' $script:CGrey)
|
||||
Write-Host -NoNewline (Format-Color 'DEPLOY' $script:CYellow)
|
||||
Write-Host -NoNewline (Format-Color " to continue with remote path '" $script:CGrey)
|
||||
Write-Host -NoNewline (Format-Color $RemoteDeploymentPath $script:CCyan)
|
||||
Write-Host -NoNewline (Format-Color "' " $script:CGrey)
|
||||
$confirmation = Read-Host
|
||||
if ($confirmation -ne 'DEPLOY') {
|
||||
Write-Fail 'Deployment cancelled.'
|
||||
throw 'Deployment cancelled.'
|
||||
}
|
||||
}
|
||||
|
||||
$archivePath = $null
|
||||
$uploadHelperPath = $null
|
||||
$totalWatch = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
$stepWatch = [System.Diagnostics.Stopwatch]::new()
|
||||
|
||||
try {
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Exporting current homepage content for PostgreSQL sync'
|
||||
Write-StepHeader 'Export homepage content for PostgreSQL sync'
|
||||
$stepWatch.Restart()
|
||||
Export-HomepageContent -ProjectPath $LocalProjectPath -OutputPath $GeneratedHomepageContentPath
|
||||
Write-Ok 'Homepage content exported' $stepWatch.Elapsed.TotalSeconds
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Creating deployment archive'
|
||||
if (-not $SkipLegacySeed) {
|
||||
Write-StepHeader 'Build legacy clients seed for mail-api image'
|
||||
$stepWatch.Restart()
|
||||
$legacySeedPath = Build-LegacySeed -ProjectPath $LocalProjectPath
|
||||
Write-Info "Seed file: $legacySeedPath"
|
||||
Write-Ok 'Legacy clients seed ready' $stepWatch.Elapsed.TotalSeconds
|
||||
} else {
|
||||
Write-Note 'Legacy clients seed step skipped (-SkipLegacySeed).'
|
||||
}
|
||||
|
||||
Write-StepHeader 'Create deployment archive'
|
||||
$stepWatch.Restart()
|
||||
$archivePath = New-DeployArchive -ProjectPath $LocalProjectPath
|
||||
Write-Host "[deploy] Archive ready: $archivePath"
|
||||
Write-Info "Archive: $archivePath"
|
||||
Write-Ok 'Archive ready' $stepWatch.Elapsed.TotalSeconds
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Uploading remote helper'
|
||||
Write-StepHeader 'Upload remote helper'
|
||||
$stepWatch.Restart()
|
||||
$uploadHelperPath = New-UnixScriptCopy -SourcePath $LocalRemoteHelperPath
|
||||
Invoke-External -FilePath 'scp' -Arguments ($sshArgs + @($uploadHelperPath, $scpHelperTarget))
|
||||
Write-Ok 'Helper uploaded' $stepWatch.Elapsed.TotalSeconds
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Uploading application archive'
|
||||
Write-StepHeader 'Upload application archive'
|
||||
$stepWatch.Restart()
|
||||
Invoke-External -FilePath 'scp' -Arguments ($sshArgs + @($archivePath, $scpArchiveTarget))
|
||||
Write-Ok 'Archive uploaded' $stepWatch.Elapsed.TotalSeconds
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Running remote deployment'
|
||||
Write-StepHeader 'Run remote deployment'
|
||||
$stepWatch.Restart()
|
||||
Invoke-External -FilePath 'ssh' -Arguments ($sshArgs + @(
|
||||
$sshTarget,
|
||||
'bash',
|
||||
@@ -338,9 +603,10 @@ try {
|
||||
$MaintenanceFlagPath
|
||||
) + $(if (-not [string]::IsNullOrWhiteSpace($Service)) { @('--service', $Service) } else { @() }) `
|
||||
+ $(if ($SeedAdminData) { @('--seed-admin-data') } else { @() }))
|
||||
Write-Ok 'Remote deployment finished' $stepWatch.Elapsed.TotalSeconds
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Cleaning remote temporary files'
|
||||
Write-StepHeader 'Clean remote temporary files'
|
||||
$stepWatch.Restart()
|
||||
Invoke-External -FilePath 'ssh' -Arguments ($sshArgs + @(
|
||||
$sshTarget,
|
||||
'rm',
|
||||
@@ -348,13 +614,28 @@ try {
|
||||
$RemoteArchivePath,
|
||||
$RemoteHelperPath
|
||||
))
|
||||
Write-Ok 'Remote temp files cleaned' $stepWatch.Elapsed.TotalSeconds
|
||||
|
||||
if (-not $SkipSiteCheck) {
|
||||
Invoke-SiteCheck -Url $VerifyUrl
|
||||
Invoke-AllFormSmokeTests -SshTarget $sshTarget -SshArgList $sshArgs -DeployPath $RemoteDeploymentPath
|
||||
}
|
||||
|
||||
$totalWatch.Stop()
|
||||
$border = '──────────────────────────────────────────────────────'
|
||||
Write-Host ''
|
||||
Write-Host (Format-Color " ╭$border╮" $script:CGreen)
|
||||
Write-Host -NoNewline (Format-Color ' │' $script:CGreen)
|
||||
Write-Host -NoNewline (Format-Color ' ✓ ' $script:COk)
|
||||
Write-Host -NoNewline (Format-Color 'Deployment complete' $script:CYellow)
|
||||
$elapsedText = (' · {0:N1}s total' -f $totalWatch.Elapsed.TotalSeconds)
|
||||
Write-Host -NoNewline (Format-Color $elapsedText $script:CDim)
|
||||
$visibleLen = (' ✓ Deployment complete' + $elapsedText).Length
|
||||
$padLen = 54 - $visibleLen
|
||||
if ($padLen -lt 1) { $padLen = 1 }
|
||||
Write-Host (Format-Color ((' ' * $padLen) + '│') $script:CGreen)
|
||||
Write-Host (Format-Color " ╰$border╯" $script:CGreen)
|
||||
Write-Host ''
|
||||
Write-Host '[deploy] Deployment completed successfully'
|
||||
}
|
||||
finally {
|
||||
if ($archivePath -and (Test-Path -LiteralPath $archivePath)) {
|
||||
|
||||
Reference in New Issue
Block a user