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 断言其互斥),未删。
164 lines
9.5 KiB
JavaScript
164 lines
9.5 KiB
JavaScript
/* Kole UI —— 部署打包器(.dockerignore 是唯一真源)
|
||
*
|
||
* 背景(实测事故):
|
||
* 远端 /opt/kole-ui 之前靠手工拷贝维护,没有 git、也没有 .dockerignore,
|
||
* 于是 ① .design_library/kole-ui/specs 等目录随镜像发布并对外可下载;
|
||
* ② 曾经修好的 nginx.conf(absolute_redirect off)被下一次拷贝覆盖丢失。
|
||
* 本脚本把「该发什么、不该发什么」固化为可复现的一步:
|
||
* - 文件集只包含 docker build 真正需要的顶层条目;
|
||
* - 排除规则**解析 .dockerignore**(含 Docker 的匹配语义),不再维护第二份清单;
|
||
* - 硬断言:规范原文/agent-reports/preview/ui_kits 绝不出现,站点运行必需文件必须出现。
|
||
*
|
||
* 用法:
|
||
* node tools/pack-deploy.mjs # 生成 dist-deploy/kole-ui/ + manifest
|
||
* node tools/pack-deploy.mjs --tar # 额外打出 dist-deploy/kole-ui-deploy.tar.gz
|
||
*/
|
||
'use strict';
|
||
import fs from 'node:fs';
|
||
import path from 'node:path';
|
||
import { execFileSync } from 'node:child_process';
|
||
import { collectPublishSet, TOP_FILES } from './lib/publish-set.mjs';
|
||
|
||
const ROOT = process.cwd();
|
||
const OUT_DIR = path.join(ROOT, 'dist-deploy');
|
||
const STAGE = path.join(OUT_DIR, 'kole-ui');
|
||
|
||
/* 发布集(含 .dockerignore 的解析与匹配语义、内容目录清单、历史版本快照目录)统一在
|
||
tools/lib/publish-set.mjs 里,与 tools/verify-deploy-consistency.mjs 同源 ——
|
||
「打包器发了什么」与「线上核对看到什么」必须是同一份清单。 */
|
||
const { kept, candidates, excludedCount, versionDirs } = collectPublishSet(ROOT);
|
||
const pcIndex = JSON.parse(fs.readFileSync(path.join(ROOT, '.design_library', 'kole-ui', 'components', 'index.json'), 'utf8'));
|
||
const pcCount = pcIndex.components.length;
|
||
const pcFrameworkCount = pcCount * 5;
|
||
|
||
/* ---------- 硬断言:不该发的绝不允许出现 ---------- */
|
||
const MUST_ABSENT = [
|
||
'.design_library/kole-ui/specs/组件1.txt',
|
||
'.design_library/kole-ui/agent-reports/phase3-component-modal.json',
|
||
'.design_library/kole-ui/preview',
|
||
'.design_library/kole-ui/ui_kits',
|
||
'.design_library/kole-ui/SKILL.md',
|
||
/* 移动端规格原文与 PC 规格同级:仓库内部真源,不进镜像 */
|
||
'.design_library/kole-ui-mobile/spec',
|
||
'AGENTS.md', 'CHANGELOG.md', 'ROADMAP.md', 'TESTING.md', 'PLATFORMS.md',
|
||
'docker-compose.yml.bak-20260916', 'build-site.ps1',
|
||
];
|
||
const MUST_PRESENT = [
|
||
'site/index.html', 'site/app.js', 'site/logger.js', 'site/data.json', 'site/data.js',
|
||
'site/tokens/tokens.css', 'site/style.css', 'site/i18n.js',
|
||
'site/assets/kole-mark.svg', 'site/assets/kole-mark-mono.svg', 'site/brand-mark.generated.json',
|
||
/* 逐示例用法片段(P21):目录留在 data.js,正文按组件懒加载;缺了页面就没有「一个使用场景一块代码」 */
|
||
'site/examples/button.json',
|
||
'frameworks/Button.css', 'frameworks/Button.html', 'frameworks/Button.vue3.vue',
|
||
'tests/index.html',
|
||
'.design_library/kole-ui/colors_and_type.css', '.design_library/kole-ui/components.css',
|
||
'.design_library/kole-ui/css.json', '.design_library/kole-ui/components/button.json',
|
||
/* 移动端平台:文档站六页(与 PC 同名同序的指南页 + 平台页)+ 自包含数据 + 索引 + 令牌层 + 实现 + 测试页 */
|
||
'site/m/index.html', 'site/m/guide.html', 'site/m/design.html', 'site/m/faq.html',
|
||
'site/m/changelog.html', 'site/m/platform.html',
|
||
'site/m/style.css', 'site/m/data.mobile.json',
|
||
'frameworks-mobile/NavBar.html', 'frameworks-mobile/NavBar.css', 'frameworks-mobile/NavBar.uniapp.vue',
|
||
'tests/mobile/index.html', 'tests/mobile/_collect.html',
|
||
'.design_library/kole-ui-mobile/colors_and_type.css', '.design_library/kole-ui-mobile/components/index.json',
|
||
/* PC × uni-app 端(试点集) */
|
||
'frameworks-uniapp-pc/Button.uniapp.vue', '.design_library/kole-ui-uniapp/index.json',
|
||
'Dockerfile', '.dockerignore', 'nginx.conf', 'docker-compose.yml', 'sitemap.xml', 'versions.json',
|
||
];
|
||
|
||
const problems = [];
|
||
for (const f of MUST_ABSENT) {
|
||
const bad = kept.some((k) => k === f || k.startsWith(f + '/'));
|
||
if (bad) problems.push('不该出现:' + f);
|
||
}
|
||
for (const f of MUST_PRESENT) {
|
||
if (!kept.includes(f)) problems.push('缺失必需文件:' + f);
|
||
}
|
||
const shells = kept.filter((f) => /^site\/components\/[^/]+\.html$/.test(f)).length;
|
||
if (shells !== pcCount) problems.push(`site/components 薄壳数应为 ${pcCount},实际 ${shells}`);
|
||
/* 历史版本快照必须整份随包(缺壳或缺依赖 → 版本菜单点进去白屏) */
|
||
for (const dir of versionDirs) {
|
||
for (const rel of ['site/index.html', 'site/data.js',
|
||
'frameworks/Button.css', '.design_library/kole-ui/colors_and_type.css', 'VERSION.json']) {
|
||
if (!kept.includes(`${dir}/${rel}`)) problems.push(`版本快照 ${dir} 缺少 ${rel}`);
|
||
}
|
||
/* 快照不该带自己的 versions.json:那是归档当时的快照,之后新归档的版本不会进去,
|
||
实测会让快照页误判成单版本、角标不可点(回不到最新版)。运行时读部署根那份。 */
|
||
if (kept.includes(`${dir}/site/versions.json`)) problems.push(`版本快照 ${dir} 带了过期的 versions.json`);
|
||
const snapShells = kept.filter((f) => new RegExp(`^${dir}/site/components/[^/]+\\.html$`).test(f)).length;
|
||
if (snapShells !== 79) problems.push(`版本快照 ${dir} 的组件薄壳数应为 79,实际 ${snapShells}`);
|
||
}
|
||
const fw = kept.filter((f) => f.startsWith('frameworks/')).length;
|
||
if (fw !== pcFrameworkCount) problems.push(`frameworks 实现文件应为 ${pcFrameworkCount},实际 ${fw}`);
|
||
/* 移动端:组件数从索引读(不写死),实现文件 = 组件数 × 6 端;文档页与测试页各 = 组件数 */
|
||
const mobileIndex = JSON.parse(
|
||
fs.readFileSync(path.join(ROOT, '.design_library', 'kole-ui-mobile', 'components', 'index.json'), 'utf8')
|
||
);
|
||
const mbCount = mobileIndex.components.length;
|
||
const mbEnds = mobileIndex.ends.length;
|
||
const fwMobile = kept.filter((f) => f.startsWith('frameworks-mobile/')).length;
|
||
if (fwMobile !== mbCount * mbEnds) {
|
||
problems.push(`frameworks-mobile 实现文件应为 ${mbCount * mbEnds}(${mbCount} 组件 × ${mbEnds} 端),实际 ${fwMobile}`);
|
||
}
|
||
const mDocs = kept.filter((f) => /^site\/m\/component\/[^/]+\.html$/.test(f)).length;
|
||
if (mDocs !== mbCount) problems.push(`site/m/component 文档页应为 ${mbCount},实际 ` + mDocs);
|
||
/* 移动端文档站根页:与 PC 同名同序的指南页(组件总览/快速开始/设计规范/常见问题/更新日志)+ 平台与端 */
|
||
const mRootPages = kept.filter((f) => /^site\/m\/(index|guide|design|faq|changelog|platform)\.html$/.test(f)).length;
|
||
if (mRootPages !== 6) problems.push('site/m 根页应为 6(index/guide/design/faq/changelog/platform),实际 ' + mRootPages);
|
||
const mTests = kept.filter((f) => /^tests\/mobile\/[^/]+\.html$/.test(f)).length;
|
||
if (mTests !== mbCount + 4) {
|
||
problems.push(`tests/mobile 页面应为 ${mbCount + 4}(组件页 + index + _collect + _template + _index_template),实际 ${mTests}`);
|
||
}
|
||
const mTestSlugs = kept.filter(
|
||
(f) => /^tests\/mobile\/[^/]+\.html$/.test(f) && !/^tests\/mobile\/(index|_collect|_template|_index_template)\.html$/.test(f)
|
||
).length;
|
||
if (mTestSlugs !== mbCount) problems.push(`tests/mobile 组件测试页应为 ${mbCount},实际 ` + mTestSlugs);
|
||
const fwUniappPc = kept.filter((f) => f.startsWith('frameworks-uniapp-pc/')).length;
|
||
if (fwUniappPc !== 3) problems.push('frameworks-uniapp-pc 试点文件应为 3,实际 ' + fwUniappPc);
|
||
|
||
if (problems.length) {
|
||
console.error('[pack-deploy] FAIL');
|
||
for (const p of problems) console.error(' - ' + p);
|
||
process.exit(1);
|
||
}
|
||
|
||
/* ---------- 落盘 ---------- */
|
||
fs.rmSync(STAGE, { recursive: true, force: true });
|
||
let bytes = 0;
|
||
for (const rel of kept) {
|
||
const dst = path.join(STAGE, rel);
|
||
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
||
const buf = fs.readFileSync(path.join(ROOT, rel));
|
||
fs.writeFileSync(dst, buf);
|
||
bytes += buf.length;
|
||
}
|
||
const manifest = {
|
||
generatedAt: new Date().toISOString(),
|
||
files: kept.length,
|
||
bytes,
|
||
components: shells,
|
||
frameworks: fw,
|
||
mobile: { impl: fwMobile, docs: mDocs, testPages: mTestSlugs },
|
||
uniappPc: fwUniappPc,
|
||
excludedByDockerignore: excludedCount,
|
||
list: kept,
|
||
};
|
||
fs.writeFileSync(path.join(OUT_DIR, 'deploy-manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
||
|
||
console.log('[pack-deploy] OK');
|
||
console.log(' staging : ' + STAGE);
|
||
console.log(' files : ' + kept.length + '(内容文件 ' + candidates.length + ',按 .dockerignore 排除 ' + excludedCount + ';部署配置文件 ' + TOP_FILES.length + ' 个始终保留)');
|
||
console.log(' bytes : ' + (bytes / 1024).toFixed(1) + ' KB');
|
||
console.log(' components : ' + shells + ' 个薄壳 / frameworks ' + fw + ' 个实现文件');
|
||
console.log(' mobile : ' + fwMobile + ' 个实现文件(' + mbCount + ' 组件 × 6 端)/ 文档页 ' + mDocs + ' / 测试页 ' + mTestSlugs);
|
||
console.log(' uniapp-pc : ' + fwUniappPc + ' 个试点 SFC(PC × uni-app)');
|
||
console.log(' 断言 : 排除项 ' + MUST_ABSENT.length + ' 条全部未出现;必需项 ' + MUST_PRESENT.length + ' 条全部存在');
|
||
|
||
if (process.argv.includes('--tar')) {
|
||
/* 用相对路径调用 tar:bsdtar(Windows 自带)对含中文的绝对路径 + -C 组合会报 status 2 */
|
||
const tgzRel = path.join('dist-deploy', 'kole-ui-deploy.tar.gz');
|
||
const tgz = path.join(ROOT, tgzRel);
|
||
fs.rmSync(tgz, { force: true });
|
||
execFileSync('tar', ['-czf', tgzRel, '-C', 'dist-deploy', 'kole-ui'], { cwd: ROOT, stdio: 'inherit' });
|
||
console.log(' tarball : ' + tgz + '(' + (fs.statSync(tgz).size / 1024).toFixed(1) + ' KB)');
|
||
}
|