param( [string]$Root = (Join-Path $PSScriptRoot "..\site"), [string]$PublicRoot = "", [string]$PublicArchive = "" ) $ErrorActionPreference = "Stop" $Root = (Resolve-Path -LiteralPath $Root).Path function Assert-Check { param([bool]$Condition, [string]$Message) if (-not $Condition) { throw "FAIL: $Message" } Write-Host "PASS: $Message" } $requiredFiles = @( "index.html", "404.html", "robots.txt", "sitemap.xml", "site.webmanifest", "assets/css/main.css", "assets/js/main.js", "assets/js/site-config.json", "assets/fonts/NotoSerifSC-subset.woff2", "assets/fonts/OFL.txt", "assets/images/avatar.jpg", "assets/images/avatar-frame-animated.png", "assets/images/avatar-frame-mask.svg", "assets/images/avatar-frame-static.png", "assets/images/favicon.svg" ) foreach ($relative in $requiredFiles) { Assert-Check (Test-Path -LiteralPath (Join-Path $Root $relative) -PathType Leaf) "required file exists: $relative" } $index = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "index.html") $notFound = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "404.html") $manifestPath = Join-Path $Root "site.webmanifest" $manifest = Get-Content -Raw -Encoding UTF8 $manifestPath | ConvertFrom-Json $siteConfig = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "assets/js/site-config.json") | ConvertFrom-Json [Uri]$siteUri = $siteConfig.site.url [string]$siteRoot = $siteUri.AbsoluteUri.TrimEnd('/') + '/' $robots = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "robots.txt") [xml]$sitemap = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "sitemap.xml") [xml]$favicon = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "assets/images/favicon.svg") $nginxPath = Join-Path $PSScriptRoot "..\nginx\midctrl.com.example.conf" Assert-Check (Test-Path -LiteralPath $nginxPath -PathType Leaf) "sanitized Nginx example exists" $nginx = Get-Content -Raw -Encoding UTF8 $nginxPath $css = Get-Content -Raw -Encoding UTF8 (Join-Path $Root "assets/css/main.css") $jsPath = Join-Path $Root "assets/js/main.js" $js = Get-Content -Raw -Encoding UTF8 $jsPath $indexScriptVersion = [regex]::Match($index, 'main\.js\?v=([^"'']+)').Groups[1].Value $notFoundScriptVersion = [regex]::Match($notFound, 'main\.js\?v=([^"'']+)').Groups[1].Value $fontVersion = [regex]::Match($css, 'NotoSerifSC-subset\.woff2\?v=([^"'']+)').Groups[1].Value Assert-Check ($notFound -match 'href="/assets/images/avatar\.jpg"' -and $notFound -match 'href="/assets/css/main\.css(?:\?[^" ]*)?"' -and $notFound -match 'src="/assets/js/main\.js(?:\?[^" ]*)?"' -and $notFound -match 'src="/assets/images/avatar\.jpg"') "404 assets use root-absolute URLs" Assert-Check ($notFound -match 'class="portrait-image"' -and $notFound -match 'avatar-frame-animated\.png' -and $notFound -notmatch 'axis-line') "404 page uses the current avatar structure" Assert-Check ($manifest.icons[0].src -eq "/assets/images/avatar.jpg") "manifest icon uses root-absolute URL" Assert-Check ($manifest.icons[0].sizes -eq "512x512") "manifest icon size matches the avatar" Assert-Check ([string]$manifest.start_url -eq "/") "manifest starts at the site root" Assert-Check ($siteConfig.site.name -and $siteConfig.site.description -and $siteUri.IsAbsoluteUri -and $siteUri.Scheme -eq "https") "site identity and HTTPS URL are centralized in site-config.json" Assert-Check ($siteConfig.links.Count -gt 0 -and @($siteConfig.links | Where-Object { -not $_.name -or $_.url -notmatch '^https://' }).Count -eq 0) "navigation configuration contains named HTTPS links" Assert-Check ([string]$sitemap.urlset.url.loc -eq $siteRoot -and $robots -match [regex]::Escape("Sitemap: ${siteRoot}sitemap.xml")) "robots and sitemap use the configured canonical homepage" Assert-Check ($index -match 'rel="canonical" href="/"' -and $index -match 'property="og:url" content="/"' -and $js -match 'applySiteMetadata') "HTML keeps portable metadata fallbacks and applies the configured site URL" Assert-Check ([string]$favicon.svg.viewBox -eq "0 0 64 64") "favicon is valid SVG XML" Add-Type -AssemblyName System.Drawing $avatar = [System.Drawing.Image]::FromFile((Join-Path $Root "assets/images/avatar.jpg")) try { Assert-Check ($avatar.Width -eq 512 -and $avatar.Height -eq 512) "avatar is 512x512" } finally { $avatar.Dispose() } $node = Get-Command node -ErrorAction SilentlyContinue Assert-Check ($null -ne $node) "Node.js is available for JavaScript syntax validation" & $node.Source --check $jsPath Assert-Check ($LASTEXITCODE -eq 0) "main.js passes node --check" $renderTargetTest = Join-Path $PSScriptRoot "..\tests\test-render-target-resize.js" & $node.Source $renderTargetTest $jsPath Assert-Check ($LASTEXITCODE -eq 0) "Render Target resize and recovery tests pass" Assert-Check ($js -match 'pointFlower\.numFlowers\s*=\s*1600') "particle count remains 1600" Assert-Check (([regex]::Matches($js, 'gl\.drawArrays\(gl\.POINTS')).Count -eq 5) "flower draw call count remains 5" Assert-Check ($js -match 'for\(var i = 0; i < 2; i\+\+\)' -and ([regex]::Matches($js, 'drawEffect\(effectLib\.dirBlur\)').Count -eq 2)) "Bloom retains four directional blur draws" Assert-Check ($js -match 'maximumFrameRate\s*=\s*120') "animation cap remains 120 FPS" Assert-Check ($js -match 'requestAnimationFrame' -and $js -match 'matchMedia\(' -and $js -match 'prefers-reduced-motion') "refresh scheduling and reduced-motion handling are present" Assert-Check ($js -match 'DOMContentLoaded' -and $js -match 'initializeSakura' -and $js -notmatch "window\.addEventListener\('load'") "WebGL starts when the DOM is ready without waiting for fonts or images" Assert-Check ($js -match "addEventListener\('pagehide'" -and $js -match "addEventListener\('pageshow'" -and $js -match 'synchronizeAnimationState') "page lifecycle recovery re-synchronizes the animation loop" Assert-Check ($js -match 'request\.timeout\s*=\s*3000') "navigation configuration has a bounded network timeout" Assert-Check ($indexScriptVersion -and $indexScriptVersion -eq $notFoundScriptVersion -and $indexScriptVersion -eq $fontVersion) "homepage, 404 page, and font use one cache version" Assert-Check ($js -match 'createRenderTargetSet' -and $js -match 'deleteRenderTargetSet') "render target sets are swapped atomically" Assert-Check ($js -match 'normalRenderPixelBudget\s*=\s*10000000' -and $js -match 'constrainedRenderPixelBudget\s*=\s*6000000') "render target pixel budgets are configured" Assert-Check ($js -match 'resizeDebounceMs\s*=\s*140' -and $js -match 'measureCanvasSize' -and $js -match 'commitCanvasSize') "resize measurement and delayed commit are present" Assert-Check ($js -match 'shouldReleaseBeforeAllocate' -and $js -match 'gl\.finish\(\)' -and $js -match 'getResizeCandidates') "high-memory resize and allocation fallback paths are present" Assert-Check ($js -notmatch 'gl\.enable\(gl\.TEXTURE_2D\)' -and $js -notmatch 'gl\.disable\(gl\.TEXTURE_2D\)' -and $js -notmatch 'gl\.POINT\b') "invalid WebGL capability enums are absent" Assert-Check ($nginx -match 'root /var/www/midctrl\.com;' -and $nginx -match 'try_files \$uri \$uri/ =404') "Nginx serves only the dedicated static web root" Assert-Check ($nginx -match 'location ~\* \^/assets/\(\?:css\|js\)/' -and $nginx -match 'expires -1') "unversioned CSS and JavaScript revalidate" Assert-Check ($nginx -match 'location \^~ /assets/fonts/' -and $nginx -match 'Cache-Control "public, immutable"') "self-hosted fonts use long-lived immutable caching" Assert-Check ($nginx -notmatch 'location\s+/[a-f0-9]{16,}' -and $nginx -notmatch 'proxy_pass\s+http://127\.0\.0\.1:\d+') "public Nginx example omits host-private proxy details" Assert-Check ($js -match 'frameRequestGeneration' -and $js -match 'cancelScheduledAnimationFrame') "animation loop guards stale callbacks after context loss" Assert-Check ((Get-Item -LiteralPath (Join-Path $Root "assets/fonts/NotoSerifSC-subset.woff2")).Length -gt 1000 -and (Get-Content -Raw -Encoding UTF8 (Join-Path $Root "assets/fonts/OFL.txt")) -match 'SIL Open Font License') "Noto Serif SC subset and license are present" $allowedPublic = @( "index.html", "404.html", "robots.txt", "sitemap.xml", "site.webmanifest", "assets/css/main.css", "assets/js/main.js", "assets/js/site-config.json", "assets/fonts/NotoSerifSC-subset.woff2", "assets/fonts/OFL.txt", "assets/images/avatar.jpg", "assets/images/avatar-frame-animated.png", "assets/images/avatar-frame-mask.svg", "assets/images/avatar-frame-static.png", "assets/images/favicon.svg" ) | Sort-Object if (-not $PublicRoot) { $PublicRoot = $Root } if ($PublicRoot) { $PublicRoot = (Resolve-Path -LiteralPath $PublicRoot).Path $actualPublic = @(Get-ChildItem -LiteralPath $PublicRoot -Recurse -File | ForEach-Object { $_.FullName.Substring($PublicRoot.TrimEnd('\').Length + 1).Replace('\', '/') } | Sort-Object) Assert-Check (($actualPublic -join "`n") -eq ($allowedPublic -join "`n")) "public bundle contains only web-root files" } if ($PublicArchive) { $PublicArchive = (Resolve-Path -LiteralPath $PublicArchive).Path Add-Type -AssemblyName System.IO.Compression.FileSystem $archive = [System.IO.Compression.ZipFile]::OpenRead($PublicArchive) try { $archiveFiles = @($archive.Entries | Where-Object { $_.Name } | ForEach-Object { $_.FullName.Replace('\', '/').TrimStart('/') } | Sort-Object) Assert-Check (($archiveFiles -join "`n") -eq ($allowedPublic -join "`n")) "public ZIP contains only web-root files" } finally { $archive.Dispose() } } Write-Host "Production checks complete."