Files
jiapuapp/tests/runtime-assets-contract.ps1
T
2026-07-22 17:31:38 +08:00

105 lines
4.8 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$manifest = 'design-pipeline/manifests/runtime-assets.json'
$validator = 'design-pipeline/scripts/validate-runtime-asset-inventory.mjs'
foreach ($relativePath in @($manifest, $validator)) {
if (-not (Test-Path -LiteralPath (Join-Path $root $relativePath) -PathType Leaf)) {
throw "缺少运行时资产合同文件:$relativePath"
}
}
# 注册表是正式资产物理规格的唯一入口。递归验证同时拒绝缺失、循环、重复导入、重复 id 和重复 output。
$json = & node (Join-Path $root $validator) $manifest
if ($LASTEXITCODE -ne 0) { throw '运行时资产注册表验证失败' }
$inventory = $json | ConvertFrom-Json
$owned = @{}
foreach ($asset in $inventory.assets) {
if ($owned.ContainsKey($asset.output)) { throw "运行时资产存在重复所有者:$($asset.output)" }
$owned[$asset.output] = $asset
}
# static/assets 不允许存在“先留着再说”的文件:每个物理文件都必须有 owner,每个 owner 也必须有真实输出。
$physicalAssets = @(
Get-ChildItem -LiteralPath (Join-Path $root 'static/assets') -Recurse -File |
ForEach-Object { $_.FullName.Substring($root.Length + 1).Replace('\', '/') }
)
$unownedFiles = @($physicalAssets | Where-Object { -not $owned.ContainsKey($_) })
$missingFiles = @($owned.Keys | Where-Object { $_ -notin $physicalAssets })
if ($unownedFiles.Count -gt 0) { throw "存在未登记的 static/assets 文件:$($unownedFiles -join ', ')" }
if ($missingFiles.Count -gt 0) { throw "资产 owner 指向不存在的输出:$($missingFiles -join ', ')" }
Add-Type -AssemblyName System.Drawing
foreach ($asset in $inventory.assets) {
$absolutePath = Join-Path $root $asset.output
$image = [System.Drawing.Image]::FromFile($absolutePath)
try {
if ($image.Width -ne $asset.width -or $image.Height -ne $asset.height) {
throw "运行时资产尺寸漂移:$($asset.output)"
}
$hasAlpha = [System.Drawing.Image]::IsAlphaPixelFormat($image.PixelFormat)
if ($hasAlpha -ne [bool]$asset.alpha) {
throw "运行时资产透明通道漂移:$($asset.output)"
}
} finally {
$image.Dispose()
}
if ($asset.sha256) {
$actualBytes = (Get-Item -LiteralPath $absolutePath).Length
$actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $absolutePath).Hash.ToLowerInvariant()
if ($actualBytes -ne $asset.bytes -or $actualHash -ne $asset.sha256) {
throw "直接提交的运行时资产发生漂移:$($asset.output)"
}
} elseif ((Get-Item -LiteralPath $absolutePath).Length -gt $asset.maxBytes) {
throw "清单生成资产超过体积上限:$($asset.output)"
}
}
# 消费者只从运行时代码中提取;清单、测试和文档本身不能替孤立输出制造虚假引用。
$referenceFiles = Get-ChildItem -LiteralPath @(
(Join-Path $root 'pages'),
(Join-Path $root 'components'),
(Join-Path $root 'styles'),
(Join-Path $root 'data'),
(Join-Path $root 'utils')
) -Recurse -File | Where-Object { $_.Extension -in '.vue', '.scss', '.js' }
$referenceCounts = @{}
foreach ($output in $owned.Keys) { $referenceCounts[$output] = 0 }
foreach ($file in $referenceFiles) {
$content = Get-Content -Raw -Encoding UTF8 -LiteralPath $file.FullName
foreach ($match in [regex]::Matches($content, '/?static/assets/[A-Za-z0-9_./-]+\.(?:png|jpe?g|webp|svg)')) {
$assetPath = $match.Value.TrimStart('/')
if (-not $owned.ContainsKey($assetPath)) {
throw "运行时代码引用了无 owner 的资产:$assetPath$($file.FullName.Substring($root.Length + 1))"
}
$referenceCounts[$assetPath]++
}
}
# ModulePage 的两个路径由四个受控模块名动态展开;这里验证模板和有限集合,禁止扫描器漏掉动态消费者。
$modulePagePath = Join-Path $root 'components/ModulePage.vue'
$modulePage = Get-Content -Raw -Encoding UTF8 -LiteralPath $modulePagePath
foreach ($template in @(
'/static/assets/modules/${assetModule.value}/transparent/module-content-frame.png',
'/static/assets/modules/${assetModule.value}/transparent/module-field-frame.png'
)) {
if (-not $modulePage.Contains($template)) { throw "ModulePage 动态资产模板漂移:$template" }
}
foreach ($module in @('family', 'records', 'notification', 'profile')) {
foreach ($fileName in @('module-content-frame.png', 'module-field-frame.png')) {
$assetPath = "static/assets/modules/$module/transparent/$fileName"
if (-not $owned.ContainsKey($assetPath)) { throw "ModulePage 动态资产没有 owner$assetPath" }
$referenceCounts[$assetPath]++
}
}
$orphanedOutputs = @($referenceCounts.Keys | Where-Object { $referenceCounts[$_] -eq 0 })
if ($orphanedOutputs.Count -gt 0) {
throw "资产清单保留了没有运行时消费者的输出:$($orphanedOutputs -join ', ')"
}
Write-Output "RUNTIME-ASSETS-CONTRACT PASS ($($owned.Count) assets)"