为什么已经有编译器,还需要自检脚本
ArkTS 编译器会检查语法和类型,却不会知道你的产品约束:
- Bundle ID 必须等于备案和市场后台中的值;
- 只允许一个设备认证权限;
- 中英日资源 Key 必须完全一致;
- 应用图标不能被小尺寸占位图替换;
- 本地优先 App 不应该突然加入网络权限;
- 统计页必须保留审核回归功能。
这些规则如果只写在 README 中,很容易在修改时被忽略。
更可靠的方式是:
文档说明为什么 脚本检查有没有违反一、脚本不需要一开始就做成复杂工具
项目使用普通 Node ESM:
importfsfrom'node:fs';importpathfrom'node:path';constroot=path.resolve(newURL('..',import.meta.url).pathname);constreadJson=(relative)=>JSON.parse(fs.readFileSync(path.join(root,relative),'utf8'));失败函数统一设置退出码:
constfail=(message)=>{console.error(`FAIL:${message}`);process.exitCode=1;};这样可以一次报告多个问题,而不是遇到第一项就退出。
二、检查 Bundle ID 和版本格式
constapp=readJson('AppScope/app.json5').app;constexpectedBundle='你的稳定 Bundle ID';if(app.bundleName!==expectedBundle){fail(`bundleName must be${expectedBundle}`);}if(!Number.isInteger(app.versionCode)||app.versionCode<=0){fail('versionCode must be a positive integer');}if(!/^\d+\.\d+\.\d+$/.test(app.versionName)){fail('versionName must use x.y.z format');}编译器允许很多合法字符串,但团队可以定义更窄的版本规则。
这里检查的是格式和固定身份。是否比上一个发布版本递增,还需要读取发布记录、Git Tag 或 CI 参数。
三、把允许权限做成白名单
从模块配置读取:
constmoduleProfile=readJson('entry/src/main/module.json5').module;constpermissions=moduleProfile.requestPermissions.map((item)=>item.name).sort();项目只允许设备认证:
constexpectedPermissions=['ohos.permission.ACCESS_BIOMETRIC'].sort();if(JSON.stringify(permissions)!==JSON.stringify(expectedPermissions)){fail(`unexpected permission set:${permissions.join(', ')}`);}白名单比“禁止几个高风险权限”更适合小型本地应用:任何新权限都会让检查失败,开发者必须同步更新代码、用途文案、隐私政策和测试清单。
四、比较三种语言的资源 Key
letcanonicalEntryKeys=[];for(constlocaleof['base','zh_CN','ja_JP']){conststrings=readJson(`entry/src/main/resources/${locale}`+`/element/string.json`).string;constnames=strings.map((item)=>item.name);if(newSet(names).size!==names.length){fail(`${locale}has duplicate string keys`);}constsortedNames=names.slice().sort();if(locale==='base'){canonicalEntryKeys=sortedNames;}elseif(JSON.stringify(sortedNames)!==JSON.stringify(canonicalEntryKeys)){// 输出 missing 和 extra}}这个检查能捕获:
- 新增中文文案但漏英文/日文;
- 拼写错误导致某语言多出 Key;
- 同一个文件内重复定义;
- 删除功能时只删了一种语言。
它不能检查翻译质量,但至少保证资源结构完整。
五、应用名称资源也要单独检查
桌面应用名称位于 AppScope,不是页面模块资源。
for(constlocaleof['base','zh_CN','ja_JP']){constappStrings=readJson(`AppScope/resources/${locale}`+`/element/string.json`).string;constnames=appStrings.map((item)=>item.name);if(!names.includes('app_name')){fail(`${locale}AppScope is missing app_name`);}}否则应用内已经是日文,桌面名称却可能回退基础语言。
六、用文件特征防止资源被占位符替换
项目检查图标文件大小:
for(consticonof['AppScope/resources/base/media/app_icon.png','entry/src/main/resources/base/media/icon.png']){conststat=fs.statSync(path.join(root,icon));if(stat.size<10000){fail(`${icon}looks like a placeholder`);}}文件大小不是严格的视觉验证,但可以捕获常见事故:真实图标被一个很小的临时 PNG 覆盖。
可以继续加强:
- 读取图片宽高;
- 检查必须为 1024×1024;
- 检查颜色模式;
- 检查不含透明通道;
- 生成发布素材总览供人工确认。
七、扫描本地优先架构的明显回退
项目不使用网络,因此验证脚本检查:
for(constforbiddenof['ohos.permission.INTERNET','http.request(','@ohos/axios']){if(indexSource.includes(forbidden)){fail(`unexpected network capability:${forbidden}`);}}这是低成本保护,但必须认识局限:
- 只能扫描列出的文件;
- 字符串可能出现误报;
- 无法识别所有间接依赖;
- 第三方包内部能力不一定出现在页面源码;
- 不是安全审计或依赖分析的替代品。
随着项目变大,可以改成遍历所有.ets/.ts/.json5,再结合依赖清单和权限配置检查。
八、为审核回归功能保留“哨兵”
某些功能曾因重构丢失,可以用简单字符串做哨兵:
for(constrequiredof['uiRevision','refreshUi()',"this.named('completed_checkins')","this.named('reflection_cards')","this.named('summary')",'eligibleDayIds(habit: Habit)']){if(!indexSource.includes(required)){fail(`review regression coverage is missing:${required}`);}}这种检查比真正的 UI 自动化测试弱,但能快速发现大段功能被误删。
命名或架构重构时,哨兵也需要更新,所以它更适合作为短期回归护栏,而不是永久规范。
九、不要让静态脚本读取或打印真实签名秘密
发布检查经常需要确认“签名已配置”,但脚本不应该把密码、密钥路径或证书内容输出到日志。
正确检查目标可以是:
- 公共仓库不包含已知敏感字段;
- 发布环境中必要文件存在;
- 构建产物已经签名;
- CI Secret 名称存在但不打印值;
- 日志中对路径和凭据做脱敏。
可以增加敏感模式扫描,但只输出文件和字段名称,不输出匹配到的秘密正文。
一旦秘密进入 Git 历史,仅从当前文件删除还不够,通常还需要轮换。
十、JSON5 解析是一个隐藏细节
示例使用JSON.parse()读取.json5,前提是当前文件内容实际上符合严格 JSON:没有注释、尾随逗号或未加引号的 Key。
如果以后开始使用完整 JSON5 语法,脚本会解析失败。可选方案:
- 团队约定这些配置保持严格 JSON 子集;
- 引入 JSON5 解析库;
- 复用构建工具提供的配置解析能力。
文件扩展名不等于实际解析器能力,这一点应该在脚本说明中写清楚。
十一、如何接入日常流程
本地执行:
nodescripts/validate_project.mjs建议放在:
修改资源后 → 静态自检 → ArkTS 编译 → HAP/APP 构建 → 真机/模拟器回归如果使用 CI,可以把脚本作为构建前置步骤。退出码非 0 时停止后续发布。
但不要因为脚本通过就跳过人工审核。它适合检查确定性约束,不适合判断截图是否好看、翻译是否自然、隐私文案是否准确。
十二、适合继续增加的检查项
- 版本号与上次发布记录比较;
- 三语言格式化占位符一致;
- 隐私政策 URL 使用 HTTPS 且可访问;
- 所有公开 URL 不指向测试域名;
- 设备类型与 QA 覆盖矩阵匹配;
- HAP/APP 产物存在且时间为本次构建;
- 图标尺寸和 Alpha 通道;
- Markdown 发布文档不含敏感信息;
- 签名配置未进入公共变更集;
- 导出结构版本与代码常量一致。
总结
一个几十行的自检脚本可以承担四类职责:
- 应用身份:Bundle ID 和版本;
- 合规边界:权限与网络能力;
- 资源完整性:多语言和图标;
- 回归哨兵:关键功能没有被误删。
它的价值不在于替代编译器和测试,而是把项目独有的约束变成每次都能重复执行的检查。
本文案例来自“心晴手记(MoodMemoir)”HarmonyOS 项目的
validate_project.mjs。
参考资料
- HarmonyOS 应用开发知识地图