import { createHash } from 'node:crypto'; import { readFile, readdir, stat } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const scriptDirectory = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(scriptDirectory, '..'); const cpDirectory = path.join(root, 'activity', 'cp'); const pageDirectory = path.join(cpDirectory, 'huwaa'); const assetDirectory = path.join(pageDirectory, 'assets', 'layers'); const manifestPath = path.join(assetDirectory, 'manifest.json'); const expectedEntryFrames = { plaza: '831:3320', rank: '831:4048', rewards: '831:3913', }; const expectedDesignSizes = { plaza: { width: 1080, height: 4283 }, rank: { width: 1080, height: 3733 }, rewards: { width: 1080, height: 4782 }, }; const forbiddenAggregateNodes = new Set([ '543:1998', '823:3005', '831:3320', '831:4048', '831:3913', '831:3323', '831:3326', '831:3339', '831:3349', '831:3642', '831:4381', '831:3942', ]); function invariant(condition, message) { if (!condition) throw new Error(message); } const manifest = JSON.parse(await readFile(manifestPath, 'utf8')); invariant(manifest.schemaVersion === 1, 'Unsupported manifest schema'); invariant( manifest.figma?.fileKey === 'OTOg5cwbhVNWxkImGmzTJl', 'Unexpected Figma file key' ); invariant(manifest.figma?.canvas?.id === '543:1998', 'Unexpected Figma canvas'); invariant( JSON.stringify(manifest.figma?.entryFrames) === JSON.stringify(expectedEntryFrames), 'Huwaa CP entry-frame mapping changed' ); invariant( JSON.stringify(manifest.figma?.designSizes) === JSON.stringify(expectedDesignSizes), 'Huwaa CP design sizes changed' ); invariant(Array.isArray(manifest.assets), 'Manifest assets must be an array'); invariant(manifest.assets.length === 17, 'Manifest must describe 17 layers'); function webPDimensions(bytes) { invariant( bytes.subarray(0, 4).toString('ascii') === 'RIFF' && bytes.subarray(8, 12).toString('ascii') === 'WEBP', 'Asset is not a WebP file' ); const chunk = bytes.subarray(12, 16).toString('ascii'); if (chunk === 'VP8X') { return { width: bytes.readUIntLE(24, 3) + 1, height: bytes.readUIntLE(27, 3) + 1, }; } if (chunk === 'VP8 ') { return { width: bytes.readUInt16LE(26) & 0x3fff, height: bytes.readUInt16LE(28) & 0x3fff, }; } if (chunk === 'VP8L') { invariant(bytes[20] === 0x2f, 'Invalid lossless WebP signature'); const packed = bytes.readUInt32LE(21); return { width: (packed & 0x3fff) + 1, height: ((packed >>> 14) & 0x3fff) + 1, }; } throw new Error(`Unsupported WebP chunk ${chunk}`); } const listedFiles = new Set(); for (const asset of manifest.assets) { invariant( asset.file && !listedFiles.has(asset.file), `Duplicate asset ${asset.file}` ); listedFiles.add(asset.file); invariant(asset.sourceNode, `${asset.file}: sourceNode is required`); invariant( !forbiddenAggregateNodes.has(asset.sourceNode), `${asset.file}: aggregate Figma export is forbidden` ); invariant( asset.sourceName && asset.sourceType, `${asset.file}: source name/type is required` ); invariant(asset.purpose, `${asset.file}: purpose is required`); invariant( asset.bounds?.width > 0 && asset.bounds?.height > 0, `${asset.file}: design bounds are required` ); invariant( asset.original?.width > 0 && asset.original?.height > 0, `${asset.file}: export dimensions are required` ); invariant(asset.exportFormat, `${asset.file}: export format is required`); invariant( Number.isInteger(asset.bytes) && asset.bytes > 0, `${asset.file}: byte size is required` ); invariant( /^[a-f0-9]{64}$/.test(asset.sha256 || ''), `${asset.file}: SHA-256 is required` ); for (const flag of [ 'containsText', 'containsDynamicText', 'containsControls', 'containsDynamicMedia', ]) { invariant( typeof asset[flag] === 'boolean', `${asset.file}: ${flag} must be boolean` ); } invariant( !asset.containsDynamicText, `${asset.file}: rendered dynamic text is forbidden` ); invariant( !asset.containsControls, `${asset.file}: rendered controls are forbidden` ); invariant( !asset.containsDynamicMedia, `${asset.file}: dynamic business media is forbidden` ); invariant(!asset.containsText, `${asset.file}: embedded text is forbidden`); const assetPath = path.join(assetDirectory, asset.file); const [bytes, info] = await Promise.all([ readFile(assetPath), stat(assetPath), ]); const digest = createHash('sha256').update(bytes).digest('hex'); const dimensions = webPDimensions(bytes); invariant(info.size === asset.bytes, `${asset.file}: byte size mismatch`); invariant(digest === asset.sha256, `${asset.file}: SHA-256 mismatch`); invariant( dimensions.width === asset.original.width && dimensions.height === asset.original.height, `${asset.file}: image dimensions do not match the manifest` ); } const diskAssets = (await readdir(assetDirectory)) .filter((file) => file !== 'manifest.json') .sort(); invariant( JSON.stringify(diskAssets) === JSON.stringify([...listedFiles].sort()), 'Every Huwaa CP visual asset must have exactly one manifest entry' ); const [html, css, js, theme, api, locales] = await Promise.all([ readFile(path.join(cpDirectory, 'huwaa.html'), 'utf8'), readFile(path.join(pageDirectory, 'huwaa.css'), 'utf8'), readFile(path.join(pageDirectory, 'huwaa.js'), 'utf8'), readFile(path.join(root, 'common', 'theme.css'), 'utf8'), readFile(path.join(root, 'common', 'api.js'), 'utf8'), Promise.all( ['en', 'ar', 'tr', 'es'].map((locale) => readFile( path.join(root, 'common', 'locales', `${locale}.json`), 'utf8' ) ) ), ]); const runtimeSource = [html, css, js].join('\n'); for (const nodeID of forbiddenAggregateNodes) { invariant( !runtimeSource.includes(nodeID), `Runtime must not reference aggregate Figma node ${nodeID}` ); } for (const forbidden of [ 'whole-page', 'page-screenshot', 'figma-frame.png', 'cp-plaza.png', 'cp-week.png', 'cp-reward.png', ]) { invariant( !runtimeSource.toLowerCase().includes(forbidden), `Forbidden aggregate screenshot reference: ${forbidden}` ); } for (const asset of manifest.assets) { invariant( runtimeSource.includes(asset.file), `${asset.file}: traced asset is not consumed by the Huwaa CP page` ); } invariant(theme.includes('--hy-theme-primary'), 'Common theme is invalid'); invariant(html.includes('../../common/theme.css'), 'Common theme is required'); invariant(html.includes('../../common/i18n.js'), 'Common i18n is required'); invariant(html.includes('../../common/api.js'), 'Common API is required'); invariant( html.includes('../../common/params.js'), 'Common params are required' ); invariant( html.includes('data-hy-app-code="huwaa"'), 'Huwaa app identity is required' ); invariant( html.indexOf("window.HyAppDefaultAppCode = 'huwaa'") < html.indexOf('../../common/api.js'), 'Huwaa app code must be configured before common API initialization' ); invariant( html.includes("window.HyAppI18nSupported = ['en', 'ar', 'tr', 'es']"), 'Huwaa CP must restrict its language surface to en/ar/tr/es' ); invariant(api.includes('cpWeeklyRankAPI'), 'CP weekly-rank API is required'); invariant( js.includes('HyAppAPI.cpWeeklyRank.status'), 'Huwaa CP must consume the weekly-rank API' ); invariant( !js.includes('HyAppAPI.cpSpace.intimacyLeaderboard') && js.includes("activityData.plazaState = 'unavailable'"), 'The cumulative intimacy leaderboard must not be presented as the Plaza gift-event feed' ); invariant( js.includes("get('mock') === '1'") && js.includes('isLocalRuntime()'), 'Visual fixtures must require explicit local mock mode' ); invariant( js.includes('Promise.allSettled'), 'Independent CP API failures must be isolated' ); invariant( js.includes('requestGeneration'), 'Stale asynchronous CP responses must be rejected' ); invariant( js.includes('resolveExplicitEndAt') && !js.includes('localStorage'), 'Countdown must not fabricate and persist a production activity period' ); invariant( js.includes('serverTimeOffsetMS') && js.includes('server_time_ms'), 'Countdown must use the backend server clock' ); invariant( js.includes('source.enabled !== false'), 'The weekly-rank enabled switch must control the page state' ); invariant( js.includes('source.rank_no') && js.includes('section.rankNo === rankNo'), 'Reward tiers must stay aligned to backend rank_no' ); invariant( html.includes('hero-art-text-free.webp') && html.includes('class="huwaa-cp-wordmark"') && html.includes('data-i18n="cp.pageTitle"'), 'The visible event title must be localized DOM text over text-free art' ); invariant( html.includes("var aliases = ['app', 'app_code', 'appCode']"), 'All app-code aliases must resolve to Huwaa before common scripts load' ); invariant( css.includes('background: #e98c9b') && css.includes('left: -487px') && css.includes('mix-blend-mode: screen') && css.includes('opacity: 0.5'), 'The Figma frame fill and petal texture blend must be reconstructed exactly' ); invariant( css.includes('#000 88.93%') && css.includes('transparent 100%'), 'The hero must preserve the Figma bottom fade mask' ); invariant( js.includes('stageWrap.style.minHeight = stageWrap.style.height'), 'Responsive stage height must override the initial minimum height' ); invariant( js.includes("activeTab === 'rewards' && !activityData.rewards.length"), 'Empty and failed Rewards states must not retain the full three-panel stage height' ); invariant( !runtimeSource.includes('lalu-coin'), 'Huwaa CP must not display a Lalu coin asset' ); invariant( !runtimeSource.includes('Namename') && !runtimeSource.includes('1234567') && !runtimeSource.includes('DDDDD'), 'Figma placeholder business data must not be baked into runtime code' ); invariant( html.includes('role="tablist"') && html.includes('role="tabpanel"'), 'CP tabs must expose accessible tab semantics' ); invariant( html.includes('aria-live="polite"'), 'Dynamic CP state must be announced' ); invariant( css.includes('prefers-reduced-motion'), 'Loading motion must respect reduced motion' ); for (const locale of locales) { for (const key of [ '"cp.countdownLabel"', '"cp.huwaaPlazaGiftTitle"', '"cp.previousWeekTopTitle"', '"cp.previousWeekTopSubtitle"', '"cp.rankPodiumLabel"', '"cp.rankPlace"', '"cp.loading"', '"cp.empty"', '"cp.error"', '"cp.disabled"', '"cp.unavailable"', '"cp.retry"', '"cp.reward"', '"cp.rewardCoins"', '"cp.rewardTierGold"', '"cp.rewardTierSilver"', '"cp.rewardTierBronze"', '"cp.daysLong"', ]) { invariant( locale.includes(key), `Required Huwaa CP translation is missing: ${key}` ); } } console.log( `activity/cp/huwaa.html asset audit passed (${manifest.assets.length} traced layers)` );