55100f8945
Memind CI / Test, build, and release guards (push) Failing after 2s
Generate SVG/PNG sidecars at HTML delivery and after share-preview repair so forwarded service-account links get a raster og:image when only meta exists. Co-authored-by: Cursor <cursoragent@cursor.com>
159 lines
5.3 KiB
JavaScript
159 lines
5.3 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { injectBeforeDocumentClosingHead } from '../../html-document-injection.mjs';
|
|
import { normalizeCoverMetaSuggestion, upsertMindspaceCoverMeta } from '../../mindspace-cover-meta.mjs';
|
|
import { preparePublishedPlatformBrand } from '../../mindspace-page-tag.mjs';
|
|
import { scheduleWorkspaceHtmlThumbnailSidecars } from '../../mindspace-workspace-thumbnails.mjs';
|
|
import {
|
|
artifactFileExists,
|
|
resolveArtifactLocalPath,
|
|
verifyPageArtifactContent,
|
|
} from './page-artifact.mjs';
|
|
import {
|
|
hasMindspaceCoverMeta,
|
|
hasPlatformBrandMarker,
|
|
hasShareDescription,
|
|
verifyArtifactSharePreview,
|
|
verifySharePreviewMeta,
|
|
} from './share-preview.mjs';
|
|
|
|
function escapeMetaAttribute(value) {
|
|
return String(value ?? '')
|
|
.replaceAll('&', '&')
|
|
.replaceAll('"', '"')
|
|
.replaceAll('<', '<');
|
|
}
|
|
|
|
export { resolveArtifactLocalPath } from './page-artifact.mjs';
|
|
|
|
export function extractPageTitle(html) {
|
|
const match = String(html ?? '').match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
return match?.[1]?.replace(/<[^>]+>/g, '').trim() ?? '';
|
|
}
|
|
|
|
export function inferCoverMeta({ title = '', topic = '' } = {}) {
|
|
const source = `${title} ${topic}`.trim();
|
|
let tag = '报告';
|
|
let emoji = '📄';
|
|
let accent = '#667eea';
|
|
let accent2 = '#764ba2';
|
|
if (/攻略|旅行|游记|半日|地图|景点|路线/.test(source)) {
|
|
tag = '旅行';
|
|
emoji = '🏞️';
|
|
accent = '#2e7d32';
|
|
accent2 = '#1565c0';
|
|
} else if (/新闻|早报|热点|资讯|日报/.test(source)) {
|
|
tag = '资讯';
|
|
emoji = '📰';
|
|
accent = '#2563eb';
|
|
accent2 = '#0f172a';
|
|
} else if (/诗|诗词|散文|文学/.test(source)) {
|
|
tag = '文艺';
|
|
emoji = '🌿';
|
|
accent = '#2d5016';
|
|
accent2 = '#1a2a3a';
|
|
}
|
|
const subtitle = (title || topic || 'MindSpace 页面').trim().slice(0, 80);
|
|
return normalizeCoverMetaSuggestion({ tag, emoji, accent, accent2, subtitle, cover: '' });
|
|
}
|
|
|
|
export function repairSharePreviewHtml(html, { title = '', topic = '' } = {}) {
|
|
let next = String(html ?? '');
|
|
const resolvedTitle = String(title || extractPageTitle(next) || '').trim();
|
|
const summary = (topic || resolvedTitle || 'MindSpace 页面').trim().slice(0, 160);
|
|
|
|
if (!hasShareDescription(next)) {
|
|
const meta = `<meta name="description" content="${escapeMetaAttribute(summary)}">`;
|
|
next = injectBeforeDocumentClosingHead(next, `${meta}\n`);
|
|
}
|
|
|
|
if (!hasMindspaceCoverMeta(next)) {
|
|
next = upsertMindspaceCoverMeta(next, inferCoverMeta({ title: resolvedTitle, topic }));
|
|
}
|
|
|
|
if (!hasPlatformBrandMarker(next)) {
|
|
next = preparePublishedPlatformBrand(next);
|
|
}
|
|
|
|
return next;
|
|
}
|
|
|
|
function syncArtifactSharePreview(artifact, localPath) {
|
|
if (!artifact || !localPath) return artifact;
|
|
try {
|
|
const content = fs.readFileSync(localPath, 'utf8');
|
|
artifact.sharePreview = verifySharePreviewMeta(content);
|
|
artifact.exists = true;
|
|
artifact.isHtmlDocument = /<(?:html|body|main|article)\b/i.test(content);
|
|
} catch {
|
|
// Keep the caller's existing artifact metadata when disk read fails.
|
|
}
|
|
return artifact;
|
|
}
|
|
|
|
export function repairArtifactSharePreview(artifact, { topic = '', publishDir = '' } = {}) {
|
|
const localPath = resolveArtifactLocalPath(artifact, publishDir);
|
|
const resolvedArtifact = localPath ? { ...artifact, localPath } : artifact;
|
|
if (!artifactFileExists(resolvedArtifact)) {
|
|
return { ok: false, reason: 'missing_file', changes: [] };
|
|
}
|
|
if (!verifyPageArtifactContent(resolvedArtifact).ok) {
|
|
return { ok: false, reason: 'not_repairable_artifact', changes: [] };
|
|
}
|
|
if (!localPath) {
|
|
return { ok: false, reason: 'missing_local_path', changes: [] };
|
|
}
|
|
|
|
const before = fs.readFileSync(localPath, 'utf8');
|
|
if (verifySharePreviewMeta(before).ok) {
|
|
syncArtifactSharePreview(artifact, localPath);
|
|
return { ok: true, reason: null, changes: [], alreadyReady: true };
|
|
}
|
|
|
|
const title = extractPageTitle(before);
|
|
const after = repairSharePreviewHtml(before, { title, topic });
|
|
if (after === before) {
|
|
return { ok: false, reason: 'repair_noop', changes: [] };
|
|
}
|
|
|
|
const changes = [];
|
|
if (!hasShareDescription(before) && hasShareDescription(after)) changes.push('description');
|
|
if (!hasMindspaceCoverMeta(before) && hasMindspaceCoverMeta(after)) changes.push('mindspace_cover');
|
|
if (!hasPlatformBrandMarker(before) && hasPlatformBrandMarker(after)) changes.push('platform_brand');
|
|
|
|
fs.writeFileSync(localPath, after, 'utf8');
|
|
syncArtifactSharePreview(artifact, localPath);
|
|
const verified = verifyArtifactSharePreview(artifact, { publishDir });
|
|
if (!verified.ok) {
|
|
return { ok: false, reason: verified.reason ?? 'repair_incomplete', changes };
|
|
}
|
|
if (publishDir && artifact?.relativePath) {
|
|
scheduleWorkspaceHtmlThumbnailSidecars(
|
|
publishDir,
|
|
String(artifact.relativePath).replace(/\\/g, '/'),
|
|
);
|
|
}
|
|
return { ok: true, reason: null, changes };
|
|
}
|
|
|
|
export function ensureArtifactsSharePreviewReady(
|
|
artifacts = [],
|
|
{ topic = '', publishDir = '' } = {},
|
|
) {
|
|
const next = [];
|
|
for (const artifact of artifacts) {
|
|
if (!artifact) continue;
|
|
if (artifact.auxiliary || artifact.isStub) {
|
|
next.push(artifact);
|
|
continue;
|
|
}
|
|
if (artifact.sharePreview?.ok === true) {
|
|
next.push(artifact);
|
|
continue;
|
|
}
|
|
repairArtifactSharePreview(artifact, { topic, publishDir });
|
|
next.push(artifact);
|
|
}
|
|
return next;
|
|
}
|