YD-WEB/scripts/deploy-oss.ps1
2026-07-27 16:44:06 +08:00

138 lines
5.1 KiB
PowerShell

[CmdletBinding()]
param(
[string]$Bucket = 'yuedong-wechatapplet',
[string]$Prefix = 'static/wwwweb',
[string]$PublicOrigin = 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com',
[string]$ConfigFile = (Join-Path $HOME '.ossutilconfig'),
[string]$OssutilPath = '',
[switch]$SkipRemoteVerify
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$ProjectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$DistDir = Join-Path $ProjectRoot 'dist'
$DefaultOssutil = Join-Path $ProjectRoot '.tools\ossutil-2.3.0-windows-amd64\ossutil.exe'
if (-not $OssutilPath) {
$OssutilPath = $DefaultOssutil
}
if (-not (Test-Path -LiteralPath $OssutilPath -PathType Leaf)) {
throw "ossutil not found: $OssutilPath"
}
if (-not (Test-Path -LiteralPath $ConfigFile -PathType Leaf)) {
throw "OSS config not found: $ConfigFile"
}
if (-not (Get-Command 'npm.cmd' -ErrorAction SilentlyContinue)) {
throw 'npm.cmd was not found. Install Node.js and reopen PowerShell.'
}
$Timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$CleanOrigin = $PublicOrigin.TrimEnd('/')
$CleanPrefix = $Prefix.Trim('/')
$PublicBase = "$CleanOrigin/$CleanPrefix/$Timestamp/"
$OssTarget = "oss://$Bucket/$CleanPrefix/$Timestamp/"
Push-Location $ProjectRoot
try {
Write-Host "[1/5] Building production files" -ForegroundColor Cyan
& npm.cmd run build -- "--base=$PublicBase"
if ($LASTEXITCODE -ne 0) {
throw "Vite build failed with exit code $LASTEXITCODE"
}
Write-Host "[2/5] Rewriting and validating OSS asset paths" -ForegroundColor Cyan
& npm.cmd run rewrite:oss -- $PublicBase
if ($LASTEXITCODE -ne 0) {
throw "OSS path validation failed with exit code $LASTEXITCODE"
}
$IndexPath = Join-Path $DistDir 'index.html'
$IndexContent = [System.IO.File]::ReadAllText($IndexPath)
$DuplicatePrefix = $PublicBase.TrimEnd('/') + $PublicBase
if ($IndexContent.Contains($DuplicatePrefix)) {
throw 'The generated index.html contains a duplicated OSS prefix.'
}
if (-not $IndexContent.Contains($PublicBase)) {
throw 'The generated index.html does not reference the new OSS directory.'
}
$Files = Get-ChildItem -LiteralPath $DistDir -Recurse -File
$UploadFiles = @($Files | Where-Object { $_.Name -ne 'index.html' })
$UploadBytes = ($UploadFiles | Measure-Object Length -Sum).Sum
Write-Host "[3/5] Uploading $($UploadFiles.Count) static files" -ForegroundColor Cyan
Write-Host " Target: $OssTarget"
& $OssutilPath -c $ConfigFile cp "$DistDir\" $OssTarget -r --exclude 'index.html' -f -q --no-progress
if ($LASTEXITCODE -ne 0) {
throw "OSS upload failed with exit code $LASTEXITCODE"
}
Write-Host '[4/5] Confirming index.html was excluded' -ForegroundColor Cyan
$RemoteIndexUrl = "${PublicBase}index.html?verify=$Timestamp"
try {
$RemoteIndexResponse = Invoke-WebRequest -Uri $RemoteIndexUrl -Method Head -UseBasicParsing
if ($RemoteIndexResponse.StatusCode -eq 200) {
throw 'index.html exists in the new OSS directory, but it must remain on the web server.'
}
} catch {
if (-not $_.Exception.Response) {
throw
}
$RemoteIndexStatus = [int]$_.Exception.Response.StatusCode
if ($RemoteIndexStatus -ne 404) {
throw "Unable to confirm that index.html was excluded. HTTP status: $RemoteIndexStatus"
}
}
if (-not $SkipRemoteVerify) {
Write-Host '[5/5] Verifying uploaded JS, CSS, and image URLs' -ForegroundColor Cyan
$ScriptMatch = [regex]::Match($IndexContent, '<script[^>]+src="([^"]+)"')
$StyleMatch = [regex]::Match($IndexContent, '<link[^>]+rel="stylesheet"[^>]+href="([^"]+)"')
if (-not $ScriptMatch.Success -or -not $StyleMatch.Success) {
throw 'Could not locate generated JS or CSS URLs in index.html.'
}
$CacheBuster = "verify=$Timestamp"
$ScriptUrl = $ScriptMatch.Groups[1].Value
$StyleUrl = $StyleMatch.Groups[1].Value
$ScriptResponse = Invoke-WebRequest -Uri "${ScriptUrl}?$CacheBuster" -UseBasicParsing
$StyleResponse = Invoke-WebRequest -Uri "${StyleUrl}?$CacheBuster" -Method Head -UseBasicParsing
if ($ScriptResponse.StatusCode -ne 200 -or $StyleResponse.StatusCode -ne 200) {
throw 'The uploaded JS or CSS file is not publicly accessible.'
}
if ($ScriptResponse.Content.Contains($DuplicatePrefix) -or
$ScriptResponse.Content -match 'https://[^\s"''`]+https://') {
throw 'The uploaded JS contains a duplicated URL.'
}
$SampleImage = "${PublicBase}site/assets/logo.png"
$ImageResponse = Invoke-WebRequest -Uri "${SampleImage}?$CacheBuster" -Method Head -UseBasicParsing
if ($ImageResponse.StatusCode -ne 200) {
throw 'The uploaded sample image is not publicly accessible.'
}
} else {
Write-Host '[5/5] Remote verification skipped by parameter' -ForegroundColor Yellow
}
$SizeMiB = [math]::Round($UploadBytes / 1MB, 2)
Write-Host ''
Write-Host 'Deployment completed successfully.' -ForegroundColor Green
Write-Host "OSS directory : $OssTarget"
Write-Host "Public base : $PublicBase"
Write-Host "Uploaded : $($UploadFiles.Count) files / $SizeMiB MiB"
Write-Host "Server index : $IndexPath"
} finally {
Pop-Location
}