一、同名文件出现在两个模块,不一定说明工程坏了

一次应用拆模块的工作很容易引出这种争论:业务组把图表抽成 HAR,主工程仍然保留一份空状态插画;仓库里同时出现 empty_state.png,有人认为会覆盖资源,有人认为根本不用管。两种判断都过于匆忙。资源文件的物理同名、ArkUI 引用时的名称解析,以及最终构建工具对资源的处理,是三个不同的问题。先把它们拆开,才能避免把静态扫描做成一个到处误报的红灯。

我给这个场景做了一份设计模型,叫 HarFence。它不读取已发布的 HAR 二进制,也不假装替代 DevEco Studio 构建;它只检查当前源码工作区中需要发布的 entry 和 chart_har 两个模块的资源目录,并把待人工判断的同名情况做成报告。任务编号固定为 HF-226:样例清单共有 12 个文件,按文件名合并后有 10 个唯一名称,其中两组在两个模块同时出现,结果标为 REVIEW,而不是 FAILED。

这份数据是文章预设的演示输入,不是来自某个真实项目的构建报告。如此处理还有一个好处:讨论重点不再是“截图里有没有两行红字”,而是能不能解释清楚告警依据、误报来源和下一步处理人。

二、先核实依赖链,再决定扫描放在哪里

截至本文核对时,华为《添加依赖项》指南明确说明:ohpm 负责安装、共享和分发代码,oh-package.json5 中有 dependencies、devDependencies 等不同依赖字段;远程三方包、本地模块以及本地 HAR/HSP 的配置方式和构建行为并不完全一致。工程级与模块级的依赖位置会影响实际编译结果,因此发布前不能只看工程根目录里有没有一个包名。

另一个细节来自《构建 HAR》和《发布共享包》。HAR 产物不只是 ArkTS 代码,还可能包含资源、配置文件和文档。发布共享包前通常要准备 README、CHANGELOG 与 LICENSE。也就是说,扫描对象应明确为源码模块的资源清单,而不是把所有 oh_modules 缓存、构建中间目录甚至 .har 压缩产物混在一个集合里。

HarFence 约定两个扫描根:entry/src/main/resources 与 chart_har/src/main/resources。脚本允许继续扩展模块列表,但不会自行猜测包依赖,也不会声称找到了依赖树内所有间接 HAR。图表库如果已经发布成二进制包,应该另外按包内容和实际构建结果复核,不能拿源码扫描报告作全量结论。

三、第一段代码:递归采集时留下模块身份

先解决“文件从哪里来”的问题。若采集阶段只记录文件名,后面就无法区分同模块内的不同目录和跨模块的重复。下面用 Node.js 自带的文件系统与路径模块递归遍历,并为每一条记录留下 module、相对路径和完整路径;这些是脚本自身的数据字段,不是 HarmonyOS SDK 接口。

// scripts/scan-resources.mjs(Node.js 18+,片段一)
import { readdir } from 'node:fs/promises';
import path from 'node:path';

async function collect(root, moduleName, dir = root) {
  const items = [];
  for (const entry of await readdir(dir, { withFileTypes: true })) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      items.push(...await collect(root, moduleName, full));
    } else if (entry.isFile() && /\.(png|jpg|json)$/i.test(entry.name)) {
      items.push({
        module: moduleName,
        path: path.relative(root, full).split(path.sep).join('/'),
        fullPath: full
      });
    }
  }
  return items;
}

这里只处理普通文件,忽略符号链接以及临时文件,目的在于让一次扫描可以复现。实际仓库可能还有媒体格式、资源限定词目录以及自定义清单,不能把这段白名单当成平台允许资源类型的完整定义。生产脚本需要把扫描根和扩展名写成可审核配置,并对目录不可读、文件在遍历中被删除的情况明确报错。

采集结束后先确认总数。本文演示样例是 12,这个数字来自固定输入清单;如果真正落地后出现 11 或 13,就应该重新生成报告,不能为了和设计图一致去改日志。采集的异步生命周期以每次命令调用为边界,脚本不会向 UI 保留文件句柄,也不需要人工安排“释放资源”步骤。

四、第二段代码:把同名警告压在合理范围

最容易写错的逻辑,是见到 color.json 就直接认定构建失败。不同模块出现同名资源文件,有时是合理组织;相同基础资源名也可能在完全不同的用途下存在。HarFence 的策略是只标记跨模块同名候选,并让审核者点击查看双方路径,不在扫描阶段自动改名、删除文件或阻止发布。

// scripts/scan-resources.mjs(片段二)
function checkCrossModuleNames(rows) {
  const grouped = new Map();
  for (const item of rows) {
    const name = path.basename(item.path);
    const records = grouped.get(name) ?? [];
    records.push(item);
    grouped.set(name, records);
  }
  const warnings = [...grouped.entries()]
    .filter(([, records]) =>
      new Set(records.map((item) => item.module)).size > 1)
    .map(([name, records]) => ({
      name,
      locations: records.map((item) => `${item.module}/${item.path}`)
    }));
  return { scanned: rows.length, unique: grouped.size, warnings };
}

