Files
jiapuapp/tests/a01-code-pipeline-contract.ps1
T
2026-07-16 07:58:53 +08:00

113 lines
4.7 KiB
PowerShell

$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$manifestRelativePath = 'design-pipeline/manifests/a01.json'
$requiredFiles = @(
'design-pipeline/package.json',
$manifestRelativePath,
'design-pipeline/scripts/build-a01.mjs'
)
foreach ($relativePath in $requiredFiles) {
$absolutePath = Join-Path $root $relativePath
if (-not (Test-Path -LiteralPath $absolutePath -PathType Leaf)) {
throw "Missing A01 code pipeline file: $relativePath"
}
}
$manifest = Get-Content -LiteralPath (Join-Path $root $manifestRelativePath) -Raw -Encoding UTF8 | ConvertFrom-Json
if ($manifest.schemaVersion -ne 1) {
throw "Expected A01 pipeline schemaVersion 1, found $($manifest.schemaVersion)."
}
if ($manifest.canvas.width -ne 412 -or $manifest.canvas.height -ne 915) {
throw 'A01 logical canvas must be 412x915.'
}
$requiredStates = @('password-hidden', 'password-visible', 'sms-default', 'sms-countdown')
foreach ($stateName in $requiredStates) {
if ($manifest.states.name -notcontains $stateName) {
throw "Missing A01 preview state: $stateName"
}
}
$workspacePrefix = [IO.Path]::GetFullPath($root).TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
$runtimePrefix = 'static/assets/modules/auth/'
$assetIds = @{}
foreach ($asset in $manifest.assets) {
if (-not $asset.id -or $assetIds.ContainsKey($asset.id)) {
throw "A01 asset id must be present and unique: $($asset.id)"
}
$assetIds[$asset.id] = $true
foreach ($propertyName in @('source', 'output', 'width', 'height', 'alpha', 'maxBytes')) {
if ($null -eq $asset.$propertyName -or "$($asset.$propertyName)" -eq '') {
throw "A01 asset '$($asset.id)' is missing property: $propertyName"
}
}
if (-not $asset.output.StartsWith($runtimePrefix, [StringComparison]::OrdinalIgnoreCase)) {
throw "A01 runtime asset must stay in ${runtimePrefix}: $($asset.output)"
}
if ($asset.source -ne $asset.output) {
throw "A01 portable pipeline must use the committed runtime asset as its stable source: $($asset.id)"
}
if ($asset.source -match '(?i)candidates|review|generated|node_modules|\.psd$') {
throw "A01 portable pipeline source must not depend on local intermediate files: $($asset.source)"
}
if ($asset.output -match '(?i)review|page-v2|\.psd$') {
throw "A01 runtime asset must not use a preview, full-page image, or PSD: $($asset.output)"
}
$sourcePath = [IO.Path]::GetFullPath((Join-Path $root $asset.source))
$outputPath = [IO.Path]::GetFullPath((Join-Path $root $asset.output))
foreach ($path in @($sourcePath, $outputPath)) {
if (-not $path.StartsWith($workspacePrefix, [StringComparison]::OrdinalIgnoreCase)) {
throw "A01 pipeline path escaped the workspace: $path"
}
}
if (-not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
throw "Missing A01 source asset: $($asset.source)"
}
if (-not (Test-Path -LiteralPath $outputPath -PathType Leaf)) {
throw "Missing generated A01 runtime asset: $($asset.output)"
}
$bytes = [IO.File]::ReadAllBytes($outputPath)
if ($bytes.Length -lt 24 -or $bytes[0] -ne 0x89 -or $bytes[1] -ne 0x50 -or $bytes[2] -ne 0x4E -or $bytes[3] -ne 0x47) {
throw "Generated A01 asset is not a PNG: $($asset.output)"
}
$width = [Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($bytes, 16))
$height = [Net.IPAddress]::NetworkToHostOrder([BitConverter]::ToInt32($bytes, 20))
if ($width -ne $asset.width -or $height -ne $asset.height) {
throw "Generated A01 asset has wrong dimensions: $($asset.output) expected $($asset.width)x$($asset.height), found ${width}x${height}."
}
if ($bytes.Length -gt $asset.maxBytes) {
throw "Generated A01 asset exceeds maxBytes: $($asset.output) has $($bytes.Length), limit $($asset.maxBytes)."
}
$pngColorType = $bytes[25]
$hasAlpha = $pngColorType -in @(4, 6)
if ([bool]$asset.alpha -ne $hasAlpha) {
throw "Generated A01 asset Alpha contract mismatch: $($asset.output)"
}
}
foreach ($preview in $manifest.previews) {
$previewPath = Join-Path $root $preview.output
if (-not (Test-Path -LiteralPath $previewPath -PathType Leaf)) {
throw "Missing generated A01 preview: $($preview.output)"
}
}
$buildReport = Join-Path $root 'design-pipeline/generated/a01/build-report.json'
if (-not (Test-Path -LiteralPath $buildReport -PathType Leaf)) {
throw 'Missing A01 code pipeline build report.'
}
$builder = Get-Content -LiteralPath (Join-Path $root 'design-pipeline/scripts/build-a01.mjs') -Raw -Encoding UTF8
foreach ($forbidden in @('Photoshop.Application', '.psd', 'scripts/photoshop')) {
if ($builder -match [regex]::Escape($forbidden)) {
throw "A01 code pipeline must not depend on Photoshop or PSD: $forbidden"
}
}
Write-Output 'A01-CODE-PIPELINE-CONTRACT PASS'