feat(C-05):SEO预渲染+依赖瘦身+i18n补齐
build链追加seo预渲染(19工具页静态HTML+sitemap+robots)与seo:check验收;剔除chakra/emotion/@types/qrcode死依赖(pnpm-lock同步,手写qrcode.d.ts顶上);6语言locales补齐新功能文案。附带vite/nginx代理段与C-01同口径微调。e01-visual.spec.ts仅归档不跑(需8756/5173真机)。
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env node
|
||||
/* C-05 SEO 验收:扫描 dist 校验 title/description/sitemap 覆盖。
|
||||
* 用法:node scripts/check_seo.mjs [--dist dist]
|
||||
* 退出码:0 全绿;1 有失败(打印明细)。
|
||||
*/
|
||||
import { readFileSync, existsSync, readdirSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const distIdx = args.indexOf('--dist')
|
||||
const DIST = resolve(distIdx >= 0 && args[distIdx + 1] ? args[distIdx + 1] : 'dist')
|
||||
|
||||
const EXPECT_TOOLS = [
|
||||
'/baidu-translate', '/utility/json-formatter', '/utility/timestamp', '/utility/base64',
|
||||
'/utility/encoding-converter', '/utility/regex', '/utility/code-formatter', '/utility/color-converter',
|
||||
'/qrcode-generator', '/utility/text-diff', '/utility/calculator', '/utility/date-calculator',
|
||||
'/utility/image-compressor', '/color-picker', '/utility/charset-converter', '/utility/image-editor',
|
||||
]
|
||||
const EXPECT_PAGES = ['/', '/utility', '/top']
|
||||
|
||||
let fail = 0
|
||||
const ok = (cond, msg) => {
|
||||
console.log(`${cond ? 'PASS' : 'FAIL'} ${msg}`)
|
||||
if (!cond) fail++
|
||||
}
|
||||
|
||||
const titleOf = (html) => (html.match(/<title>([\s\S]*?)<\/title>/) || [])[1] || ''
|
||||
const descOf = (html) => (html.match(/<meta name="description" content="([\s\S]*?)"\s*\/?>/) || [])[1] || ''
|
||||
|
||||
// 1. 工具页 HTML 含正文 + 唯一 title/description
|
||||
const titles = new Set()
|
||||
for (const u of EXPECT_TOOLS) {
|
||||
const f = join(DIST, u.replace(/^\//, ''), 'index.html')
|
||||
ok(existsSync(f), `${u} -> dist 产物存在`)
|
||||
if (!existsSync(f)) continue
|
||||
const html = readFileSync(f, 'utf-8')
|
||||
const t = titleOf(html), d = descOf(html)
|
||||
ok(t.length > 0, `${u} title 非空(${t.slice(0, 24)}…)`)
|
||||
ok(!titles.has(t), `${u} title 唯一`)
|
||||
titles.add(t)
|
||||
ok(d.length > 0, `${u} description 非空`)
|
||||
ok(html.includes('<h1>') && html.includes('免费在线工具'), `${u} 含静态正文(h1+说明,非空#root)`)
|
||||
ok(html.includes('og:title') && html.includes('og:description'), `${u} OG 标签齐全`)
|
||||
}
|
||||
|
||||
// 2. 聚合页
|
||||
for (const u of EXPECT_PAGES) {
|
||||
const f = u === '/' ? join(DIST, 'index.seo.html') : join(DIST, u.replace(/^\//, ''), 'index.html')
|
||||
ok(existsSync(f), `${u} -> 聚合产物存在`)
|
||||
if (!existsSync(f)) continue
|
||||
const html = readFileSync(f, 'utf-8')
|
||||
ok(titleOf(html).length > 0 && descOf(html).length > 0, `${u} title/description 非空`)
|
||||
}
|
||||
|
||||
// 3. sitemap/robots
|
||||
const sm = join(DIST, 'sitemap.xml')
|
||||
ok(existsSync(sm), 'sitemap.xml 存在')
|
||||
if (existsSync(sm)) {
|
||||
const body = readFileSync(sm, 'utf-8')
|
||||
const missing = [...EXPECT_TOOLS, ...EXPECT_PAGES].filter((u) => !body.includes(`<loc>`) || !body.includes(u))
|
||||
ok(missing.length === 0, `sitemap 覆盖 ${EXPECT_TOOLS.length + EXPECT_PAGES.length} URL${missing.length ? '(缺:' + missing.join(',') + ')' : ''}`)
|
||||
}
|
||||
const robots = join(DIST, 'robots.txt')
|
||||
ok(existsSync(robots), 'robots.txt 存在')
|
||||
if (existsSync(robots)) {
|
||||
const body = readFileSync(robots, 'utf-8')
|
||||
ok(body.includes('Sitemap:'), 'robots 指向 sitemap')
|
||||
ok(body.includes('Disallow: /profile') && body.includes('Disallow: /wallet/'), 'robots 屏蔽登录态页')
|
||||
}
|
||||
|
||||
// 4. SPA 外壳未被破坏
|
||||
const shell = join(DIST, 'index.html')
|
||||
ok(existsSync(shell) && readFileSync(shell, 'utf-8').includes('id="root"'), 'SPA 外壳 index.html 完好(含 #root)')
|
||||
|
||||
// 5. 种子对齐:TOOLS 数量 == 后端启用工具数(硬编码 16,漂移即报)
|
||||
ok(EXPECT_TOOLS.length === 16, `工具清单 16 条(与 seed_tools 启用数对齐)`)
|
||||
|
||||
console.log(fail === 0 ? `\nSEO CHECK ALL GREEN (${EXPECT_TOOLS.length} tools + ${EXPECT_PAGES.length} pages)` : `\nSEO CHECK FAILED: ${fail} 项`)
|
||||
process.exit(fail === 0 ? 0 : 1)
|
||||
Reference in New Issue
Block a user