背景:
mp-harmony 项目的子包 HAR 配置分散在 4 个地方:
1. entry/har-deps-required.json5 的 requiredHarDeps (单一事实源)
2. mp-harmony/build-profile.json5 的 modules 数组
3. entry/oh-package.json5 的 dependencies
4. ascf_build 实际目录
新增/删除子包时, 任何一处忘改都会导致鸿蒙构建失败:
- build-profile.json5 没加 → hvigor 不构建该模块
- entry/oh-package.json5 没加 file: → ohpm 不 link 该 HAR
- ascf_build/ 目录不存在 → ohpm "Field IS Empty Error"
- har-deps-required.json5 没加 → check-har-deps 不会检查
改动:
新增 scripts/check-subpackage-sync.js:
- 读取 entry/har-deps-required.json5 作为单一事实源
- 扫描 build-profile.json5 的 modules / entry/oh-package.json5
的 file: 依赖 / ascf_build 实际目录 / 每个子包的关键文件齐备性
- 任何一处不一致立即报错, 指出哪一处需要修改
- 支持 --strict 模式 (CI 用, exit 1 阻断 build)
- 已验证反向场景: 故意添加 pages_extra 到 build-profile.json5
后跑 --strict, 立即报告 "build-profile.json5 多了 [pages_extra]"
使用方式:
本地日常: node scripts/check-subpackage-sync.js
CI 严格: node scripts/check-subpackage-sync.js --strict
新增子包 SOP: 跑一次确认没漏, 不一致会列出缺失位置
依赖: 项目已有 json5 包 (devDependencies transitive), 无需新装
209 lines
8.2 KiB
JavaScript
209 lines
8.2 KiB
JavaScript
#!/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/<pkg>/ 创建新子包模板 (含 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/<pkg>"', '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);
|
|
} |