import fs from 'node:fs'; import { artifactFileExists } from './page-artifact.mjs'; import { hasPlatformBrandMarker } from '../../mindspace-page-tag.mjs'; export { hasPlatformBrandMarker }; const RASTER_IMAGE_PATTERN = /\.(png|jpe?g|webp|gif)(?:[?#]|$)/i; export function hasMindspaceCoverMeta(html) { return /]*name=["']mindspace-cover["']/i.test(String(html ?? '')); } export function hasShareDescription(html) { const source = String(html ?? ''); return ( /]+property=["']og:description["']/i.test(source) || /]+name=["']description["']/i.test(source) ); } export function hasRasterShareImageHint(html) { const source = String(html ?? ''); if (/]+property=["']og:image["'][^>]+content=["'][^"']+/i.test(source)) { const match = source.match(/]+property=["']og:image["'][^>]+content=["']([^"']+)["']/i) ?? source.match(/]+content=["']([^"']+)["'][^>]+property=["']og:image["']/i); const url = match?.[1] ?? ''; if (url && !/\.svg(?:[?#]|$)/i.test(url)) return true; } const coverMatch = source.match(/]*name=["']mindspace-cover["'][^>]*content=(["'])([\s\S]*?)\1/i); if (!coverMatch?.[2]) return false; try { const meta = JSON.parse(coverMatch[2].replaceAll('"', '"')); const cover = String(meta.cover ?? meta.image ?? '').trim(); if (!cover) return false; if (/^https?:\/\//i.test(cover)) return RASTER_IMAGE_PATTERN.test(cover); return RASTER_IMAGE_PATTERN.test(cover) || !/\.svg(?:[?#]|$)/i.test(cover); } catch { return false; } } /** * WeChat link cards need mindspace-cover (for thumbnail.png) + description. * Visual pages should also reference a raster hero in cover/image when possible. */ export function verifySharePreviewMeta(html) { if (!hasMindspaceCoverMeta(html)) { return { ok: false, reason: 'missing_mindspace_cover' }; } if (!hasShareDescription(html)) { return { ok: false, reason: 'missing_description' }; } if (!hasPlatformBrandMarker(html)) { return { ok: false, reason: 'missing_platform_brand' }; } return { ok: true, reason: null }; } export function verifyArtifactSharePreview(artifact) { if (!artifactFileExists(artifact)) { return { ok: false, reason: 'missing_file' }; } const content = fs.readFileSync(artifact.localPath, 'utf8'); return verifySharePreviewMeta(content); } export function filterSharePreviewReadyArtifacts(artifacts = []) { return artifacts.filter((artifact) => verifyArtifactSharePreview(artifact).ok); }