Files
lucky_shop/stub-app-projects/mp-harmony/entry/scripts/check-har-deps.js
ZF sun 8ebd7918fb chore(mp-harmony): 修正子包 HAR name 字段 + 纳入元数据到 git
背景:
  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 等关键字段变更
2026-07-30 14:26:55 +08:00

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);
}