#!/usr/bin/env node /* eslint-disable no-console */ /** * scripts/check-subpackage-sync.js * * 检查 mp-harmony 项目的子包 HAR 配置在 4 个地方是否同步: * 1. mp-harmony/build-profile.json5 的 modules 数组 * 2. entry/oh-package.json5 的 dependencies * 3. entry/har-deps-required.json5 的 requiredHarDeps (单一事实源) * 4. ascf_build 实际目录 * * 任何一处不一致 → 立即报错并指出哪一处需要修改。 * 防止新增/删除子包时漏改某处导致 build/runtime 故障。 * * 使用: * node scripts/check-subpackage-sync.js * node scripts/check-subpackage-sync.js --strict # 不一致即 exit 1 (CI 用) * * 输出: * ✓ 5 个子包配置完全一致 * 或 * ✘ 缺失 [pages_extra] 在以下位置: * - build-profile.json5 (mp-harmony) * - entry/oh-package.json5 * 建议操作: ... */ 'use strict'; const fs = require('fs'); const path = require('path'); const JSON5 = require('json5'); function loadJson5(filePath) { const raw = fs.readFileSync(filePath, 'utf8'); return JSON5.parse(raw); } function exists(p) { try { fs.accessSync(p, fs.constants.F_OK); return true; } catch { return false; } } function listDirs(p) { try { return fs.readdirSync(p, { withFileTypes: true }) .filter(e => e.isDirectory()) .map(e => e.name) .sort(); } catch { return []; } } function diff(a, b) { const aSet = new Set(a); const bSet = new Set(b); const onlyA = [...aSet].filter(x => !bSet.has(x)); const onlyB = [...bSet].filter(x => !aSet.has(x)); return { onlyA, onlyB }; } function color(s, c) { 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[c] + s + codes.reset; } function main() { const ROOT = path.resolve(__dirname, '..'); const strict = process.argv.includes('--strict'); // === 单一事实源: har-deps-required.json5 === const configPath = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'entry', 'har-deps-required.json5'); if (!exists(configPath)) { console.error(color(`✘ 缺少白名单配置: ${configPath}`, 'red')); process.exit(strict ? 1 : 0); } const config = loadJson5(configPath); const canonical = (config.requiredHarDeps || []).slice().sort(); console.log(color('=== mp-harmony 子包配置一致性检查 ===', 'cyan')); console.log(`白名单 (har-deps-required.json5): ${canonical.join(', ')}\n`); const errors = []; const warnings = []; // === 检查 1: mp-harmony/build-profile.json5 的 modules 数组 === const buildProfilePath = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'build-profile.json5'); const buildProfileModules = []; if (!exists(buildProfilePath)) { errors.push(`build-profile.json5 不存在: ${buildProfilePath}`); } else { try { const bp = loadJson5(buildProfilePath); // modules 是顶层数组, 包含 entry + 5 个子包 // entry 也算一个 module, 但它不在我们的白名单里 const moduleNames = (bp.modules || []).map(m => m.name).filter(n => n && n !== 'entry'); buildProfileModules.push(...moduleNames); const d = diff(canonical, buildProfileModules); if (d.onlyA.length) { errors.push(`build-profile.json5 缺失子包: [${d.onlyA.join(', ')}]`); console.log(` ${color('✘', 'red')} build-profile.json5: 缺失 [${d.onlyA.join(', ')}]`); } else if (d.onlyB.length) { errors.push(`build-profile.json5 多了子包: [${d.onlyB.join(', ')}]`); console.log(` ${color('✘', 'red')} build-profile.json5: 多了 [${d.onlyB.join(', ')}]`); } else { console.log(` ${color('✔', 'green')} build-profile.json5: ${buildProfileModules.length} 个子包模块与白名单一致`); } } catch (e) { errors.push(`build-profile.json5 解析失败: ${e.message}`); } } // === 检查 2: entry/oh-package.json5 的 dependencies === const ohPkgPath = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'entry', 'oh-package.json5'); const ohPkgDeps = []; if (!exists(ohPkgPath)) { errors.push(`entry/oh-package.json5 不存在: ${ohPkgPath}`); } else { try { const op = loadJson5(ohPkgPath); const deps = op.dependencies || {}; // 提取 5 个 file: 依赖 (key 是包名) ohPkgDeps.push(...Object.keys(deps).filter(k => deps[k] && deps[k].startsWith('file:'))); const d = diff(canonical, ohPkgDeps); if (d.onlyA.length) { errors.push(`entry/oh-package.json5 缺失 file: 依赖: [${d.onlyA.join(', ')}]`); console.log(` ${color('✘', 'red')} entry/oh-package.json5: 缺失 [${d.onlyA.join(', ')}]`); } else if (d.onlyB.length) { warnings.push(`entry/oh-package.json5 多了 file: 依赖: [${d.onlyB.join(', ')}] (可能是新的子包还未更新白名单)`); console.log(` ${color('⚠', 'yellow')} entry/oh-package.json5: 多了 [${d.onlyB.join(', ')}]`); } else { console.log(` ${color('✔', 'green')} entry/oh-package.json5: ${ohPkgDeps.length} 个 file: 依赖与白名单一致`); } } catch (e) { errors.push(`entry/oh-package.json5 解析失败: ${e.message}`); } } // === 检查 3: ascf_build 实际目录 === const ascfBuildRoot = path.join(ROOT, 'stub-app-projects', 'mp-harmony', 'ascf', 'ascf_build'); const ascfBuildDirs = listDirs(ascfBuildRoot); const ascfBuildD = diff(canonical, ascfBuildDirs); if (ascfBuildD.onlyA.length) { errors.push(`ascf_build/ 目录缺失子包: [${ascfBuildD.onlyA.join(', ')}]`); console.log(` ${color('✘', 'red')} ascf_build/: 缺失 [${ascfBuildD.onlyA.join(', ')}]`); } else if (ascfBuildD.onlyB.length) { warnings.push(`ascf_build/ 多了子包: [${ascfBuildD.onlyB.join(', ')}] (可能是历史遗留)`); console.log(` ${color('⚠', 'yellow')} ascf_build/: 多了 [${ascfBuildD.onlyB.join(', ')}]`); } else { console.log(` ${color('✔', 'green')} ascf_build/: ${ascfBuildDirs.length} 个子包目录与白名单一致`); } // === 检查 4: 每个子包的关键文件是否齐全 === console.log(color('--- 子包关键文件检查 ---', 'cyan')); for (const pkg of canonical) { const pkgRoot = path.join(ascfBuildRoot, pkg); const requiredFiles = [ 'oh-package.json5', 'build-profile.json5', 'hvigorfile.ts', 'obfuscation-rules.txt', 'src/main/module.json5', ]; const missing = requiredFiles.filter(f => !exists(path.join(pkgRoot, f))); if (missing.length) { errors.push(`${pkg} 缺失文件: [${missing.join(', ')}]`); console.log(` ${color('✘', 'red')} ${pkg}: 缺失 [${missing.join(', ')}]`); } else { console.log(` ${color('✔', 'green')} ${pkg}: 关键文件齐备`); } } // === 总结 === console.log(); if (warnings.length) { console.log(color(`警告 (${warnings.length}):`, 'yellow')); for (const w of warnings) console.log(' - ' + w); } if (errors.length) { console.log(); console.log(color(`错误 (${errors.length}):`, 'red')); for (const e of errors) console.log(' - ' + e); console.log(); console.log(color('✘ 子包配置不一致, 鸿蒙构建可能失败。', 'red')); console.log(color(' 修复步骤:', 'gray')); console.log(color(' 1. 修改 entry/har-deps-required.json5 的 requiredHarDeps (单一事实源)', 'gray')); console.log(color(' 2. 在 ascf_build// 创建新子包模板 (含 oh-package.json5 / build-profile.json5 / ...) ', 'gray')); console.log(color(' 3. 在 mp-harmony/build-profile.json5 的 modules 数组加新模块', 'gray')); console.log(color(' 4. 在 entry/oh-package.json5 的 dependencies 加 "file:../ascf/ascf_build/"', 'gray')); console.log(color(' 5. 在 DevEco Sync 项目 (让 ohpm 处理 file: 依赖)', 'gray')); console.log(color(' 6. 跑 node scripts/check-subpackage-sync.js --strict 验证', 'gray')); process.exit(strict ? 1 : 0); } console.log(color(`✔ 5 个子包配置完全一致 (${canonical.length} 个包)`, 'green')); process.exit(0); } try { main(); } catch (e) { console.error(color(`脚本异常: ${e.stack || e.message}`, 'red')); process.exit(1); }