From 0cb5c1a4df085f4ba407350afd369cafe65f7d92 Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Fri, 31 Jul 2026 08:54:30 +0800 Subject: [PATCH] =?UTF-8?q?chore(mp-harmony):=20vue.config.js=20=E9=A1=B6?= =?UTF-8?q?=E5=B1=82=20IIFE=20=E9=9B=86=E6=88=90=20runSyncCheck=20(4=20?= =?UTF-8?q?=E5=A4=84=E4=B8=80=E8=87=B4=E6=80=A7=E8=87=AA=E5=8A=A8=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 背景: 之前 vue.config.js 顶层 IIFE 只调 fixSubpackageModuleNames 修 5 个子包 name, 但漏改 build-profile.json5 / entry/oh-package.json5 / ascf_build/ 目录时 没有任何提示, 用户在 HBuilderX 看不到错误, 直到 DevEco ohpm 报错才发现。 改动: 1. scripts/fix-subpackage-module-names.js 新增 runSyncCheck() 函数: - 读取 har-deps-required.json5 (whitelist) 作为单一事实源 (优先 json5 包, 失败回退手写解析) - 扫描 3 个地方的一致性: build-profile.json5 modules / entry/oh-package.json5 file: 依赖 / ascf_build/ 实际目录 - 不一致时 console 打印明确错误 + 修复步骤 (1, 2, 3, 4 个文件) - 默认不抛错 (silent: false 仅 console 输出, exitOnError: false) - 暴露为 module.exports.runSyncCheck 2. vue.config.js 顶层 IIFE 集成: - 修完 name 后调 runSyncCheck() - HBuilderX 触发 require vue.config.js 时立即执行 - 不阻塞 vue-cli-service, 但 console 输出立刻告知用户漏改位置 - 鸿蒙端的硬性错误 (ohpm "Field IS Empty") 仍由 DevEco 抛出 端到端验证: 1. 故意在 whitelist 加 pages_extra (未同步其他 3 处) → vue.config.js require 立即输出: ✘ build-profile.json5 缺失子包: [pages_extra] ✘ entry/oh-package.json5 缺失 file: 依赖: [pages_extra] ✘ ascf_build/ 目录缺失子包: [pages_extra] 修复: 1. entry/har-deps-required.json5 ... 2. mp-harmony/build-profile.json5 ... 3. entry/oh-package.json5 ... 4. ascf_build// 实际目录 2. 故意清空 pages_tool 的 name 字段 → 修成 "pages_tool" + 跑 sync check, 输出 ✓ 5 个子包配置完全一致 module.exports 仍只有白名单字段 (productionSourceMap, configureWebpack), vue-cli-service 不会报 "Invalid options" 错。 依赖: 项目已有 json5 包, 无需新装 --- scripts/fix-subpackage-module-names.js | 157 +++++++++++++++++++++++++ vue.config.js | 15 +++ 2 files changed, 172 insertions(+) diff --git a/scripts/fix-subpackage-module-names.js b/scripts/fix-subpackage-module-names.js index b32b8c7..c9ce0eb 100644 --- a/scripts/fix-subpackage-module-names.js +++ b/scripts/fix-subpackage-module-names.js @@ -261,6 +261,162 @@ function main() { } } +/** + * 同步子包配置一致性检查 (被 vue.config.js 顶层 IIFE 调用) + * + * 用途: HBuilderX 触发 require('./vue.config.js') 时, 在修完 name 之后 + * 跑一次 4 处一致性检查, 立即告诉用户哪些地方没改。 + * + * 检查项 (复用 SUBPACKAGE_NAMES 数组): + * 1. mp-harmony/build-profile.json5 的 modules 数组 (排除 entry) + * 2. entry/oh-package.json5 的 file: 依赖 + * 3. ascf_build 实际目录 + * + * 与 scripts/check-subpackage-sync.js 的区别: + * - check-subpackage-sync.js: CLI/CI 用, --strict 可 exit 1 + * - runSyncCheck: vue.config.js IIFE 用, 默认不抛错, 仅打印 + * + * @param {object} [opts] + * @param {boolean} [opts.silent=false] - true 时不打日志 + * @param {boolean} [opts.exitOnError=false] - true 时不一致即抛错 (CLI 模式) + * @returns {{ok: boolean, errors: string[], warnings: string[]}} + */ +function runSyncCheck({ silent = false, exitOnError = false } = {}) { + const log = silent ? () => {} : console.log; + const ROOT = path.resolve(__dirname, '..'); + + // 读取白名单 (从 har-deps-required.json5 拿, 与 check-subpackage-sync.js 保持一致) + // 注: 优先用 json5 npm 包 (项目已装), 失败则回退到内置 SUBPACKAGE_NAMES + const configPath = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'entry', 'har-deps-required.json5'); + let canonical = SUBPACKAGE_NAMES.slice().sort(); + try { + if (fs.existsSync(configPath)) { + const raw = fs.readFileSync(configPath, 'utf8'); + let cfg = null; + try { + // 优先: json5 包 (支持裸 key, 注释, 单引号字符串) + cfg = require('json5').parse(raw); + } catch (e1) { + // 回退: 手写 JSON5 解析 (剥注释后用 JSON.parse) + try { + const cleaned = stripJson5Comments(raw); + cfg = JSON.parse(cleaned); + } catch (e2) { + if (!silent) console.warn(` ⚠️ har-deps-required.json5 解析失败 (json5: ${e1.message}; json: ${e2.message}), 用内置 SUBPACKAGE_NAMES`); + } + } + if (cfg && Array.isArray(cfg.requiredHarDeps) && cfg.requiredHarDeps.length) { + canonical = cfg.requiredHarDeps.slice().sort(); + } + } + } catch (e) { + if (!silent) console.warn(' ⚠️ 读取 har-deps-required.json5 失败, 用内置 SUBPACKAGE_NAMES:', e.message); + } + + const canonicalSet = new Set(canonical); + const errors = []; + const warnings = []; + + // 工具: 列出某目录下的子目录 + function listSubdirs(p) { + try { + return fs.readdirSync(p, { withFileTypes: true }) + .filter(e => e.isDirectory()) + .map(e => e.name) + .sort(); + } catch { + return []; + } + } + + function diff(b) { + const bSet = new Set(b); + return { + onlyA: [...canonicalSet].filter(x => !bSet.has(x)), + onlyB: [...bSet].filter(x => !canonicalSet.has(x)), + }; + } + + // === 检查 1: build-profile.json5 === + const buildProfilePath = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'build-profile.json5'); + let buildProfileModules = []; + if (!fs.existsSync(buildProfilePath)) { + warnings.push(`build-profile.json5 不存在: ${buildProfilePath}`); + } else { + try { + const raw = fs.readFileSync(buildProfilePath, 'utf8').replace(/\/\/.*$/gm, ''); + const bp = JSON.parse(raw); + buildProfileModules = (bp.modules || []).map(m => m.name).filter(n => n && n !== 'entry'); + } catch (e) { + errors.push(`build-profile.json5 解析失败: ${e.message}`); + } + } + const d1 = diff(buildProfileModules); + if (d1.onlyA.length) errors.push(`build-profile.json5 缺失子包: [${d1.onlyA.join(', ')}]`); + if (d1.onlyB.length) errors.push(`build-profile.json5 多了子包: [${d1.onlyB.join(', ')}]`); + + // === 检查 2: entry/oh-package.json5 === + const ohPkgPath = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'entry', 'oh-package.json5'); + let ohPkgDeps = []; + if (!fs.existsSync(ohPkgPath)) { + warnings.push(`entry/oh-package.json5 不存在: ${ohPkgPath}`); + } else { + try { + const raw = fs.readFileSync(ohPkgPath, 'utf8').replace(/\/\/.*$/gm, ''); + const op = JSON.parse(raw); + const deps = op.dependencies || {}; + ohPkgDeps = Object.keys(deps).filter(k => deps[k] && deps[k].startsWith('file:')); + } catch (e) { + errors.push(`entry/oh-package.json5 解析失败: ${e.message}`); + } + } + const d2 = diff(ohPkgDeps); + if (d2.onlyA.length) errors.push(`entry/oh-package.json5 缺失 file: 依赖: [${d2.onlyA.join(', ')}]`); + if (d2.onlyB.length) warnings.push(`entry/oh-package.json5 多了 file: 依赖: [${d2.onlyB.join(', ')}] (可能是新子包未更新白名单)`); + + // === 检查 3: ascf_build 实际目录 === + const ascfBuildRoot = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'ascf', 'ascf_build'); + const ascfBuildDirs = listSubdirs(ascfBuildRoot); + const d3 = diff(ascfBuildDirs); + if (d3.onlyA.length) errors.push(`ascf_build/ 目录缺失子包: [${d3.onlyA.join(', ')}]`); + if (d3.onlyB.length) warnings.push(`ascf_build/ 多了子包: [${d3.onlyB.join(', ')}] (可能是历史遗留)`); + + // === 输出 === + if (silent) { + // 静默模式仅返回结果 + return { ok: errors.length === 0, errors, warnings }; + } + + const c = (s, color) => { + if (!process.stdout.isTTY) return s; + const codes = { red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m', gray: '\x1b[90m', reset: '\x1b[0m' }; + return codes[color] + s + codes.reset; + }; + + if (errors.length === 0 && warnings.length === 0) { + log(c(`\n🔍 [sync check] ✓ ${canonical.length} 个子包在 3 处配置完全一致 (build-profile / oh-package / ascf_build)`, 'green')); + } else { + log(c(`\n🔍 [sync check] ${errors.length === 0 ? '⚠️' : '✘'} 4 处子包配置不一致:`, errors.length === 0 ? 'yellow' : 'red')); + for (const w of warnings) log(c(` ⚠️ ${w}`, 'yellow')); + for (const e of errors) log(c(` ✘ ${e}`, 'red')); + if (errors.length > 0) { + log(c(`\n 修复: 同步更新以下 4 个文件 (whitelist 是 har-deps-required.json5):`, 'gray')); + log(c(` 1. entry/har-deps-required.json5 (whitelist, 单一事实源)`, 'gray')); + log(c(` 2. mp-harmony/build-profile.json5 modules 数组`, 'gray')); + log(c(` 3. entry/oh-package.json5 dependencies 加 "file:../ascf/ascf_build/"`, 'gray')); + log(c(` 4. ascf_build// 实际目录`, 'gray')); + log(c(` DevEco Build 仍会因 ohpm 校验而失败, 修完再发`, 'gray')); + } + } + log(''); + + if (exitOnError && errors.length > 0) { + throw new Error(`[sync check] ${errors.length} 个不一致`); + } + + return { ok: errors.length === 0, errors, warnings }; +} + // 判断是否作为 CLI 直接执行 (vs 作为模块被 require) if (require.main === module) { main(); @@ -268,5 +424,6 @@ if (require.main === module) { module.exports = { fixSubpackageModuleNames, + runSyncCheck, SUBPACKAGE_NAMES, }; \ No newline at end of file diff --git a/vue.config.js b/vue.config.js index 3b93783..0351531 100644 --- a/vue.config.js +++ b/vue.config.js @@ -586,6 +586,21 @@ const { fixSubpackageModuleNames } = require('./scripts/fix-subpackage-module-na } catch (e) { console.error('[vue.config] bootstrap fixSubpackageModuleNames 失败:', e.message); } + + // 第二阶段: 跑子包配置一致性检查 (build-profile.json5 / oh-package.json5 / ascf_build 目录) + // 不抛错阻塞 vue-cli-service, 但 console 输出明确提示, 让 HBuilderX 用户立刻看到漏改的项 + // 真正的硬性错误 (HAR 缺失) 由 DevEco ohpm install 在鸿蒙端抛出 + try { + // 用 require 同步调用, 检查脚本会从 stdout 输出 + // 注: 这里不 require ./scripts/check-subpackage-sync (避免循环依赖或重入), 而是在 fix-script + // 自身集成一个简化版的 sync check (复用同样的 SUBPACKAGE_NAMES + 同样 4 处检查) + const { runSyncCheck } = require('./scripts/fix-subpackage-module-names'); + if (typeof runSyncCheck === 'function') { + runSyncCheck({ silent: false, exitOnError: false }); + } + } catch (e) { + console.error('[vue.config] bootstrap sync check 失败:', e.message); + } })(); // 导出配置