#!/usr/bin/env node import { createHash } from 'node:crypto'; import { brotliCompressSync, constants as zlibConstants } from 'node:zlib'; import { readFile, readdir, stat } from 'node:fs/promises'; import { relative, resolve, sep } from 'node:path'; import sharp from 'sharp'; import { fileTypeFromBuffer } from 'file-type'; import { probeVideo, runMediaCommand } from './lib/video-frames.mjs'; import { classifyArtifact, evaluateBudgetTotals, evaluateMediaAssetBudget, normalizeDetectedMediaType, resolveBudgetGateMode, validateRuntimeBudgetTraceReport, } from './lib/bundle-budgets.mjs'; const root = resolve(process.argv[2] ?? 'dist'); const transferOnly = process.argv.includes('--transfer-only'); const runtimeTraceIndex = process.argv.indexOf('--runtime-trace'); const runtimeTracePath = runtimeTraceIndex === -1 ? null : process.argv[runtimeTraceIndex + 1]; if (runtimeTraceIndex !== -1 && runtimeTracePath === undefined) throw new Error('--runtime-trace requires one explicit drive-trace JSON path.'); const gateMode = resolveBudgetGateMode({ transferOnly, runtimeTracePath }); if (!(await stat(root)).isDirectory()) throw new Error('Bundle budget target must be a directory.'); const listFiles = async (directory) => { const files = []; for (const entry of await readdir(directory, { withFileTypes: true })) { const path = resolve(directory, entry.name); if (entry.isSymbolicLink()) throw new Error('Bundle budget input cannot contain symbolic links.'); if (entry.isDirectory()) files.push(...await listFiles(path)); else if (entry.isFile()) files.push(path); else throw new Error('Bundle budget input contains an unsupported entry.'); } return files; }; const compressedSize = (path, bytes) => (/\.(?:avif|br|gif|jpe?g|mp3|mp4|opus|png|webm|webp|woff2?)$/iu.test(path) ? bytes.byteLength : brotliCompressSync(bytes, { params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 11 } }).byteLength); const readJson = async (path, label) => { try { return JSON.parse(await readFile(path, 'utf8')); } catch { throw new Error(`${label} must be valid JSON.`); } }; const probeAudio = async (path) => { const { stdout } = await runMediaCommand('ffprobe', [ '-v', 'error', '-show_entries', 'format=duration:stream=codec_type', '-of', 'json', path, ], 'ASSET-AUDIO-PROBE-001', 20_000); let result; try { result = JSON.parse(stdout); } catch { throw new Error('ASSET-AUDIO-PROBE-001'); } const streams = Array.isArray(result.streams) ? result.streams : []; const duration = Number(result.format?.duration); if (streams.length !== 1 || streams[0]?.codec_type !== 'audio' || !Number.isFinite(duration) || duration <= 0 || duration > 300) { throw new Error('ASSET-AUDIO-PROBE-001'); } return { duration }; }; const manifest = await readJson(resolve('content/public/asset-manifest.json'), 'Public asset manifest'); const content = await readJson(resolve('content/public/portfolio-content.json'), 'Portfolio content'); if (!Array.isArray(manifest?.assets)) throw new Error('Public asset manifest is invalid.'); const assetClassById = new Map(); for (const project of [...(content.projects?.selected ?? []), ...(content.projects?.additional ?? [])]) { for (const media of project.media ?? []) { assetClassById.set(media.assetId, media.kind === 'chart' ? 'chart' : 'project-still'); if (media.posterAssetId) assetClassById.set(media.posterAssetId, 'project-still'); if (media.fallbackAssetId) assetClassById.set(media.fallbackAssetId, 'project-still'); } for (const assetId of project.assetIds ?? []) if (!assetClassById.has(assetId)) assetClassById.set(assetId, 'project-still'); } for (const group of content.toolkit ?? []) for (const technology of group.technologies ?? []) { if (technology.assetId) assetClassById.set(technology.assetId, 'project-still'); } if (content.metadata?.socialPreviewAssetId) assetClassById.set(content.metadata.socialPreviewAssetId, 'project-still'); if (content.soundtrack) { for (const region of [ content.soundtrack.intro, content.soundtrack.main, content.soundtrack.afterglow, content.soundtrack.driveLift, ]) { if (region?.opusAssetId) assetClassById.set(region.opusAssetId, 'soundtrack-audio'); if (region?.mp3AssetId) assetClassById.set(region.mp3AssetId, 'soundtrack-audio'); } } const manifestByPath = new Map(manifest.assets.map((asset) => [`assets/${asset.fileName}`, asset])); const records = []; const mediaViolations = []; for (const absolute of await listFiles(root)) { const path = relative(root, absolute).split(sep).join('/'); const bytes = await readFile(absolute); const source = /\.(?:css|html|js|json|txt|xml)$/u.test(path) ? bytes.toString('utf8') : ''; const manifestAsset = manifestByPath.get(path); let bucket; if (manifestAsset !== undefined) { const digest = createHash('sha256').update(bytes).digest('hex'); if (digest !== manifestAsset.sha256) throw new Error(`Published asset hash mismatch: ${path}`); const mediaType = manifestAsset.mediaType; const detected = await fileTypeFromBuffer(bytes); const detectedMediaType = normalizeDetectedMediaType(detected?.mime) ?? (/^\s*]/u.test(bytes.toString('utf8', 0, Math.min(bytes.length, 256))) ? 'image/svg+xml' : null); if (detectedMediaType !== mediaType) throw new Error(`Manifest media type disagrees with deterministic probing: ${manifestAsset.id}`); let probe; if (mediaType?.startsWith('video/')) { const video = await probeVideo(absolute, { allowAudio: false }); probe = { mediaType, width: video.width, height: video.height, durationMs: Math.round(video.duration * 1_000) }; bucket = 'project-video'; } else if (mediaType?.startsWith('image/')) { const image = await sharp(absolute, { failOn: 'error', limitInputPixels: 50_000_000 }).metadata(); probe = { mediaType, width: image.width, height: image.height, durationMs: null }; bucket = assetClassById.get(manifestAsset.id) === 'chart' ? 'chart' : 'project-still'; } else if (mediaType?.startsWith('audio/')) { const audio = await probeAudio(absolute); probe = { mediaType, width: null, height: null, durationMs: Math.round(audio.duration * 1_000) }; bucket = 'soundtrack-audio'; } else { throw new Error(`Manifest asset has unsupported media type: ${manifestAsset.id}`); } mediaViolations.push(...evaluateMediaAssetBudget( manifestAsset, probe, compressedSize(path, bytes), bucket === 'chart' ? 'chart' : bucket === 'soundtrack-audio' ? 'soundtrack-audio' : 'project-still', ).violations); } else { bucket = classifyArtifact(path, source); if (['project-video', 'project-still', 'chart', 'soundtrack-audio'].includes(bucket)) { throw new Error(`Project media must be present in the public asset manifest: ${path}`); } } records.push({ path, bucket, bytes: bytes.byteLength, transferBytes: compressedSize(path, bytes) }); } for (const path of manifestByPath.keys()) if (!records.some((record) => record.path === path)) throw new Error(`Manifest asset is missing from the build: ${path}`); const sum = (bucket) => records.filter((record) => record.bucket === bucket).reduce((total, record) => total + record.transferBytes, 0); const totals = { initialHtmlCriticalCss: sum('initial-html') + sum('critical-css'), initialApplicationJs: sum('initial-application-js'), heroPoster: sum('hero-poster'), initialFonts: sum('initial-fonts'), firstLiveScene: sum('first-live-scene'), deferredProjectVisuals: sum('project-still') + sum('chart'), highTierExtras: sum('high-tier-extras'), deferredNonVideo: sum('first-live-scene') + sum('high-tier-extras') + sum('project-still') + sum('chart'), deferredAudio: sum('soundtrack-audio'), }; const entry = records.find((record) => record.bucket === 'initial-application-js'); const scene = records.find((record) => record.bucket === 'first-live-scene'); if (entry === undefined || scene === undefined) throw new Error('Both initial application and deferred live-scene chunks are required.'); const entrySource = await readFile(resolve(root, entry.path), 'utf8'); if (!/import\([^)]*environment-/u.test(entrySource)) throw new Error('The live scene is not separated behind a dynamic import.'); if (/WebGLRenderer|WebGLProgram|ShaderMaterial/u.test(entrySource)) throw new Error('Three.js/live-scene code leaked into the reduced-motion initial chunk.'); let runtime = null; if (gateMode.kind === 'full') { const trace = await readJson(resolve(gateMode.runtimeTracePath), 'Runtime drive trace'); runtime = validateRuntimeBudgetTraceReport(trace); } const violations = [...evaluateBudgetTotals(totals).violations, ...mediaViolations, ...(runtime?.violations ?? [])]; const report = { schema: 'portfolio-bundle-budget/v1', compression: 'brotli-quality-11-or-raw-already-compressed', totals, runtime: runtime === null ? { status: 'transfer-only-non-gating', gating: false, runtimeEvidence: 'pending' } : { status: 'verified-production-path-trace', gating: true, tier: runtime.tier }, records: records.sort((left, right) => left.path.localeCompare(right.path)), violations, }; process.stdout.write(`${JSON.stringify(report, null, 2)}\n`); if (violations.length > 0) process.exitCode = 1;