Regression / regression (push) Canceled after 0s
## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」, 那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」, 移动端站已是「K」;移动端文档站则完全没有 favicon)。 - 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边; 描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」 - 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark); 顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28)) - 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg) - 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留) - 移动端 7 个模板补 favicon(此前计数 0) 验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/ theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致; 两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。 ## 并行会话成果(本次一并入库) - 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具 + IconPreview 预览页 + ICON-SPEC.md 冻结规格 - 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁 - PC 组件:103 个大后台组件 / 组件11 批次 - uni-app:PC 端试点 + 移动端端实现 + 真实编译验证 ## 工程 - .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库) - CHANGELOG 补品牌标识条目 - ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
163 lines
9.4 KiB
JavaScript
163 lines
9.4 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',
|
||
/* 逐示例用法片段(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)');
|
||
}
|