Files
aurora-admin/tools/pack-deploy.mjs
T
aurora-admin f1fbfc2ddb
Regression / regression (push) Canceled after 0s
feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
## 品牌标识(本次会话)

起因:品牌此前没有任何图形标识 —— 唯一 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 未做部分)
2026-09-21 10:05:48 +08:00

163 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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)');
}