对固定清单来说,empty_state.png 和 color.json 恰好构成两条告警,扫描 12 项、唯一名称 10 项、跨模块同名候选 2 项。注意 unique 只是按照 basename 去重后的统计量,不是“真正不会冲突的资源数量”。更不能据此断言运行时资源覆盖顺序。是否需要消除同名,应结合 HAR 使用者的引用路径、资源 ID、实际构建和设备侧显示来判断。

这里还保留了一个可解释的误报处理原则:即使确定某个同名资源不会引起问题,也不要在脚本中硬编码“所有 color.json 永远忽略”。更稳妥的做法,是在经过代码审查的规则文件里写清允许的模块对、资源名和原因,并在模块发生变化时重新确认。误报收敛依赖可追溯的例外,而不是无限扩大忽略列表。

五、第三段代码:状态只能写 REVIEW

为了让扫描结论能被其他人重复消费,需要一份结构化报告。下面的输出函数只写文件并打印固定字段,不依赖 DevEco 专用日志 API。对 CI 来说,可以选择在文件采集错误时返回失败退出码,但同名候选本身保持 REVIEW,留给真正的构建流程判定。

// scripts/scan-resources.mjs(片段三)
import { mkdir, writeFile } from 'node:fs/promises';

async function saveAudit(result) {
  const gate = result.warnings.length ? 'REVIEW' : 'PASS';
  const report = {
    taskId: 'HF-226',
    scanned: result.scanned,
    unique: result.unique,
    duplicateNames: result.warnings.length,
    gate,
    warnings: result.warnings
  };
  await mkdir('reports', { recursive: true });
  await writeFile('reports/resource-audit.json',
    JSON.stringify(report, null, 2), 'utf8');
  console.info(`[HarFence] HF-226 scanned=${report.scanned} unique=${report.unique}`);
  console.info(`[HarFence] HF-226 duplicateNames=${report.duplicateNames}`);
  console.info(`[HarFence] HF-226 gate=${report.gate}`);
}

报告输出位置属于脚本执行目录,CI 使用时应固定工作目录或改为绝对路径。writeFile 是一次异步写入;如果磁盘空间不足或没有写权限,不应输出 PASS,也不应保留上一轮旧 JSON 冒充本轮结果。正式工程可采用临时文件写入、校验成功再重命名的方式,避免中断时留下半份报告。若 UI 读取报告,还要区分“报告尚未生成”和“检查通过”,二者不是同一种状态。

上图是按 HarFence / AuditPage.ets / scan-resources.mjs 绘制的开发界面示意。左侧为模块结构,中间为 Node 脚本逻辑,右侧为静态结果预览,底部 HiLog 展示预设日志文本。它不代表真的在 DevEco 中运行 Node 扫描,也不证明 HAR 已构建成功。 UI 面板展示的数据契约只有 HF-226、12、10、2、REVIEW,避免把“展示端快照”当成构建产物。

六、真正值得验收的是边界样例

完整检查不应停在这两个文件。比如同一模块里两个目录各有一个 icon.png,是否告警要看工具定位;不同模块的同名资源如果使用不同的访问方式,也不一定导致运行问题。再把输入样例中的 empty_state.png 一方改名,候选数理论上就从 2 降到 1;但这只是规则逻辑的预期,不能包装成已经在本地完成的自动化测试成绩。

我会把验收拆成三个阶段:先校验扫描规则面对空目录、路径不存在、权限不足、大小写差异时的行为;再通过 ohpm 的实际依赖锁定和 HAR 构建检查真实工程;最后在引用资源的页面确认 UI 显示。前一阶段发现的是“线索”,后二阶段才有机会建立关于构建和产品行为的证据。如此做能减少误判,也方便说明告警到底由谁处理。

手机画面是 HF-226 的结果页演示:两个同名条目待人工复核,界面明确提示“同名只是静态线索,不等于构建失败”。它不是来自实机,更不是应用已经通过上架审核的证明。扫描器的业务价值在于把可能被忽视的地方整理出来,而不是用一个脚本代替平台构建、资源解析和人工审查。

七、工程收尾:把脚本当检查清单,不当裁判

这次选题的结论比较克制:ohpm 管依赖,DevEco/Hvigor 管构建,HarFence 只负责在提交前形成可解释的资源同名候选名单。静态脚本本身没有页面生命周期;若做成持续监听的桌面服务,还应补充退出时关闭 watcher、清理定时器和去除重复事件。文章中的页面是读取示例结果的单独展示层,不涉及系统资源管理权限。

可以先把它接到代码审查说明里,但发布门禁不能只依赖 REVIEW 数量。只要把“扫描到了什么”和“真的出错了什么”分开,很多跨模块讨论就不必消耗在误报上。后续需要验证的仍是完整 ohpm 安装、HAR 打包和实际资源显示;未执行的环节应始终标为待验证。

官方资料(已核对页面):

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