Regression / regression (push) Canceled after 0s
本提交含两条并行工作线,因互相咬合(package.json scripts、regression.yml、 npm run build 链)无法按文件干净拆分,故合并为一次自洽提交。 ## 详情页减重(本轮主任务:修复「AI 干太重」) - 导航收敛:宽屏只用右侧目录、≤1200px 只用页内 sticky 导航,纯 CSS 媒体查询 实现(不引入 JS 宽度监听)。此前两套导航同时可见,active 状态互相打架。 - 入口去重:标题区由「查看示例/在线测试/契约 JSON」三个减为「在线测试」一个; 契约 JSON 归入实现资源;删除示例底部重复的「在线测试」。 - 示例工具栏:删除与目录锚点重复的示例下拉选择器;「全部展开代码」只在 示例数 >1 时出现(103 个组件里 49 个仅 1 个示例,此前恒显示)。 - 重复文案:示例区两句同义导语合并为一句。 - 首页 CTA 由 5 个减为 2 个(浏览组件/快速开始),测试总览入口挂到已有的 通过率统计卡上,不再另占 Hero 按钮。 - 统一详情取数:抽出 fetchDetail/loadDetail 作为 details/*.json 的唯一路径, FAQ 不再自行 fetch 一遍,与组件页共用缓存与失败兜底。 ## i18n - 删除 12 组重复键(含整段 FAQ 说明),字典 604 → 585 唯一键。 - 删除本次改动产生的 6 个死键。 - verify-i18n.mjs 新增 `unique dictionary keys` 断言:重复键在对象字面量里 是静默的后值覆盖,此前无从发现;现由门禁拦住。 ## 文档事实修正(实测为准) - TESTING/CONTRIBUTING:79 → 103 组件;旧断言数改为回指 tests/report.json。 - PLATFORMS:移动端 108 文件/18 端 → 282 文件/47 端;契约 5 → 47; 令牌 17 → 15;uni-app SFC 21 → 50。 - AGENTS:断言 1405/18 页 → 1464/103 页(PC)、807/47 页(移动端)。 - package.json:YOUR-ACCOUNT 占位 → gitea 实址与 kole-ui.mymoyu.top。 ## 品牌标识(并行会话成果,一并入库) - brand-mark.json 收归真源,build:brand 生成 favicon 与单色 SVG; PC 与移动端共用资产,verify:brand 24 条断言。 - regression.yml 增加 verify:brand 步骤。 ## 门禁与验收 新增工具:verify-component-page.mjs(86 条真实浏览器断言,随详情页改造同步 更新为「只允许一套导航可见」)、verify-brand-mark.mjs、lib/i18n-dead-keys.mjs (只读诊断)。 回归:PC 100%(1464/1464,103 页,N/A 50)· 移动端 100%(807/807,47 页)。 门禁:component-page 86 · i18n 17 · brand 24 · routes all · smoke all · theme OK · isolation 31 · nav all · examples 9 · api-docs 10 · mobile-docs 12。 已知未做:i18n 另有约 44 条历史死键(非本次产生),已记为 ROADMAP S8-P6; 顶栏与悬浮区的两个主题入口为刻意设计(verify-theme 断言其互斥),未删。
117 lines
6.5 KiB
JavaScript
117 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* verify-brand-mark.mjs — 项目品牌图标静态门禁。
|
||
* 组件 Icon registry 是另一套系统,本脚本只检查 favicon 与文档站品牌标记。
|
||
*/
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { collectPublishSet } from './lib/publish-set.mjs';
|
||
import {
|
||
generatedData,
|
||
readBrandSpec,
|
||
renderFaviconSvg,
|
||
renderMonoSvg,
|
||
validateBrandSpec,
|
||
BRAND_ASSET_REL,
|
||
BRAND_DATA_REL,
|
||
BRAND_MONO_REL,
|
||
BRAND_SPEC_REL,
|
||
} from './lib/brand-mark.mjs';
|
||
|
||
const ROOT = process.cwd();
|
||
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8').replace(/^\uFEFF/, '');
|
||
const exists = (rel) => fs.existsSync(path.join(ROOT, rel));
|
||
let pass = 0;
|
||
const failures = [];
|
||
function check(ok, label, detail = '') {
|
||
if (ok) {
|
||
pass++;
|
||
console.log(` PASS ${label}${detail ? ` — ${detail}` : ''}`);
|
||
} else {
|
||
failures.push(`${label}${detail ? ` — ${detail}` : ''}`);
|
||
console.log(` FAIL ${label}${detail ? ` — ${detail}` : ''}`);
|
||
}
|
||
}
|
||
|
||
console.log('Kole UI 项目品牌图标门禁');
|
||
check(exists(BRAND_SPEC_REL), 'B1 品牌规格存在', BRAND_SPEC_REL);
|
||
check(exists(BRAND_ASSET_REL), 'B2 favicon SVG 存在', BRAND_ASSET_REL);
|
||
check(exists(BRAND_MONO_REL), 'B3 单色 SVG 存在', BRAND_MONO_REL);
|
||
check(exists(BRAND_DATA_REL), 'B4 生成数据存在', BRAND_DATA_REL);
|
||
|
||
const spec = readBrandSpec(ROOT);
|
||
const specErrors = validateBrandSpec(spec);
|
||
check(specErrors.length === 0, 'B5 品牌规格结构有效', specErrors.join(';'));
|
||
|
||
const expectedFavicon = renderFaviconSvg(spec) + '\n';
|
||
const expectedMono = renderMonoSvg(spec) + '\n';
|
||
const expectedData = JSON.stringify(generatedData(spec), null, 2) + '\n';
|
||
const favicon = exists(BRAND_ASSET_REL) ? read(BRAND_ASSET_REL) : '';
|
||
const mono = exists(BRAND_MONO_REL) ? read(BRAND_MONO_REL) : '';
|
||
const data = exists(BRAND_DATA_REL) ? read(BRAND_DATA_REL) : '';
|
||
check(favicon === expectedFavicon, 'B6 favicon 是规格生成物');
|
||
check(mono === expectedMono, 'B7 单色标记是规格生成物');
|
||
check(data === expectedData, 'B8 generated JSON 是规格生成物');
|
||
check(!/<image\b|(?:href|src)=["']https?:\/\//i.test(favicon + mono), 'B9 品牌 SVG 不含外部资源');
|
||
check((favicon.match(/<path\b/g) || []).length === spec.geometry.paths.length, 'B10 favicon 完整包含全部几何路径', `${spec.geometry.paths.length} 条`);
|
||
check((mono.match(/<path\b/g) || []).length === spec.geometry.paths.length, 'B11 单色标记完整包含全部几何路径', `${spec.geometry.paths.length} 条`);
|
||
const faviconPaint = spec.geometry.mode === 'fill' ? `fill="${spec.favicon.foreground}"` : `stroke="${spec.favicon.foreground}"`;
|
||
check(favicon.includes(`fill="${spec.favicon.background}"`) && favicon.includes(faviconPaint), 'B12 favicon 颜色固定');
|
||
const monoPaint = spec.geometry.mode === 'fill' ? 'fill="#000000"' : 'stroke="#000000"';
|
||
check(mono.includes(monoPaint) && !mono.includes(`fill="${spec.favicon.background}"`), 'B13 单色标记透明背景');
|
||
|
||
const pc = read('site/index.html');
|
||
const mobileBuilder = read('tools/build-mobile.mjs');
|
||
const mobileStyle = read('site/m/style.css');
|
||
check(pc.includes('href="assets/kole-mark.svg"'), 'B14 PC 使用 favicon 资产');
|
||
check(pc.includes('class="logo-mark"') && pc.includes('data-brand-mark="mono"'), 'B15 PC 顶栏使用单色品牌标记');
|
||
check(!pc.includes('M7 5.5V18.5'), 'B16 PC 不再内联旧几何路径');
|
||
check(mobileBuilder.includes("readBrandSpec(ROOT)"), 'B17 移动端构建读取品牌规格');
|
||
check(!mobileBuilder.includes('const BRAND_PATHS ='), 'B18 移动端构建不重复维护几何路径');
|
||
check(mobileStyle.includes('data-brand-mark="mono"') || mobileStyle.includes('kole-mark-mono.svg'), 'B19 移动端样式声明单色品牌资产');
|
||
check((pc.match(/theme-color/g) || []).length >= 2, 'B20 PC theme-color 保留亮暗两态');
|
||
|
||
const mobileIndex = JSON.parse(read('.design_library/kole-ui-mobile/components/index.json'));
|
||
const mobilePages = [
|
||
...['index', 'guide', 'design', 'faq', 'changelog', 'platform'].map((name) => `site/m/${name}.html`),
|
||
...mobileIndex.components.map((component) => `site/m/component/${component.slug}.html`),
|
||
];
|
||
const pageProblems = [];
|
||
for (const rel of mobilePages) {
|
||
if (!exists(rel)) { pageProblems.push(`${rel}: 文件缺失`); continue; }
|
||
const html = read(rel);
|
||
const head = (html.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i) || [])[1] || '';
|
||
const icons = [...head.matchAll(/<link\b[^>]*rel="icon"[^>]*>/g)];
|
||
const href = icons.length === 1 ? (icons[0][0].match(/\bhref="([^"]+)"/) || [])[1] : '';
|
||
if (!href || path.posix.normalize(path.posix.join(path.posix.dirname(rel), href)) !== BRAND_ASSET_REL) {
|
||
pageProblems.push(`${rel}: favicon 未指向统一资产`);
|
||
}
|
||
const header = (html.match(/<header class="m-top">([\s\S]*?)<\/header>/) || [])[1] || '';
|
||
if (!header.includes('<span class="m-logo-mark" data-brand-mark="mono" aria-hidden="true"></span>')) {
|
||
pageProblems.push(`${rel}: 顶栏标记或装饰性语义缺失`);
|
||
}
|
||
if (header.includes('M7 5.5V18.5') || (head.match(/name="theme-color"/g) || []).length !== 2) {
|
||
pageProblems.push(`${rel}: 旧几何残留或 theme-color 缺失`);
|
||
}
|
||
}
|
||
check(pageProblems.length === 0, 'B21 移动端全部生成页使用统一品牌资产', pageProblems.slice(0, 5).join(';') || `${mobilePages.length} 页`);
|
||
const pcStyle = read('site/style.css');
|
||
const pcMask = (pcStyle.match(/\.logo-mark\[data-brand-mark="mono"\]::before\s*\{([^}]+)\}/) || [])[1] || '';
|
||
const mobileMask = (mobileStyle.match(/\.m-logo-mark\[data-brand-mark="mono"\]::before\s*\{([^}]+)\}/) || [])[1] || '';
|
||
check(pcMask.includes('background: currentColor') && pcMask.includes("url('assets/kole-mark-mono.svg')")
|
||
&& mobileMask.includes('background: currentColor') && mobileMask.includes("url('../assets/kole-mark-mono.svg')"),
|
||
'B22 两端 mask 路径同源且继承 currentColor');
|
||
const published = new Set(collectPublishSet(ROOT).kept);
|
||
check([BRAND_ASSET_REL, BRAND_MONO_REL, BRAND_DATA_REL].every((rel) => published.has(rel)), 'B23 发布集包含全部品牌产物');
|
||
const scripts = JSON.parse(read('package.json')).scripts;
|
||
check(scripts['build:brand'] === 'node tools/build-brand-mark.mjs'
|
||
&& scripts['verify:brand'] === 'node tools/verify-brand-mark.mjs'
|
||
&& scripts.build.startsWith('node tools/build-brand-mark.mjs && '), 'B24 品牌构建与门禁入口完整');
|
||
|
||
if (failures.length) {
|
||
console.error(`\n[FAIL] ${failures.length} 条品牌图标断言失败(通过 ${pass} 条)`);
|
||
failures.forEach((failure) => console.error(' - ' + failure));
|
||
process.exit(1);
|
||
}
|
||
console.log(`\n[OK] 品牌图标门禁全部通过(${pass} 条断言)`);
|