- 旧流程先删 dist/TempLate 资源再构建,窗口期内请求 404 或命中根目录站点 - 新流程:构建 -> 同步 assets -> 最后替换 index.html -> 清理镜像目录陈旧文件 - 清理仅限 assets/ 与 tinymce/,保留 pages/ 等生产独有目录
139 lines
5.8 KiB
PowerShell
139 lines
5.8 KiB
PowerShell
# 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.
|
|
#
|
|
# 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)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$ProjectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$DistDir = Join-Path $ProjectRoot 'dist'
|
|
$ProdRoot = Join-Path (Split-Path -Parent $ProjectRoot) 'TempLate\GERP'
|
|
$ProdAssetsDir = Join-Path $ProdRoot 'assets'
|
|
$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. 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
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Build failed, exit code = $LASTEXITCODE"
|
|
}
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
Write-Host ' Build OK'
|
|
Write-Host ''
|
|
|
|
if (-not (Test-Path $ProdRoot)) {
|
|
New-Item -ItemType Directory -Path $ProdRoot -Force | Out-Null
|
|
}
|
|
|
|
# 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 ''
|
|
|
|
# 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)
|
|
|
|
$onlyDist = @($distSet | Where-Object { $_ -notin $prodSet })
|
|
$onlyProd = @($prodSet | Where-Object { $_ -notin $distSet })
|
|
|
|
if ($onlyDist.Count -eq 0 -and $onlyProd.Count -eq 0) {
|
|
Write-Host " OK: $($distSet.Count) assets, prod matches dist exactly" -ForegroundColor Green
|
|
} else {
|
|
Write-Host ' MISMATCH:' -ForegroundColor Red
|
|
if ($onlyDist.Count) { Write-Host " only in dist: $onlyDist" -ForegroundColor Red }
|
|
if ($onlyProd.Count) { Write-Host " only in prod: $onlyProd" -ForegroundColor Red }
|
|
throw 'Artifact verification failed!'
|
|
}
|
|
|
|
# Verify the entry JS referenced by index.html exists
|
|
$indexContent = Get-Content $IndexHtml -Raw
|
|
$entryMatch = [regex]::Match($indexContent, 'src="([^"]+\.js)"')
|
|
if ($entryMatch.Success) {
|
|
$entryPath = Join-Path $ProdRoot ($entryMatch.Groups[1].Value.TrimStart('/'))
|
|
if (Test-Path $entryPath) {
|
|
Write-Host " entry JS OK: $($entryMatch.Groups[1].Value)" -ForegroundColor Green
|
|
} else {
|
|
throw "index.html entry JS not found: $entryPath"
|
|
}
|
|
} else {
|
|
throw 'No entry JS reference found in index.html'
|
|
}
|
|
|
|
Write-Host ''
|
|
Write-Host "===== GERP DEPLOY DONE =====" -ForegroundColor Cyan
|
|
Write-Host 'Please hard-refresh the browser (Ctrl+F5) to load the new build.' -ForegroundColor Cyan
|