fix(deploy): deploy.ps1 改为先构建后替换,消除部署空窗期

- 旧流程先删 dist/TempLate 资源再构建,窗口期内请求 404 或命中根目录站点

- 新流程:构建 -> 同步 assets -> 最后替换 index.html -> 清理镜像目录陈旧文件

- 清理仅限 assets/ 与 tinymce/,保留 pages/ 等生产独有目录
This commit is contained in:
TermiNexus committed 2026-09-28 18:03:21 +08:00
1 parent cdfd6f827d
commit 5405a4dade
1 file changed
+66 -41
+66 -41
View File
@@ -1,14 +1,20 @@
# GERP frontend deploy script: build and sync dist to production dir (TempLate/GERP)
# GERP frontend deploy script: zero-downtime build and sync to production dir (TempLate/GERP)
#
# IMPORTANT: keep this file pure ASCII. PS 5.1 parses BOM-less UTF-8 files
# using the ANSI codepage, so non-ASCII text would be garbled.
#
# Usage:
# 1. Clean old artifacts (dist and TempLate/GERP/assets) so no stale hashes remain
# 2. Full rebuild (vue-tsc type check + vite build)
# 3. Sync dist to TempLate/GERP (index.html + assets)
# 4. Verify TempLate matches dist exactly
# Order matters (zero downtime):
# 1. Build first (dist/) -- prod keeps serving the old build meanwhile
# 2. Copy new assets/dirs (additive) -- old index.html still points to old
# assets, which are still present, so no request can 404
# 3. Swap index.html last -- every asset it references already exists
# 4. Prune stale files in the mirror dirs (assets / tinymce)
# 5. Verify prod matches dist
#
# Never delete index.html / assets before the new build is ready: SubdomainView
# (gvsds/apps/core/views.py) serves gerp.gvsds.com straight out of this directory.
#
# Usage:
# Run: .\deploy.ps1
# (if execution policy blocks: Set-ExecutionPolicy -Scope Process Bypass)
@@ -23,32 +29,10 @@ $IndexHtml = Join-Path $ProdRoot 'index.html'
Write-Host "`n===== GERP DEPLOY START =====" -ForegroundColor Cyan
Write-Host "Project dir : $ProjectRoot"
Write-Host "Prod dir : $ProdRoot"
Write-Host ""
# 1. Clean old artifacts
Write-Host '[1/4] Cleaning old build artifacts...' -ForegroundColor Yellow
if (Test-Path $DistDir) {
Remove-Item -Recurse -Force $DistDir
Write-Host " removed: $DistDir"
}
if (Test-Path $ProdAssetsDir) {
Remove-Item -Recurse -Force $ProdAssetsDir
Write-Host " removed: $ProdAssetsDir"
}
if (Test-Path $IndexHtml) {
Remove-Item -Force $IndexHtml
Write-Host " removed: $IndexHtml"
}
# tinymce 为构建产物(裁剪后的运行时文件),清理防旧完整包残留
$ProdTinymceDir = Join-Path $ProdRoot 'tinymce'
if (Test-Path $ProdTinymceDir) {
Remove-Item -Recurse -Force $ProdTinymceDir
Write-Host " removed: $ProdTinymceDir"
}
Write-Host ''
# 2. Full build (vue-tsc type check + vite build)
Write-Host '[2/4] Running npm run build (type check + full build)...' -ForegroundColor Yellow
# 1. Full build (vue-tsc type check + vite build). Non-destructive for prod.
Write-Host '[1/5] Running npm run build (type check + full build)...' -ForegroundColor Yellow
Push-Location $ProjectRoot
try {
npm run build
@@ -61,26 +45,67 @@ try {
Write-Host ' Build OK'
Write-Host ''
# 3. Sync artifacts to prod dir
Write-Host '[3/4] Syncing artifacts to prod dir...' -ForegroundColor Yellow
if (-not (Test-Path $ProdRoot)) {
New-Item -ItemType Directory -Path $ProdRoot -Force | Out-Null
}
Copy-Item -Recurse -Force (Join-Path $DistDir '*') $ProdRoot
Copy-Item -Force (Join-Path $DistDir 'index.html') $IndexHtml
Write-Host ' Sync done'
# 2. Copy new assets/dirs first (additive; stale files are pruned in step 4 so the
# still-active old index.html never dangles)
Write-Host '[2/5] Syncing new assets to prod dir...' -ForegroundColor Yellow
Get-ChildItem -Path $DistDir -Force | Where-Object { $_.Name -ne 'index.html' } | ForEach-Object {
Copy-Item -Recurse -Force $_.FullName $ProdRoot
Write-Host " synced: $($_.Name)"
}
Write-Host ''
# 4. Verify prod dir matches dist (file name sets)
Write-Host '[4/4] Verifying artifact consistency...' -ForegroundColor Yellow
# 3. Swap index.html last: at this point every asset it references already exists
Write-Host '[3/5] Swapping index.html...' -ForegroundColor Yellow
Copy-Item -Force (Join-Path $DistDir 'index.html') $IndexHtml
Write-Host ' index.html updated'
Write-Host ''
# 4. Prune stale files in the mirror dirs (assets / tinymce) that the new build no
# longer ships. Only mirror dirs are touched -- prod-only dirs like pages/ are kept.
Write-Host '[4/5] Pruning stale files...' -ForegroundColor Yellow
function Remove-StaleFiles([string]$distDir, [string]$prodDir) {
if (-not (Test-Path $prodDir)) { return }
if (-not (Test-Path $distDir)) {
# Whole dir is gone from the build: remove it from prod
Remove-Item -Recurse -Force $prodDir
Write-Host " removed dir: $($prodDir.Substring($ProdRoot.Length))"
return
}
$distNames = @(Get-ChildItem -Path $distDir -Recurse -File | ForEach-Object { $_.FullName.Substring($distDir.Length) })
$stale = @(Get-ChildItem -Path $prodDir -Recurse -File | Where-Object {
$_.FullName.Substring($prodDir.Length) -notin $distNames
})
foreach ($f in $stale) {
Remove-Item -Force $f.FullName
Write-Host " removed: $($f.FullName.Substring($ProdRoot.Length))"
}
# Drop now-empty sub dirs (deepest first)
Get-ChildItem -Path $prodDir -Recurse -Directory |
Sort-Object { $_.FullName.Length } -Descending |
Where-Object { -not (Get-ChildItem -Path $_.FullName -Force) } |
ForEach-Object {
Remove-Item -Force $_.FullName
Write-Host " removed empty dir: $($_.FullName.Substring($ProdRoot.Length))"
}
}
Remove-StaleFiles (Join-Path $DistDir 'assets') $ProdAssetsDir
Remove-StaleFiles (Join-Path $DistDir 'tinymce') (Join-Path $ProdRoot 'tinymce')
Write-Host ''
# 5. Verify prod dir matches dist (file name sets)
Write-Host '[5/5] Verifying artifact consistency...' -ForegroundColor Yellow
function Get-AssetNames([string]$dir) {
if (-not (Test-Path $dir)) { return @() }
return @(Get-ChildItem -Path $dir -Recurse -File | ForEach-Object { $_.FullName.Substring($dir.Length) })
}
$distNames = Get-AssetNames (Join-Path $DistDir 'assets')
$prodNames = Get-AssetNames $ProdAssetsDir
$distSet = @($distNames | Sort-Object -Unique)
$prodSet = @($prodNames | Sort-Object -Unique)
$distNames = Get-AssetNames (Join-Path $DistDir 'assets')
$prodNames = Get-AssetNames $ProdAssetsDir
$distSet = @($distNames | Sort-Object -Unique)
$prodSet = @($prodNames | Sort-Object -Unique)
$onlyDist = @($distSet | Where-Object { $_ -notin $prodSet })
$onlyProd = @($prodSet | Where-Object { $_ -notin $distSet })