修复前端发布缓存完整性与资源清单覆盖

This commit is contained in:
qiuxiang 2026-09-21 09:26:29 +08:00
parent cb5b7fb680
commit 339a67efde
3 changed files with 57 additions and 0 deletions

View File

@ -29,6 +29,7 @@ $dependencyFingerprintPath = Join-Path $frontendCacheRoot 'node-modules.sha'
$jsManifestCachePath = Join-Path $frontendCacheRoot 'manifest-js.json'
$cssManifestCachePath = Join-Path $frontendCacheRoot 'manifest-css.json'
$cachedVendorPath = Join-Path $frontendOutputRoot 'vendor'
$outputFingerprintPath = Join-Path $frontendCacheRoot 'frontend-output.sha'
function Invoke-Native {
param(
@ -147,6 +148,10 @@ $cssFingerprintInputs = @(
$jsFingerprint = "$NodeVersion|$TerserWorkers|$(Get-PathFingerprint $jsFingerprintInputs)"
$cssFingerprint = "$NodeVersion|$(Get-PathFingerprint $cssFingerprintInputs)"
$dependencyFingerprint = "$NodeVersion|$packageLockHash"
$outputCacheValid = (Test-Path -LiteralPath $outputFingerprintPath) -and
(Test-Path -LiteralPath $frontendOutputRoot) -and
((Get-Content -Raw -LiteralPath $outputFingerprintPath).Trim() -eq
(Get-PathFingerprint @('.caiwu-build/public')))
$jsCacheValid = Test-CacheFingerprint `
-FingerprintPath $jsFingerprintPath `
@ -170,6 +175,12 @@ $cssCacheValid = Test-CacheFingerprint `
$cssManifestCachePath
)
if (-not $outputCacheValid) {
$jsCacheValid = $false
$cssCacheValid = $false
Write-Host 'Frontend output integrity is missing or changed. Rebuilding assets.'
}
if ($jsCacheValid -and $cssCacheValid) {
Write-Host 'Frontend inputs unchanged. Reusing JavaScript, CSS, and dependency caches.'
@ -337,12 +348,19 @@ try {
foreach ($manifestPath in @($jsManifestCachePath, $cssManifestCachePath)) {
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
foreach ($property in $manifest.PSObject.Properties) {
if ($manifestPath -eq $jsManifestCachePath -and $property.Name -notlike '*.js') { continue }
if ($manifestPath -eq $cssManifestCachePath -and $property.Name -notlike '*.css') { continue }
$mergedManifest[$property.Name] = $property.Value
}
}
$mergedManifestJson = $mergedManifest | ConvertTo-Json
Write-Utf8NoBom -Path (Join-Path $frontendOutputRoot 'mix-manifest.json') -Value $mergedManifestJson
Write-Utf8NoBom -Path $publicManifestPath -Value $mergedManifestJson
if ($jsFingerprint -ne "$NodeVersion|$TerserWorkers|$(Get-PathFingerprint $jsFingerprintInputs)" -or
$cssFingerprint -ne "$NodeVersion|$(Get-PathFingerprint $cssFingerprintInputs)") {
throw 'Frontend sources changed during build. Retry from a stable checkout.'
}
Write-Utf8NoBom -Path $outputFingerprintPath -Value (Get-PathFingerprint @('.caiwu-build/public'))
}
finally {
Pop-Location

View File

@ -356,6 +356,20 @@ try {
docker run --rm --network none --user www-data --entrypoint php $fullImage artisan --version
} '发布镜像无法引导 Laravel。服务器未发生任何变更。'
# Compare the image's source and compiled assets with this workstation build.
# This also catches a Docker COPY or stale build-context overwrite before upload.
foreach ($asset in @(
@{ Local = 'resources/assets/js/components/business/AccountBills.vue'; Image = 'resources/assets/js/components/business/AccountBills.vue' },
@{ Local = '.caiwu-build/public/js/business/pages.min.js'; Image = 'public/js/business/pages.min.js' },
@{ Local = '.caiwu-build/public/mix-manifest.json'; Image = 'public/mix-manifest.json' }
)) {
$expectedHash = (Get-FileHash -LiteralPath (Join-Path $repoRoot $asset.Local) -Algorithm SHA256).Hash.ToLowerInvariant()
$imageHash = @(& docker run --rm --network none --user www-data --entrypoint sha256sum $fullImage $asset.Image)
if ($LASTEXITCODE -ne 0 -or ($imageHash -join ' ') -notmatch "^$expectedHash\s") {
throw "发布镜像源码或前端资源不匹配:$($asset.Image)。服务器未发生任何变更。"
}
}
$previousShaOutput = @(& ssh @sshOptions -o StrictHostKeyChecking=accept-new $sshTarget "cat '$RemotePath/.deployed-sha' 2>/dev/null || true")
$previousSha = ($previousShaOutput -join "`n").Trim()
if ($LASTEXITCODE -ne 0) {

View File

@ -0,0 +1,25 @@
$ErrorActionPreference = 'Stop'
$source = Join-Path $PSScriptRoot '../../scripts/build-frontend.ps1'
$ast = [Management.Automation.Language.Parser]::ParseFile((Resolve-Path $source), [ref]$null, [ref]$null)
$function = $ast.Find({ param($node) $node -is [Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq 'Get-PathFingerprint' }, $true)
Invoke-Expression $function.Extent.Text
$check = $ast.Find({ param($node) $node -is [Management.Automation.Language.AssignmentStatementAst] -and $node.Left.Extent.Text -eq '$outputCacheValid' }, $true).Right.Extent.Text
$repoRoot = Join-Path ([IO.Path]::GetTempPath()) ('caiwu-cache-test-' + [guid]::NewGuid().ToString('N'))
$frontendOutputRoot = Join-Path $repoRoot '.caiwu-build/public'
$outputFingerprintPath = Join-Path $repoRoot 'output.sha'
New-Item -ItemType Directory -Path $frontendOutputRoot -Force | Out-Null
try {
$asset = Join-Path $frontendOutputRoot 'pages.js'
Set-Content -LiteralPath $asset -Value 'new calendar'
if (Invoke-Expression $check) { throw 'Missing integrity stamp was accepted.' }
Set-Content -LiteralPath $outputFingerprintPath -Value (Get-PathFingerprint @('.caiwu-build/public'))
if (-not (Invoke-Expression $check)) { throw 'Valid output was rejected.' }
Set-Content -LiteralPath $asset -Value 'old calendar'
if (Invoke-Expression $check) { throw 'Overwritten output was accepted.' }
Remove-Item -LiteralPath $asset
if (Invoke-Expression $check) { throw 'Missing output was accepted.' }
Write-Host 'PASS missing stamp, valid cache, overwritten and missing asset detection'
} finally {
if ((Split-Path $repoRoot -Leaf) -notlike 'caiwu-cache-test-*') { throw 'Unexpected test directory.' }
Remove-Item -LiteralPath $repoRoot -Recurse -Force
}