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 未做部分)
107 lines
5.6 KiB
JavaScript
107 lines
5.6 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* verify-deploy-consistency.mjs — 发布快照与本地工作树的一致性核对
|
||
*
|
||
* 用法:
|
||
* node tools/verify-deploy-consistency.mjs # 默认对公网 https://kole-ui.mymoyu.top
|
||
* node tools/verify-deploy-consistency.mjs --base=http://127.0.0.1:3311
|
||
* node tools/verify-deploy-consistency.mjs --only=site/playground # 只核对路径含该子串的文件
|
||
* 退出码:0 一致 / 1 有滞后或缺失 / 2 环境问题(连不上 / 目录缺失)
|
||
*
|
||
* 为什么需要它:AGENTS §九 要求"打包前后各查一次产物哈希",但那是**手工步骤** —— 实测踩过:
|
||
* 2026-09-20 的一次发布里,快照与工作树在 6 个文件上不一致(`site/app.js` / `site/style.css` /
|
||
* `site/dev-server.js` / `site/versions.json` / `tests/_runtime.js` / 生成物 `report.json`),
|
||
* 其中 `style.css` 的差异直接在线上表现为**缺陷**:375px 下语言切换器被挤出顶栏(实测宽度 0)。
|
||
* 逐字节比对只能靠脚本,凭"刚发布过"的印象一定会漏。
|
||
*
|
||
* 判据:按目录递归枚举工作树里**会随发布出去**的文件(与 pack-deploy 的发布集一致),
|
||
* 逐个拉远端同路径比对 sha256。生成物(tests/report*.json|xml、site/data.js)单独计数,
|
||
* 差异只提示不算失败 —— 它们本来就随每次运行变化。
|
||
*
|
||
* 注意:比对是**大小写敏感**的(远端是 Linux 容器)。本地 Windows 上写错大小写的路径
|
||
* 在本地 dev-server 能过、到线上就是 404 —— 那类问题由这里暴露,不要"顺手"改成不敏感。
|
||
*/
|
||
import { readFileSync, existsSync } from 'node:fs';
|
||
import { createHash } from 'node:crypto';
|
||
import { collectPublishSet, NOT_WEB_SERVED } from './lib/publish-set.mjs';
|
||
|
||
const args = process.argv.slice(2);
|
||
const arg = (name) => {
|
||
const hit = args.find((a) => a.startsWith(`--${name}=`));
|
||
return hit ? hit.slice(name.length + 3) : '';
|
||
};
|
||
const BASE = (arg('base') || process.env.DEPLOY_BASE || 'https://kole-ui.mymoyu.top').replace(/\/+$/, '');
|
||
const ONLY = arg('only');
|
||
const CONC = Number(arg('conc') || process.env.DEPLOY_CONC) || 8;
|
||
const ROOT = process.cwd();
|
||
/* 生成物:本来就随每次运行变化(回归报告、precompute 后的 data.js),差异只提示不算失败 */
|
||
const GENERATED = /^(tests\/report(-junit)?\.(json|xml)|site\/data\.js)$/;
|
||
/* 发布集直接复用打包器那份实现(tools/lib/publish-set.mjs,规则真源是 .dockerignore)。
|
||
再剔掉不经 HTTP 暴露的构建/运维配置(Dockerfile / nginx.conf / index.html …)——
|
||
它们在镜像里但不在 web 根下,没法用 URL 比对。 */
|
||
let targets;
|
||
try {
|
||
targets = collectPublishSet(ROOT).kept.filter((f) => !NOT_WEB_SERVED.includes(f));
|
||
} catch (e) {
|
||
console.error(`[deploy] 环境问题:${e.message}(请在仓库根目录运行)`);
|
||
process.exit(2);
|
||
}
|
||
if (ONLY) targets = targets.filter((t) => t.includes(ONLY));
|
||
if (!targets.length) { console.error(`[deploy] --only=${ONLY} 没匹配到任何文件`); process.exit(2); }
|
||
|
||
const sha = (buf) => createHash('sha256').update(buf).digest('hex');
|
||
const hashOf = new Map(targets.filter((t) => existsSync(t)).map((t) => [t, sha(readFileSync(t))]));
|
||
|
||
/* 先确认 BASE 真的是这个站 */
|
||
try {
|
||
const probe = await fetch(`${BASE}/site/data.json`, { signal: AbortSignal.timeout(30000) });
|
||
if (!probe.ok) throw new Error(`HTTP ${probe.status}`);
|
||
} catch (e) {
|
||
console.error(`[deploy] 连不上 ${BASE}/site/data.json — ${e.message}`);
|
||
process.exit(2);
|
||
}
|
||
|
||
const lagged = [];
|
||
const missing = [];
|
||
const generatedDiff = [];
|
||
let same = 0;
|
||
/* nginx 会**故意**把这些路径 302 到别处(不是文件内容不一致):
|
||
- `/<x.y.z>/` 与 `/<x.y.z>/index.html` → 该版站点(nginx.conf 的版本裸入口规则)
|
||
- 根 `/index.html` → `/site/`
|
||
这类路径取到的字节是 nginx 的 302 响应体,与磁盘文件天然不同 —— 不排除就会报假滞后
|
||
(2026-09-20 实测:`1.4.1/index.html` 被当成「线上滞后」,而线上该 URL 本就是 302)。 */
|
||
const NGINX_REDIRECTS = [/^index\.html$/, /^\d+\.\d+\.\d+\/index\.html$/];
|
||
let skipped = 0;
|
||
const queue = [...targets].filter((rel) => {
|
||
if (NGINX_REDIRECTS.some((re) => re.test(rel))) { skipped++; return false; }
|
||
return true;
|
||
});
|
||
async function worker() {
|
||
while (queue.length) {
|
||
const rel = queue.shift();
|
||
let res;
|
||
try {
|
||
res = await fetch(`${BASE}/${encodeURI(rel)}`, { signal: AbortSignal.timeout(60000) });
|
||
} catch (e) {
|
||
lagged.push(`${rel}(请求失败 ${e.message})`);
|
||
continue;
|
||
}
|
||
if (!res.ok) { missing.push(`${rel} → HTTP ${res.status}`); continue; }
|
||
const live = sha(Buffer.from(await res.arrayBuffer()));
|
||
if (live === hashOf.get(rel)) { same++; continue; }
|
||
if (GENERATED.test(rel)) generatedDiff.push(rel);
|
||
else lagged.push(rel);
|
||
}
|
||
}
|
||
await Promise.all(Array.from({ length: CONC }, worker));
|
||
|
||
console.log(`[deploy] ${BASE}`);
|
||
console.log(`[deploy] 核对 ${targets.length - skipped} 个文件(并发 ${CONC},另跳过 ${skipped} 个 nginx 重定向路径):一致 ${same} / 滞后 ${lagged.length} / 线上缺失 ${missing.length}`);
|
||
if (generatedDiff.length) console.log(`[deploy] 生成物差异(随运行变化,不计失败):${generatedDiff.join(', ')}`);
|
||
for (const l of lagged) console.log(`[deploy] 滞后: ${l}`);
|
||
for (const m of missing) console.log(`[deploy] 缺失: ${m}`);
|
||
|
||
const bad = lagged.length + missing.length;
|
||
console.log(`\n[deploy] ${bad === 0 ? 'OK — 快照与工作树一致' : `FAIL — ${bad} 个文件不一致(线上快照落后于工作树,或工作树在打包后被改写)`}`);
|
||
process.exit(bad === 0 ? 0 : 1);
|