e2ad3bf62b
- 新增 public share widget 与 workspace relative path 解析 - 增强 publications/chat-plaza 发布链路与缩略图 demo - 补充 schema、cover 检查脚本与回归测试 Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
5.3 KiB
JavaScript
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);
|