背景:
mp-harmony 项目的 5 个 ascf shared HAR 子包 (components_diy / pages_goods /
pages_order / pages_promotion / pages_tool) 在 ascf_build 目录下, 关键
元数据 (oh-package.json5 / module.json5 / build-profile.json5 /
Index.ets / hvigorfile.ts / obfuscation-rules.txt) 之前被 .gitignore
整体忽略, 从未被 git 跟踪, 导致 "module.name 被清空" 等回归无法被
git diff / blame 追踪。
改动:
1. .gitignore 改造
- 不再整体 ignore ascf_build
- 精确 ignore 已知产物路径 (rawfile/**/*.pack.js / rawfile/**/*.html
/ rawfile/**/*.json / build/ / oh_modules/ / .preview/ / .cxx/
/ .test/ / node_modules/)
- ! 取反保留 HAR 必需文件 (placeholder/index.*, rawfile/index.js,
base/element/*.json)
- 补充 entry / hvigor / 鸿蒙签名材料 / IDE 临时文件 ignore 规则
2. 首次将 5 个 HAR 包的元数据纳入 git (40 个文件)
3. 新增 scripts/fix-subpackage-module-names.js: 任何入口 require
vue.config.js 时自动修复 oh-package.json5.name + module.json5.module.name
双重覆盖, 防止 DevEco ohpm 报 "00604007 Field IS Empty Error" 或
hvigor 报 "Schema validate failed"
4. vue.config.js 顶层 IIFE 触发修复 (任何 require 立即生效, 不依赖
webpack 钩子)
5. entry/scripts/check-har-deps.js 扩展: 校验 ascf_build 子包
module.json5.module.name 字段 (兜底校验)
6. entry/har-deps-required.json5 扩展: 新增 subpackageModuleJson5
路径映射 + ohModulesRoots 校验资源齐备性
7. entry/README-HAR-DEPS.md 更新: 故障速查表新增 module.name 相关
报错条目
预期效果:
- HBuilderX 触发 vue-cli-service build → vue.config.js 自动修复子包
name → DevEco ohpm install / hvigor schema 校验通过
- check-har-deps.js 兜底, 任何回归立即被检测并报错
- 团队成员 git pull 后能立即看到 ascf_build 元数据, 可用 git diff
跟踪 module.name 等关键字段变更
234 lines
8.8 KiB
JavaScript
234 lines
8.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/* eslint-disable no-console */
|
|
/**
|
|
* 鸿蒙元服务 entry 模块依赖校验脚本
|
|
*
|
|
* 目的:
|
|
* 防止 entry 的 HAP 打包时丢失 5 个 ascf shared HAR 子包依赖,
|
|
* 否则运行时会找不到 components-diy/diy-group.pack.js 等子包 chunk,
|
|
* DIY 首页 + 商品分类 + 订单 + 营销页全部失效 (componentPlaceholder fallback 成 view)。
|
|
*
|
|
* 使用:
|
|
* node scripts/check-har-deps.js # 仅校验 entry/build/.../module.json
|
|
* node scripts/check-har-deps.js --rawfile # 同时校验 rawfile 资源是否就位
|
|
* node scripts/check-har-deps.js --strict # 任一缺失即 exit 1
|
|
*
|
|
* 默认情况:
|
|
* 缺失时仅打印警告 (CI 友好, 不阻塞开发), 加 --strict 才严格失败。
|
|
*
|
|
* 依赖:
|
|
* ./har-deps-required.json5 (本脚本的单一事实来源)
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const parseJson5 = require('./load-json5');
|
|
|
|
const ENTRY_DIR = path.resolve(__dirname, '..');
|
|
const CONFIG_PATH = path.join(ENTRY_DIR, 'har-deps-required.json5');
|
|
|
|
function loadJson5(filePath) {
|
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|
return parseJson5(raw);
|
|
}
|
|
|
|
function exists(p) {
|
|
try {
|
|
fs.accessSync(p, fs.constants.F_OK);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function listDir(p) {
|
|
try {
|
|
return fs.readdirSync(p);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function color(s, c) {
|
|
const codes = {
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
cyan: '\x1b[36m',
|
|
gray: '\x1b[90m',
|
|
reset: '\x1b[0m',
|
|
};
|
|
return process.stdout.isTTY ? codes[c] + s + codes.reset : s;
|
|
}
|
|
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
const checkRawfile = args.includes('--rawfile');
|
|
const strict = args.includes('--strict');
|
|
|
|
if (!exists(CONFIG_PATH)) {
|
|
console.error(color(`✘ 缺少白名单配置: ${CONFIG_PATH}`, 'red'));
|
|
process.exit(strict ? 1 : 0);
|
|
}
|
|
|
|
const config = loadJson5(CONFIG_PATH);
|
|
const requiredHarDeps = config.requiredHarDeps || [];
|
|
const expectedSources = config.expectedDependencySources || {};
|
|
const moduleJsonRel = (config.buildArtifacts && config.buildArtifacts.moduleJson)
|
|
|| 'build/default/intermediates/package/default/module.json';
|
|
|
|
const moduleJsonPath = path.join(ENTRY_DIR, moduleJsonRel);
|
|
|
|
console.log(color('=== HAR 子包依赖校验 ===', 'cyan'));
|
|
console.log(`配置白名单: ${requiredHarDeps.join(', ')}`);
|
|
console.log(`module.json: ${moduleJsonPath}`);
|
|
console.log();
|
|
|
|
const errors = [];
|
|
const warnings = [];
|
|
|
|
// ---- 1. 校验 module.json dependencies ----
|
|
if (!exists(moduleJsonPath)) {
|
|
warnings.push(`module.json 不存在 (${moduleJsonPath}), 跳过依赖检查。可能尚未 Build。`);
|
|
} else {
|
|
let mj;
|
|
try {
|
|
mj = JSON.parse(fs.readFileSync(moduleJsonPath, 'utf8'));
|
|
} catch (e) {
|
|
errors.push(`module.json 解析失败: ${e.message}`);
|
|
}
|
|
if (mj) {
|
|
const deps = (mj.module && mj.module.dependencies) || mj.dependencies || [];
|
|
const depNames = new Set(deps.map(d => d.moduleName));
|
|
for (const har of requiredHarDeps) {
|
|
if (depNames.has(har)) {
|
|
console.log(color(` ✔ module.json.dependencies 包含 ${har}`, 'green'));
|
|
} else {
|
|
errors.push(`module.json.dependencies 缺失 ${har} (实际: ${[...depNames].join(', ') || '空'})`);
|
|
console.log(color(` ✘ module.json.dependencies 缺失 ${har}`, 'red'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- 2. 校验 oh_modules 子包资源是否就位 (可选) ----
|
|
if (checkRawfile) {
|
|
console.log();
|
|
console.log(color('--- oh_modules 子包资源检查 ---', 'cyan'));
|
|
const ohModulesRoots = (config.ohModulesRoots) || {};
|
|
const ohModulesDir = path.join(ENTRY_DIR, 'oh_modules');
|
|
if (!exists(ohModulesDir)) {
|
|
warnings.push(`oh_modules 目录不存在 (${ohModulesDir}), 请先在 DevEco 中 Sync 项目`);
|
|
}
|
|
for (const har of requiredHarDeps) {
|
|
const cfg = ohModulesRoots[har];
|
|
if (!cfg) continue;
|
|
const subDir = path.join(ohModulesDir, cfg.pkg, 'src', 'main', 'resources', 'rawfile', cfg.root);
|
|
if (!exists(subDir)) {
|
|
errors.push(`oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 缺失 (鸿蒙运行时会找不到组件)`);
|
|
console.log(color(` ✘ oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 缺失`, 'red'));
|
|
} else {
|
|
const files = listDir(subDir);
|
|
const packFiles = files.filter(f => f.endsWith('.pack.js'));
|
|
const inner = listDir(subDir).filter(f => {
|
|
try { return fs.statSync(path.join(subDir, f)).isDirectory(); } catch { return false; }
|
|
});
|
|
const totalPack = packFiles.length + inner.reduce((acc, d) => {
|
|
try { return acc + fs.readdirSync(path.join(subDir, d)).filter(f => f.endsWith('.pack.js')).length; }
|
|
catch { return acc; }
|
|
}, 0);
|
|
if (totalPack === 0) {
|
|
warnings.push(`oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 存在但无 .pack.js`);
|
|
console.log(color(` ⚠ oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 无 .pack.js`, 'yellow'));
|
|
} else {
|
|
console.log(color(` ✔ oh_modules/${cfg.pkg}/.../rawfile/${cfg.root} 存在, ${totalPack} 个 .pack.js`, 'green'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- 3. 校验 oh-package.json5 的 dependencies 与白名单一致 ----
|
|
const ohPkgPath = path.join(ENTRY_DIR, 'oh-package.json5');
|
|
if (exists(ohPkgPath)) {
|
|
console.log();
|
|
console.log(color('--- oh-package.json5 检查 ---', 'cyan'));
|
|
const ohPkg = loadJson5(ohPkgPath);
|
|
const ohDeps = ohPkg.dependencies || {};
|
|
for (const har of requiredHarDeps) {
|
|
if (!(har in ohDeps)) {
|
|
errors.push(`oh-package.json5.dependencies 缺失 ${har}`);
|
|
console.log(color(` ✘ oh-package.json5 缺失 ${har}`, 'red'));
|
|
} else {
|
|
const expected = expectedSources[har];
|
|
const actual = ohDeps[har];
|
|
if (expected && actual !== expected) {
|
|
warnings.push(`oh-package.json5 中 ${har} 的 source 不是预期: ${actual} (预期 ${expected})`);
|
|
console.log(color(` ⚠ ${har} source 异常: ${actual}`, 'yellow'));
|
|
} else {
|
|
console.log(color(` ✔ oh-package.json5 包含 ${har} (${actual})`, 'green'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---- 4. 校验 ascf_build 子包 module.json5 的 module.name 字段 ----
|
|
// 防止 vue.config.js 重新生成时把 module.name 清空, 导致 hvigor schema 校验失败
|
|
console.log();
|
|
console.log(color('--- ascf_build 子包 module.json5 检查 ---', 'cyan'));
|
|
const mpHarmonyDir = path.resolve(ENTRY_DIR, '..');
|
|
const subpackageModuleJson5 = config.subpackageModuleJson5 || {};
|
|
for (const har of requiredHarDeps) {
|
|
const rel = subpackageModuleJson5[har];
|
|
if (!rel) continue;
|
|
const moduleJson5Path = path.resolve(mpHarmonyDir, rel);
|
|
if (!exists(moduleJson5Path)) {
|
|
warnings.push(`ascf_build/${har}/src/main/module.json5 缺失 (${moduleJson5Path})`);
|
|
console.log(color(` ⚠ ${moduleJson5Path} 不存在`, 'yellow'));
|
|
continue;
|
|
}
|
|
let mj;
|
|
try {
|
|
mj = loadJson5(moduleJson5Path);
|
|
} catch (e) {
|
|
errors.push(`${har} 的 module.json5 解析失败: ${e.message}`);
|
|
console.log(color(` ✘ ${har} module.json5 解析失败`, 'red'));
|
|
continue;
|
|
}
|
|
const moduleName = mj && mj.module && mj.module.name;
|
|
if (!moduleName || moduleName !== har) {
|
|
errors.push(`${har} 的 module.json5.module.name 异常: "${moduleName}" (应为 "${har}", 常见原因: vue.config.js 重新生成时未写入 packageName)`);
|
|
console.log(color(` ✘ ${har} module.name = "${moduleName}" (应为 "${har}")`, 'red'));
|
|
} else {
|
|
console.log(color(` ✔ ${har} module.name = "${moduleName}"`, 'green'));
|
|
}
|
|
}
|
|
|
|
// ---- 总结 ----
|
|
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('✘ 校验失败: entry 模块缺少必要的 HAR 子包依赖。', 'red'));
|
|
console.log(color(' 原因: 这会导致运行时找不到 components-diy/diy-group.pack.js 等子包 chunk,', 'gray'));
|
|
console.log(color(' DIY 首页 + 商品分类 + 订单 + 营销页全部失效, 12s splash 兜底超时。', 'gray'));
|
|
console.log(color(' 修复: 在 entry/oh-package.json5 的 dependencies 里补齐 5 个 file: 依赖,', 'gray'));
|
|
console.log(color(' 同步 ./har-deps-required.json5, 然后重新 Build。', 'gray'));
|
|
process.exit(strict ? 1 : 0);
|
|
}
|
|
console.log(color('✔ 校验通过', 'green'));
|
|
}
|
|
|
|
try {
|
|
main();
|
|
} catch (e) {
|
|
console.error(color(`脚本异常: ${e.stack || e.message}`, 'red'));
|
|
process.exit(1);
|
|
} |