Files
memind/scripts/check-mindspace-cover.mjs
T
john e2ad3bf62b feat(mindspace): 公开页分享组件、workspace 路径与 Plaza 发布增强
- 新增 public share widget 与 workspace relative path 解析
- 增强 publications/chat-plaza 发布链路与缩略图 demo
- 补充 schema、cover 检查脚本与回归测试

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 23:14:41 +08:00

152 lines
5.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Scan MindSpace public/*.html for mindspace-cover compliance (scheme A gate).
*
* Usage:
* node scripts/check-mindspace-cover.mjs
* node scripts/check-mindspace-cover.mjs --user <uuid>
* node scripts/check-mindspace-cover.mjs --root /path/to/MindSpace
* node scripts/check-mindspace-cover.mjs --file public/thumbnail-demo-samples/vietnam-guide-good.html
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parseMindspaceCoverMeta } from '../mindspace-cover-meta.mjs';
import { hasPlatformBrandMarker } from '../mindspace-page-tag.mjs';
import {
hasMindspaceCoverMeta,
hasRasterShareImageHint,
hasShareDescription,
} from '../wechat/verify/share-preview.mjs';
const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const DEFAULT_PUBLISH_ROOT = 'MindSpace';
function parseArgs(argv) {
let root = path.join(repoRoot, DEFAULT_PUBLISH_ROOT);
let userId = null;
let file = null;
for (let i = 2; i < argv.length; i += 1) {
if (argv[i] === '--root' && argv[i + 1]) {
root = path.resolve(argv[i + 1]);
i += 1;
} else if (argv[i] === '--user' && argv[i + 1]) {
userId = argv[i + 1];
i += 1;
} else if (argv[i] === '--file' && argv[i + 1]) {
file = path.resolve(argv[i + 1]);
i += 1;
} else if (argv[i] === '--help' || argv[i] === '-h') {
console.log(`Usage: node scripts/check-mindspace-cover.mjs [--root ${DEFAULT_PUBLISH_ROOT}] [--user <uuid>] [--file <html>]`);
process.exit(0);
}
}
return { root, userId, file };
}
function hasCoverImageHint(html) {
if (hasRasterShareImageHint(html)) return true;
const coverMeta = parseMindspaceCoverMeta(html);
const cover = String(coverMeta?.cover ?? coverMeta?.image ?? '').trim();
if (!cover) return false;
if (/^https?:\/\//i.test(cover)) return true;
if (/\.svg(?:[?#]|$)/i.test(cover)) return false;
return true;
}
function auditCoverHtml(htmlPath, html) {
const issues = [];
if (!hasMindspaceCoverMeta(html)) {
issues.push({ level: 'error', code: 'missing_mindspace_cover', message: '缺少 <meta name="mindspace-cover">' });
}
if (!hasShareDescription(html)) {
issues.push({ level: 'error', code: 'missing_description', message: '缺少 description / og:description' });
}
if (!hasPlatformBrandMarker(html)) {
issues.push({ level: 'warn', code: 'missing_platform_brand', message: '缺少 data-mindspace-page-tag="platform-brand" 页脚' });
}
const coverMeta = parseMindspaceCoverMeta(html);
if (coverMeta) {
if (!String(coverMeta.tag ?? '').trim()) {
issues.push({ level: 'warn', code: 'missing_tag', message: 'mindspace-cover 缺少 tag' });
}
if (!String(coverMeta.subtitle ?? '').trim()) {
issues.push({ level: 'warn', code: 'missing_subtitle', message: 'mindspace-cover 缺少 subtitle' });
}
if (!String(coverMeta.accent ?? '').trim()) {
issues.push({ level: 'warn', code: 'missing_accent', message: 'mindspace-cover 缺少 accent' });
}
}
if (!hasCoverImageHint(html)) {
issues.push({
level: 'warn',
code: 'missing_cover_image',
message: '无 raster 封面图(cover/image/og:image),缩略图会退化为纯色渐变',
});
}
return issues;
}
function collectHtmlFiles(root, userId) {
const files = [];
const walk = (dir) => {
if (!fs.existsSync(dir)) return;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === '.agents' || entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
walk(full);
continue;
}
if (!entry.name.endsWith('.html') || entry.name.endsWith('.thumbnail.svg')) continue;
files.push(full);
}
};
if (userId) {
walk(path.join(root, userId, 'public'));
return files;
}
if (!fs.existsSync(root)) return files;
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
if (!entry.isDirectory() || entry.name === 'wiki' || entry.name.startsWith('.')) continue;
walk(path.join(root, entry.name, 'public'));
}
return files;
}
const { root, userId, file } = parseArgs(process.argv);
const targets = file ? [file] : collectHtmlFiles(root, userId);
const results = [];
for (const htmlPath of targets) {
const html = fs.readFileSync(htmlPath, 'utf8');
const issues = auditCoverHtml(htmlPath, html);
if (issues.length) {
results.push({ htmlPath, issues });
}
}
if (results.length === 0) {
const scope = file ?? `${root}${userId ? ` (user ${userId})` : ''}`;
console.log(`OK: ${targets.length} 个 HTML 页面均符合 mindspace-cover 规范 (${scope})`);
process.exit(0);
}
let errors = 0;
let warns = 0;
console.error(`Found cover issues in ${results.length} / ${targets.length} HTML files:`);
for (const { htmlPath, issues } of results) {
const rel = file ? htmlPath : path.relative(root, htmlPath);
console.error(`\n${rel}`);
for (const issue of issues) {
const prefix = issue.level === 'error' ? ' ✗' : ' ⚠';
console.error(`${prefix} [${issue.code}] ${issue.message}`);
if (issue.level === 'error') errors += 1;
else warns += 1;
}
}
console.error(`\n合计: ${errors} 错误, ${warns} 警告`);
process.exit(errors > 0 ? 1 : 0);