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 未做部分)
359 lines
18 KiB
JavaScript
359 lines
18 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* verify-uniapp-build.mjs — uni-app 端**真实编译**验证(ROADMAP S7-P25)
|
||
*
|
||
* 与 tools/verify-uniapp.mjs 的分工(两者互补,都要跑):
|
||
* verify-uniapp.mjs 静态门禁:SFC 三段 / 语法 / 标签配平 / 禁 DOM API / 只用 uni 基础组件 / 前缀 / 单位
|
||
* verify-uniapp-build.mjs 本脚本:**真的调 uni-app 编译器**把 18(或 3)个 SFC 编译到 H5 与微信小程序
|
||
*
|
||
* 为什么必须做这件事(2026-09-20 首次执行):
|
||
* 静态门禁只能证明「源码长得合规」,证明不了「编译器接受它」。历史上本仓库有两次
|
||
* 「静态全绿但根本编译不过」的实例(`RangeQuickPicker` 的 const 重赋值、`CodeInput` 的 emit 遮蔽),
|
||
* 都是真编译才暴露的。uni-app 端的 18 个 SFC 此前**从未被编译器碰过** —— 本脚本补上这一环。
|
||
*
|
||
* 依赖处理(不污染主仓库):
|
||
* @dcloudio/* 装在**隔离目录**,仓库的 package.json 保持零运行时依赖。
|
||
* 依赖目录优先级:$KOLE_UNIAPP_DEPS_DIR > .tmp/uniapp-build/node_modules > 报错并打印安装命令。
|
||
*
|
||
* 用法:
|
||
* node tools/verify-uniapp-build.mjs # H5 + mp-weixin 两目标
|
||
* node tools/verify-uniapp-build.mjs --target=h5 # 只编译 H5(更快)
|
||
* node tools/verify-uniapp-build.mjs --keep # 保留临时工程(排查编译错误用)
|
||
*
|
||
* 首次准备(约 2 分钟,需联网;之后复用):
|
||
* mkdir -p .tmp/uniapp-build && cd .tmp/uniapp-build
|
||
* npm init -y && npm i -D @dcloudio/vite-plugin-uni@3.0.0-5020620260917001 \
|
||
* @dcloudio/uni-cli-shared@3.0.0-5020620260917001 vite@^5 vue@^3.4
|
||
* @dcloudio/uni-app@3.0.0-5020620260917001 @dcloudio/uni-h5@3.0.0-5020620260917001 \
|
||
* @dcloudio/uni-mp-weixin@3.0.0-5020620260917001 @dcloudio/uni-components@3.0.0-5020620260917001
|
||
*
|
||
* 退出码:0 全部通过;1 编译失败或产物断言失败;2 依赖缺失(环境问题,非代码问题)
|
||
*/
|
||
import {
|
||
readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, rmSync, cpSync,
|
||
} from 'node:fs';
|
||
import { execFileSync } from 'node:child_process';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { dirname, join } from 'node:path';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const ROOT = join(__dirname, '..');
|
||
const read = (p) => readFileSync(p, 'utf8').replace(/^\uFEFF/, '');
|
||
|
||
/* ---------- 参数 ---------- */
|
||
const args = process.argv.slice(2);
|
||
const only = (args.find((a) => a.startsWith('--target=')) || '').split('=')[1]; // h5 | mp-weixin
|
||
const KEEP = args.includes('--keep');
|
||
const TARGETS = only ? [only] : ['h5', 'mp-weixin'];
|
||
|
||
let pass = 0;
|
||
const failures = [];
|
||
function check(ok, id, detail) {
|
||
if (ok) {
|
||
pass++;
|
||
console.log(` PASS ${id}${detail ? ' — ' + detail : ''}`);
|
||
} else {
|
||
failures.push(id + (detail ? ' — ' + detail : ''));
|
||
console.log(` FAIL ${id}${detail ? ' — ' + detail : ''}`);
|
||
}
|
||
}
|
||
|
||
/* ---------- 依赖定位(隔离目录,不碰主仓库 node_modules) ---------- */
|
||
|
||
function resolveDepsDir() {
|
||
const candidates = [
|
||
process.env.KOLE_UNIAPP_DEPS_DIR,
|
||
join(ROOT, '.tmp', 'uniapp-build'),
|
||
].filter(Boolean);
|
||
for (const c of candidates) {
|
||
if (existsSync(join(c, 'node_modules', '@dcloudio', 'vite-plugin-uni'))) return c;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
const DEPS = resolveDepsDir();
|
||
if (!DEPS) {
|
||
console.error('[FATAL] 找不到 uni-app 工具链(@dcloudio/vite-plugin-uni)。');
|
||
console.error(' 这是环境问题,不是代码问题 —— 请先准备隔离的依赖目录:');
|
||
console.error(' mkdir -p .tmp/uniapp-build && cd .tmp/uniapp-build');
|
||
console.error(' npm init -y');
|
||
console.error(' npm i -D @dcloudio/vite-plugin-uni@3.0.0-5020620260917001 \\');
|
||
console.error(' @dcloudio/uni-cli-shared@3.0.0-5020620260917001 vite@^5 vue@^3.4 \\');
|
||
console.error(' @dcloudio/uni-app@3.0.0-5020620260917001 \\');
|
||
console.error(' @dcloudio/uni-h5@3.0.0-5020620260917001 \\');
|
||
console.error(' @dcloudio/uni-mp-weixin@3.0.0-5020620260917001 \\');
|
||
console.error(' @dcloudio/uni-components@3.0.0-5020620260917001');
|
||
console.error(' 或用 KOLE_UNIAPP_DEPS_DIR 指向已有目录。');
|
||
process.exit(2);
|
||
}
|
||
|
||
/* ---------- 待编译的 SFC(从两端索引读取,与静态门禁同一真源) ---------- */
|
||
|
||
/* 依赖版本从**已装的那份**读,而不是硬编码 —— 硬编码会在依赖升级后静默失配
|
||
(package.json 里写旧版本号时 npm/vite 解析不到,产物又变回空包) */
|
||
const DEPS_VERSION = JSON.parse(
|
||
read(join(DEPS, 'node_modules', '@dcloudio', 'vite-plugin-uni', 'package.json'))
|
||
).version;
|
||
|
||
const mbIndex = JSON.parse(read(join(ROOT, '.design_library', 'kole-ui-mobile', 'components', 'index.json')));
|
||
const mobileSfcs = mbIndex.components.map((c) => ({
|
||
slug: c.slug,
|
||
name: c.frameworksPrefix,
|
||
file: c.files.uniapp,
|
||
src: join(ROOT, 'frameworks-mobile', c.files.uniapp),
|
||
}));
|
||
|
||
const pcIndexPath = join(ROOT, '.design_library', 'kole-ui-uniapp', 'index.json');
|
||
const pcSfcs = existsSync(pcIndexPath)
|
||
? JSON.parse(read(pcIndexPath)).components.map((c) => ({
|
||
slug: c.slug,
|
||
/* 字段名是 frameworksPrefix(与移动端索引同惯例),不是 prefix —— 写错会让绑定名成 undefined
|
||
(踩坑记录:首次写成 c.prefix,21 个里 3 个绑定名是 undefined,模板标签 <undefined /> 直接编译不过) */
|
||
name: c.frameworksPrefix,
|
||
file: c.file,
|
||
src: join(ROOT, 'frameworks-uniapp-pc', c.file),
|
||
}))
|
||
: [];
|
||
|
||
const all = [...mobileSfcs, ...pcSfcs];
|
||
|
||
/* 绑定名必须齐全且唯一 —— 缺名会生成 `<undefined />`(编译不过),重名会互相覆盖(静默丢组件)。
|
||
真实冲突(2026-09-20 实测):移动端有 `Button`(kole-m-btn-*),PC×uni-app 也有 `Button`
|
||
(PC 的 btn-*)—— 两者是**不同平台的同名组件**,直接都用 `Button` 作绑定名会互相覆盖,
|
||
产物里整批组件的样式全部消失(JS 只剩 App.vue,704 字节)。
|
||
消歧规则:PC×uni-app 侧加 `Pc` 前缀(移动端保持原名,因为它是本仓库的主要交付面)。 */
|
||
const NAME_CONFLICTS = new Set();
|
||
{
|
||
const seen = new Map();
|
||
for (const s of all) {
|
||
const base = s.name;
|
||
if (seen.has(base)) NAME_CONFLICTS.add(base);
|
||
seen.set(base, s);
|
||
}
|
||
for (const s of all) {
|
||
if (NAME_CONFLICTS.has(s.name) && s.src.includes('frameworks-uniapp-pc')) {
|
||
s.binding = 'Pc' + s.name;
|
||
} else {
|
||
s.binding = s.name;
|
||
}
|
||
}
|
||
}
|
||
|
||
const names = all.map((s) => s.name);
|
||
const bindings = all.map((s) => s.binding);
|
||
const badNames = all.filter((s) => !s.binding || !/^[A-Z][A-Za-z0-9]*$/.test(s.binding)).map((s) => s.slug);
|
||
const dupBindings = [...new Set(bindings.filter((n, i) => bindings.indexOf(n) !== i))];
|
||
check(badNames.length === 0 && dupBindings.length === 0, 'B0b 组件绑定名合法且唯一',
|
||
badNames.length || dupBindings.length
|
||
? `非法 ${badNames.join(', ') || '无'} / 重名 ${dupBindings.join(', ') || '无'}`
|
||
: `${bindings.length} 个${NAME_CONFLICTS.size ? '(同名消歧:' + [...NAME_CONFLICTS].join(', ') + ' → PC 侧加 Pc 前缀)' : ''}`);
|
||
|
||
console.log(`uni-app 真实编译验证 | 待编译 ${all.length} 个 SFC(移动端 ${mobileSfcs.length} · PC ${pcSfcs.length})`);
|
||
console.log(`依赖目录:${DEPS}`);
|
||
console.log(`目标:${TARGETS.join(', ')}\n`);
|
||
|
||
/* ---------- 搭一个最小 uni-app 工程,把 SFC 全量引入并编译 ---------- */
|
||
|
||
const PROJ = join(ROOT, '.tmp', 'uniapp-probe');
|
||
if (existsSync(PROJ)) rmSync(PROJ, { recursive: true, force: true });
|
||
mkdirSync(join(PROJ, 'src', 'pages', 'index'), { recursive: true });
|
||
mkdirSync(join(PROJ, 'src', 'components'), { recursive: true });
|
||
|
||
/* 依赖走软链/复制:把隔离目录的 node_modules 挂到工程里(Windows 下用 junction) */
|
||
try {
|
||
execFileSync('cmd', ['/c', 'mklink', '/J', join(PROJ, 'node_modules'), join(DEPS, 'node_modules')], { stdio: 'pipe' });
|
||
} catch {
|
||
/* 非 Windows 或已存在:退回复制(慢但可行) */
|
||
cpSync(join(DEPS, 'node_modules'), join(PROJ, 'node_modules'), { recursive: true });
|
||
}
|
||
|
||
/* package.json 必须把 @dcloudio/* 声明进 dependencies(不能只放 devDependencies)——
|
||
踩坑记录(2026-09-20,本脚本调通前最深的一个坑):uni-app 编译器靠 package.json 里声明的
|
||
@dcloudio 依赖来**识别这是一个 uni-app 工程**。只写 `{name, private, version}` 时,
|
||
编译器照样打印 "DONE Build complete."、退出码 0,但产物只有一个 704 字节的
|
||
modulepreload polyfill —— **页面与 21 个组件全部静默丢失**。
|
||
这类失败「静态门禁」与「编译成功」都发现不了,只有「断言产物里必须出现组件类名」能抓住,
|
||
所以下面的 B-* 类名断言不是锦上添花,而是本脚本的核心价值。 */
|
||
writeFileSync(join(PROJ, 'package.json'), JSON.stringify({
|
||
name: 'kole-uniapp-probe',
|
||
private: true,
|
||
version: '0.0.0',
|
||
scripts: { 'build:h5': 'uni build -p h5', 'build:mp-weixin': 'uni build -p mp-weixin' },
|
||
dependencies: {
|
||
'@dcloudio/uni-app': DEPS_VERSION,
|
||
'@dcloudio/uni-components': DEPS_VERSION,
|
||
'@dcloudio/uni-h5': DEPS_VERSION,
|
||
'@dcloudio/uni-mp-weixin': DEPS_VERSION,
|
||
vue: '^3.4.21',
|
||
},
|
||
devDependencies: {
|
||
'@dcloudio/uni-cli-shared': DEPS_VERSION,
|
||
'@dcloudio/vite-plugin-uni': DEPS_VERSION,
|
||
vite: '^5.2.8',
|
||
},
|
||
}, null, 2) + '\n');
|
||
|
||
writeFileSync(join(PROJ, 'vite.config.js'),
|
||
"import { defineConfig } from 'vite';\nimport uni from '@dcloudio/vite-plugin-uni';\nexport default defineConfig({ plugins: [uni()] });\n");
|
||
|
||
writeFileSync(join(PROJ, 'index.html'),
|
||
'<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><title>Kole UI probe</title></head>' +
|
||
'<body><div id="app"></div><script type="module" src="/src/main.js"></script></body></html>\n');
|
||
|
||
writeFileSync(join(PROJ, 'src', 'main.js'),
|
||
"import { createSSRApp } from 'vue';\nimport App from './App.vue';\nexport function createApp() {\n" +
|
||
" const app = createSSRApp(App);\n return { app };\n}\n");
|
||
|
||
writeFileSync(join(PROJ, 'src', 'App.vue'),
|
||
'<template><view class="app-root"><text>Kole UI probe</text></view></template>\n' +
|
||
'<script setup></script>\n<style>.app-root { padding: 20rpx; }</style>\n');
|
||
|
||
writeFileSync(join(PROJ, 'src', 'pages.json'), JSON.stringify({
|
||
pages: [{ path: 'pages/index/index', style: { navigationBarTitleText: 'Kole UI probe' } }],
|
||
}, null, 2) + '\n');
|
||
|
||
writeFileSync(join(PROJ, 'src', 'manifest.json'), JSON.stringify({
|
||
name: 'kole-uniapp-probe', appid: '', description: 'Kole UI uni-app compile probe',
|
||
versionName: '1.0.0', versionCode: '100', vueVersion: '3',
|
||
}, null, 2) + '\n');
|
||
|
||
/* 拷 SFC + 令牌层(令牌是组件样式依赖的外部变量源) */
|
||
/* 落盘文件名用**绑定名**(含消歧前缀):移动端 Button.vue 与 PC 侧 PcButton.vue 各自独立,
|
||
否则后拷的会把先拷的覆盖掉(同名不同源,静默丢一个组件)。 */
|
||
for (const s of all) cpSync(s.src, join(PROJ, 'src', 'components', s.binding + '.vue'));
|
||
const tokens = join(ROOT, 'dist', 'mobile', 'tokens', 'tokens.css');
|
||
if (existsSync(tokens)) cpSync(tokens, join(PROJ, 'src', 'tokens.css'));
|
||
|
||
/* 页面把每个组件都真实挂载一次 —— 只有被 import 且**在模板里被用到**的 SFC 才会走完整编译链。
|
||
踩坑记录(2026-09-20 首次运行):初版用 `<C0 />` + `import C0 from …` 的索引式命名,
|
||
编译**通过但产物里 0 个组件**(JS 704 字节,只有 App.vue)——uni-app 的模板编译器要求
|
||
标签名与 import 绑定名可静态对应,`C0` 这类名字被当成未识别元素丢弃了。
|
||
改为「用组件自身的 Pascal 名作绑定名」后,21 个 SFC 全部进入产物(实测)。
|
||
这正是本脚本存在的意义:静态门禁与「编译成功」都发现不了这种整批静默丢弃。 */
|
||
const imports = all.map((s) => `import ${s.binding} from '../../components/${s.binding}.vue';`).join('\n');
|
||
const tags = all.map((s) => ` <${s.binding} />`).join('\n');
|
||
writeFileSync(join(PROJ, 'src', 'pages', 'index', 'index.vue'),
|
||
`<template>\n <view class="probe-page">\n${tags}\n </view>\n</template>\n\n` +
|
||
`<script setup>\n${imports}\n</script>\n\n` +
|
||
'<style>\n.probe-page { padding: 24rpx; background-color: var(--kole-color-page-bg); }\n</style>\n');
|
||
|
||
check(all.every((s) => existsSync(s.src)), 'B0 待编译 SFC 全部存在于源目录',
|
||
`${all.length} 个(${all.filter((s) => !existsSync(s.src)).map((s) => s.file).join(', ') || '无缺失'})`);
|
||
|
||
/* ---------- 逐目标编译 ---------- */
|
||
|
||
function compile(target) {
|
||
const env = { ...process.env, UNI_PLATFORM: target, NODE_ENV: 'production' };
|
||
try {
|
||
const out = execFileSync(
|
||
process.execPath,
|
||
[join(PROJ, 'node_modules', '@dcloudio', 'vite-plugin-uni', 'bin', 'uni.js'), 'build', '-p', target],
|
||
{ cwd: PROJ, env, stdio: 'pipe', timeout: 300000, encoding: 'utf8' }
|
||
);
|
||
return { ok: true, out };
|
||
} catch (e) {
|
||
return { ok: false, out: String(e.stdout || '') + String(e.stderr || '') + String(e.message || '') };
|
||
}
|
||
}
|
||
|
||
const results = {};
|
||
for (const target of TARGETS) {
|
||
const t0 = Date.now();
|
||
const r = compile(target);
|
||
const ms = Date.now() - t0;
|
||
results[target] = r;
|
||
check(r.ok, `B-${target} 编译通过`, r.ok ? `${ms} ms` : r.out.split('\n').filter(Boolean).slice(-6).join(' / ').slice(0, 300));
|
||
if (!r.ok) continue;
|
||
|
||
const dist = join(PROJ, 'dist', 'build', target);
|
||
check(existsSync(dist), `B-${target} 产物目录存在`, dist.replace(ROOT + '\\', '').replace(ROOT + '/', ''));
|
||
|
||
/* 产物里必须出现 kole-m- 类名(证明 18 个组件的样式真进了产物,而不是被 tree-shake 掉) */
|
||
let clsHit = 0;
|
||
const walk = (dir) => {
|
||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||
const p = join(dir, e.name);
|
||
if (e.isDirectory()) walk(p);
|
||
else if (/\.(css|wxss|js|wxml)$/.test(e.name)) {
|
||
const m = read(p).match(/kole-m-[a-z0-9-]+/g);
|
||
if (m) clsHit += new Set(m).size;
|
||
}
|
||
}
|
||
};
|
||
if (existsSync(dist)) walk(dist);
|
||
check(clsHit > 0, `B-${target} 产物含 kole-m- 类名`, `${clsHit} 个唯一类名`);
|
||
|
||
/* 「每个组件都真的进了产物」怎么判才**不可自证**?
|
||
两次变异测试的教训(2026-09-20):
|
||
① 从 `frameworks-mobile/<X>.css` 取类名 —— uni-app 端样式是**内联**的,
|
||
改 uniapp 端不影响那份 `.css`,判据查的是另一份文件(假通过)。
|
||
② 从 uni-app 端源码取类名 —— 改名后产物与判据**同步变化**,两边一致所以照样 PASS
|
||
(实测:`kole-m-grid` 全量改名后产物里只有新名,判据取到的也是新名)。
|
||
最终判据:拿**契约**(`.design_library/kole-ui-mobile/components/<slug>.json` 的
|
||
`variantClasses`)里声明的类名去产物里找。契约与实现是两条独立的写入路径
|
||
(实现由人手写、契约由规格推导),所以「实现悄悄改名/丢组件」时契约不会跟着变 ——
|
||
这正是能让变异测试 FAIL 的原因。 */
|
||
const missing = [];
|
||
for (const s of mobileSfcs) {
|
||
if (!existsSync(s.src)) { missing.push(s.slug + '(缺 uni-app 端)'); continue; }
|
||
const ctPath = join(ROOT, '.design_library', 'kole-ui-mobile', 'components', s.slug + '.json');
|
||
if (!existsSync(ctPath)) { missing.push(s.slug + '(缺契约)'); continue; }
|
||
const vc = (JSON.parse(read(ctPath)).variantClasses) || {};
|
||
const declared = new Set();
|
||
for (const dim of Object.values(vc)) {
|
||
for (const arr of Object.values(dim || {})) {
|
||
for (const t of arr || []) if (typeof t === 'string' && t.startsWith('.')) declared.add(t.slice(1));
|
||
}
|
||
}
|
||
if (!declared.size) { missing.push(s.slug + '(契约无类名声明)'); continue; }
|
||
/* 只要**任一**契约声明的类名出现在产物里,就证明该组件的样式真的进了包 */
|
||
let hit = false;
|
||
const walk2 = (dir) => {
|
||
if (hit) return;
|
||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||
const p = join(dir, e.name);
|
||
if (e.isDirectory()) walk2(p);
|
||
else if (/\.(css|wxss|js|wxml)$/.test(e.name)) {
|
||
const t = read(p);
|
||
for (const cls of declared) if (t.includes(cls)) { hit = true; return; }
|
||
}
|
||
}
|
||
};
|
||
if (existsSync(dist)) walk2(dist);
|
||
if (!hit) missing.push(s.slug + '(契约类名 ' + [...declared][0] + ' 未进产物)');
|
||
}
|
||
check(missing.length === 0, `B-${target} 每个移动端组件(按契约声明的类名)都进了产物`,
|
||
missing.length ? missing.slice(0, 5).join(', ') : `${mobileSfcs.length}/${mobileSfcs.length}`);
|
||
|
||
/* 小程序端不得出现 DOM API(静态门禁查的是源码,这里查的是**编译产物**) */
|
||
if (target === 'mp-weixin') {
|
||
let domHit = 0;
|
||
const walk3 = (dir) => {
|
||
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
||
const p = join(dir, e.name);
|
||
if (e.isDirectory()) walk3(p);
|
||
else if (/\.(js|wxml)$/.test(e.name) && read(p).match(/\bdocument\.(createElement|querySelector|body)/)) domHit++;
|
||
}
|
||
};
|
||
if (existsSync(dist)) walk3(dist);
|
||
check(domHit === 0, 'B-mp-weixin 产物无 DOM 操作', domHit ? `${domHit} 个文件命中` : '0');
|
||
}
|
||
}
|
||
|
||
/* ---------- 收尾 ---------- */
|
||
|
||
if (!KEEP) rmSync(PROJ, { recursive: true, force: true });
|
||
|
||
console.log('\n' + '─'.repeat(60));
|
||
if (failures.length === 0) {
|
||
console.log(`[OK] uni-app 真实编译验证通过(${pass} 条断言 · ${all.length} 个 SFC · ${TARGETS.join(' + ')})`);
|
||
console.log(' 注:App 端(app-plus)需 HBuilderX 的云端打包,无法在 CI 命令行完成;');
|
||
console.log(' 本脚本覆盖 H5 与微信小程序两个可命令行编译的目标。');
|
||
process.exit(0);
|
||
} else {
|
||
console.log(`[FAIL] ${failures.length} 条断言失败(通过 ${pass} 条):`);
|
||
failures.forEach((f) => console.log(' - ' + f));
|
||
console.log(' 排查提示:加 --keep 保留临时工程(.tmp/uniapp-probe),可进目录手工跑 uni build 看完整报错。');
|
||
process.exit(1);
|
||
}
|