/** * 灰度切换冒烟:确认 **Vue 产物经网关可达**、且这份产物确实是"10 页签"的那一份。 * * 说明(如实):本机没有浏览器自动化,jsdom 也不能执行 Vite 打出的 ESM 包, * 所以这里做的是**产物级**验证: * ① `/web/index.html` 200 且引用构建产物; * ② 该产物里含全部 10 个页签标签(取自语言包)与 10 个视图组件名; * ③ SPA 依赖的接口面(`/detail/api/fleet` 等)经网关可达。 * 浏览器内的真实交互回归不在此覆盖 —— 由 SSR 对拍(10 页签全绿)承担"呈现一致"的证明。 * * 用法:node tools/spa_smoke.mjs http://127.0.0.1:28084 */ const GATEWAY = (process.argv[2] || 'http://127.0.0.1:28084').replace(/\/$/, '') // 页签标签**以现网语言包为准**(`window.__L` 的 `tab.`)—— 不硬编码,避免拿猜测值当判据 const TAB_IDS = ['overview', 'energy', 'component', 'fault', 'vibration', 'generation', 'decision', 'assistant', 'report', 'system'] const pageHtml = await (await fetch(`${GATEWAY}/detail/v2`)).text() const langJson = pageHtml.slice(pageHtml.indexOf('window.__L'), pageHtml.indexOf('window.__L') + 400000) const TABS10 = TAB_IDS.map((id) => { const m = langJson.match(new RegExp(`"tab\\.${id}":\\s*"([^"]+)"`)) return m ? m[1] : id }) const COMPONENTS = ['Overview', 'Energy', 'Component', 'Fault', 'Vibration', 'Generation', 'Decision', 'Assistant', 'Report', 'System'] let bad = 0 const ok = (cond, msg, extra = '') => { console.log(`${cond ? '[OK]' : '[X] '} ${msg}${extra ? ' —— ' + extra : ''}`) if (!cond) bad++ } // ① index.html const idx = await fetch(`${GATEWAY}/web/index.html`) ok(idx.ok, `/web/index.html 可达`, `HTTP ${idx.status}`) const html = await idx.text() const srcs = [...html.matchAll(/]+src="([^"]+)"/g)].map((m) => m[1]) const links = [...html.matchAll(/]+href="([^"]+)"/g)].map((m) => m[1]) ok(srcs.length > 0, 'index.html 引用了脚本', srcs.join(', ').slice(0, 120)) // ② 产物内容:10 个页签 + 10 个视图 let bundleText = '' for (const s of srcs) { const u = s.startsWith('http') ? s : `${GATEWAY}/web/${s.replace(/^\.?\//, '')}` const r = await fetch(u) ok(r.ok, `脚本可达 ${s.slice(0, 48)}`, `HTTP ${r.status}`) if (r.ok) bundleText += await r.text() } for (const l of links) { const u = l.startsWith('http') ? l : `${GATEWAY}/web/${l.replace(/^\.?\//, '')}` const r = await fetch(u) ok(r.ok, `样式可达 ${l.slice(0, 40)}`, `HTTP ${r.status}`) } const missingTabs = TABS10.filter((x) => !bundleText.includes(x)) ok(missingTabs.length === 0, '产物含全部 10 个页签标签(取自语言包)', missingTabs.length ? '缺: ' + missingTabs.join('/') : `${TABS10.length}/10 · ${TABS10.join('/')}`) const missingCmp = COMPONENTS.filter((x) => !bundleText.includes(x)) ok(missingCmp.length === 0, '产物含全部 10 个视图组件', missingCmp.length ? '缺: ' + missingCmp.join('/') : `${COMPONENTS.length}/10`) // ③ SPA 依赖的接口面 for (const p of ['/detail/api/fleet', '/algorithm/healthz']) { const r = await fetch(`${GATEWAY}${p}`) ok(r.ok, `接口可达 ${p}`, `HTTP ${r.status}`) } console.log(`\n结论: ${bad === 0 ? 'Vue 产物经网关可作并列入口(灰度第一步就绪)' : bad + ' 项未通过'}`) process.exit(bad === 0 ? 0 : 1)