From 5405a4dadedb49585a8a930667222afc519a458b Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 28 Sep 2026 18:03:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20deploy.ps1=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=85=88=E6=9E=84=E5=BB=BA=E5=90=8E=E6=9B=BF=E6=8D=A2=EF=BC=8C?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E9=83=A8=E7=BD=B2=E7=A9=BA=E7=AA=97=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 旧流程先删 dist/TempLate 资源再构建,窗口期内请求 404 或命中根目录站点 - 新流程:构建 -> 同步 assets -> 最后替换 index.html -> 清理镜像目录陈旧文件 - 清理仅限 assets/ 与 tinymce/,保留 pages/ 等生产独有目录 --- deploy.ps1 | 107 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 66 insertions(+), 41 deletions(-) diff --git a/deploy.ps1 b/deploy.ps1 index 9d222ff..ef6dadb 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -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 })