[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, ']+src="([^"]+)"') $StyleMatch = [regex]::Match($IndexContent, ']+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 }