fix: recover WeChat page thumbnail delivery
Memind CI / Test, build, and release guards (pull_request) Successful in 5m1s
Memind CI / Test, build, and release guards (pull_request) Successful in 5m1s
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
parseMindspaceCoverMeta,
|
||||
upsertMindspaceCoverMeta,
|
||||
} from '../../mindspace-cover-meta.mjs';
|
||||
|
||||
const RASTER_MIME_PATTERN = /^image\/(?:png|jpeg|webp)$/i;
|
||||
|
||||
@@ -29,6 +34,7 @@ function normalizeGeneratedImage(result) {
|
||||
if (!htmlSrc && !publicUrl && !workspaceRelativePath) return null;
|
||||
return {
|
||||
jobId: String(result.jobId),
|
||||
purpose: String(result.purpose ?? '').trim() || null,
|
||||
mimeType,
|
||||
assetId: String(asset.id ?? '').trim() || null,
|
||||
htmlSrc: htmlSrc || null,
|
||||
@@ -141,6 +147,7 @@ export function verifyFreshWechatPageThumbnails(artifacts = [], images = []) {
|
||||
ok: false,
|
||||
reason: cover ? 'cover_not_from_current_run' : 'missing_generated_cover',
|
||||
artifact,
|
||||
cover: cover || null,
|
||||
matches,
|
||||
skippedArtifacts,
|
||||
};
|
||||
@@ -150,3 +157,103 @@ export function verifyFreshWechatPageThumbnails(artifacts = [], images = []) {
|
||||
}
|
||||
return { ok: true, reason: null, matches, skippedArtifacts };
|
||||
}
|
||||
|
||||
function uniqueArtifactsByLocalPath(artifacts = []) {
|
||||
const unique = new Map();
|
||||
for (const artifact of artifacts) {
|
||||
const localPath = String(artifact?.localPath ?? '').trim();
|
||||
if (localPath) unique.set(localPath, artifact);
|
||||
}
|
||||
return [...unique.values()];
|
||||
}
|
||||
|
||||
function atomicWriteHtml(localPath, content) {
|
||||
const tempPath = path.join(
|
||||
path.dirname(localPath),
|
||||
`.${path.basename(localPath)}.wechat-thumbnail-${process.pid}-${crypto.randomUUID()}.tmp`,
|
||||
);
|
||||
try {
|
||||
fs.writeFileSync(tempPath, content, 'utf8');
|
||||
fs.renameSync(tempPath, localPath);
|
||||
} finally {
|
||||
try {
|
||||
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
|
||||
} catch {
|
||||
// Best-effort cleanup only; the destination write already decided the result.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function repairUnambiguousFreshWechatPageThumbnail({
|
||||
artifacts = [],
|
||||
images = [],
|
||||
currentRunHtmlArtifacts = [],
|
||||
verificationReason = '',
|
||||
} = {}) {
|
||||
if (!['missing_generated_cover', 'cover_not_from_current_run'].includes(verificationReason)) {
|
||||
return { ok: false, reason: 'verification_not_repairable' };
|
||||
}
|
||||
const eligibleArtifacts = uniqueArtifactsByLocalPath(
|
||||
artifacts.filter((artifact) => !isWechatAuxiliaryPageArtifact(artifact)),
|
||||
);
|
||||
const pageImages = images.filter((image) => image?.purpose === 'hero' || !image?.purpose);
|
||||
if (eligibleArtifacts.length !== 1 || pageImages.length !== 1) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: 'ambiguous_page_image_mapping',
|
||||
artifactCount: eligibleArtifacts.length,
|
||||
imageCount: pageImages.length,
|
||||
};
|
||||
}
|
||||
|
||||
const artifact = eligibleArtifacts[0];
|
||||
const image = pageImages[0];
|
||||
const cover = String(image?.htmlSrc ?? '').trim();
|
||||
const localPath = String(artifact?.localPath ?? '').trim();
|
||||
if (!cover || !localPath || !fs.existsSync(localPath)) {
|
||||
return { ok: false, reason: 'repair_source_unavailable', artifact, image };
|
||||
}
|
||||
|
||||
const normalizedRelativePath = String(artifact.relativePath ?? '').trim().replace(/\\/g, '/');
|
||||
const currentRunArtifact = currentRunHtmlArtifacts.find(
|
||||
(candidate) => String(candidate?.relativePath ?? '').trim().replace(/\\/g, '/') === normalizedRelativePath,
|
||||
);
|
||||
const sourceHtml = typeof currentRunArtifact?.content === 'string'
|
||||
? currentRunArtifact.content
|
||||
: fs.readFileSync(localPath, 'utf8');
|
||||
const coverMeta = parseMindspaceCoverMeta(sourceHtml);
|
||||
if (!coverMeta) {
|
||||
return { ok: false, reason: 'valid_cover_meta_required', artifact, image };
|
||||
}
|
||||
|
||||
const repairedHtml = upsertMindspaceCoverMeta(sourceHtml, { cover });
|
||||
atomicWriteHtml(localPath, repairedHtml);
|
||||
return {
|
||||
ok: true,
|
||||
reason: null,
|
||||
artifact,
|
||||
image,
|
||||
cover,
|
||||
restoredCurrentRunHtml: Boolean(currentRunArtifact),
|
||||
};
|
||||
}
|
||||
|
||||
export function summarizeFreshWechatThumbnailVerification(verification, images = []) {
|
||||
return {
|
||||
reason: String(verification?.reason ?? 'unknown'),
|
||||
artifact: verification?.artifact
|
||||
? {
|
||||
relativePath: String(verification.artifact.relativePath ?? ''),
|
||||
localPath: path.basename(String(verification.artifact.localPath ?? '')),
|
||||
}
|
||||
: null,
|
||||
cover: String(verification?.cover ?? ''),
|
||||
matches: Array.isArray(verification?.matches) ? verification.matches.length : 0,
|
||||
images: images.map((image) => ({
|
||||
jobId: String(image?.jobId ?? ''),
|
||||
purpose: String(image?.purpose ?? ''),
|
||||
htmlSrc: String(image?.htmlSrc ?? ''),
|
||||
workspaceRelativePath: String(image?.workspaceRelativePath ?? ''),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user