#!/usr/bin/env node /** * Scan HTML pages for mindspace-cover + SEO/GEO compliance. * * Usage: * node scripts/check-mindspace-cover.mjs * node scripts/check-mindspace-cover.mjs --scope all * node scripts/check-mindspace-cover.mjs --user * 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 { auditHtmlFile, collectHtmlTargets, } from '../mindspace-seo-audit.mjs'; const repoRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); const DEFAULT_PUBLISH_ROOT = 'MindSpace'; function parseArgs(argv) { let scope = 'all'; let root = path.join(repoRoot, DEFAULT_PUBLISH_ROOT); let publicRoot = path.join(repoRoot, 'public'); let storageRoot = null; let userId = null; let file = null; let seo = true; for (let i = 2; i < argv.length; i += 1) { if (argv[i] === '--scope' && argv[i + 1]) { scope = argv[i + 1]; i += 1; } else if (argv[i] === '--root' && argv[i + 1]) { root = path.resolve(argv[i + 1]); i += 1; } else if (argv[i] === '--public-root' && argv[i + 1]) { publicRoot = path.resolve(argv[i + 1]); i += 1; } else if (argv[i] === '--storage-root' && argv[i + 1]) { storageRoot = 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] === '--seo') { seo = true; } else if (argv[i] === '--skip-seo') { seo = false; } else if (argv[i] === '--help' || argv[i] === '-h') { console.log(`Usage: node scripts/check-mindspace-cover.mjs [--scope mindspace|platform|all] [--root ${DEFAULT_PUBLISH_ROOT}] [--user ] [--file ] [--skip-seo]`); process.exit(0); } } return { scope, root, publicRoot, storageRoot, userId, file, seo }; } function isPlatformOnlyPath(htmlPath, publicRoot) { const resolvedPublicRoot = path.resolve(publicRoot); const resolved = path.resolve(htmlPath); return resolved.startsWith(`${resolvedPublicRoot}${path.sep}`); } const { scope, root, publicRoot, storageRoot, userId, file, seo } = parseArgs(process.argv); const targets = collectHtmlTargets({ scope, mindspaceRoot: root, publicRoot, storageRoot, userId, file, }); const results = []; for (const htmlPath of targets) { const html = fs.readFileSync(htmlPath, 'utf8'); const platformOnly = isPlatformOnlyPath(htmlPath, publicRoot); const issues = auditHtmlFile(htmlPath, html, { seo, requireCoverMeta: !platformOnly, }); if (issues.length) { results.push({ htmlPath, issues }); } } if (results.length === 0) { const scopeLabel = file ?? `${scope} @ ${root}${userId ? ` (user ${userId})` : ''}`; console.log(`OK: ${targets.length} 个 HTML 页面均符合 mindspace-cover + SEO/GEO 规范 (${scopeLabel})`); process.exit(0); } let errors = 0; let warns = 0; console.error(`Found cover/SEO issues in ${results.length} / ${targets.length} HTML files:`); for (const { htmlPath, issues } of results) { const rel = file ? htmlPath : path.relative(repoRoot, 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);