ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HarmonyOS 7 DevEco CLI + Node.js:Release 构建前元数据对账、版本号漂移检测与流水线阻断【鸿蒙心迹】

HarmonyOS 7 DevEco CLI + Node.js:Release 构建前元数据对账、版本号漂移检测与流水线阻断【鸿蒙心迹】 这篇不是讲某个系统 API而是我最近给 HarmonyOS 发布流程补的一道“小闸门”。起因很普通测试包已经验收到 2.8.1准备走 release结果临打包才发现流水线环境里预期的versionCode是 20801本地AppScope/app.json5也确实是 20801但另一个分支合并进来的发布参数还停在旧版本。包能编应用也能跑问题偏偏发生在“最不该出错”的发布前。我后来做了一个Release Gate Lab。它不替代 DevEco Studio 的构建也不代替 AppGallery Connect 的上架检查只做一件事在真正执行 release 构建前把应用元数据、流水线期望值和目标 build mode 摊平对账只要有一项漂移就直接退出非 0后面的构建不再继续。一、最危险的不是配置错而是配置“各自都对”HarmonyOS 工程里应用级的bundleName、versionName、versionCode放在AppScope/app.json5。构建行为又可能因为 product、buildMode、流水线参数不同而变化。真正上线时还会有一份“这次发布应该是什么”的外部期望值。问题就出在这些信息不是同一个来源。开发者看到的可能是AppScope/app.json5 versionName 2.8.1 versionCode 20801流水线变量可能是EXPECTED_VERSION_NAME 2.8.0 EXPECTED_VERSION_CODE 20800 TARGET_BUILD_MODE release两边单看都像合理配置但放在一起就是一次版本漂移。我的目标不是“自动帮你改对”而是让这种不一致在构建前就变成一个明确失败。本次 Demo 固定数据为Build ID release_gate_20261001_08bundleName com.leoyou.releasegateversionName 2.8.1versionCode 20801product defaultbuildMode release最终Drift 0Status PASS。二、先把“期望发布什么”单独放出来我没有让脚本直接猜“当前最大的 versionCode 就是要发布的”。发布目标应该是显式输入。Demo 里增加了tools/release.expected.json5{ buildId: release_gate_20261001_08, bundleName: com.leoyou.releasegate, versionName: 2.8.1, versionCode: 20801, product: default, buildMode: release }这份文件可以来自发布分支也可以由 CI 在运行时生成。关键是它代表“本轮发布目标”而app.json5代表“工程当前实际值”。两者职责不同才有对账的意义。正式项目里我还会把 buildId 换成 CI 的流水线编号并附带 git commit。这样以后拿到一个 release 包能反查“当时按哪份发布目标验的”。Demo 为了让正文和图片一致固定使用release_gate_20261001_08。三、Node 脚本只做读取、比较、退出不做偷偷修复这段代码解决的是核心对账。app.json5不是严格 JSON所以脚本用json5解析然后把实际值和 expected 一项项比较。importfsfromnode:fsimportpathfromnode:pathimportprocessfromnode:processimportJSON5fromjson5functionreadJson5(filePath){consttextfs.readFileSync(path.resolve(filePath),utf-8)returnJSON5.parse(text)}constappreadJson5(AppScope/app.json5).appconstexpectedreadJson5(tools/release.expected.json5)constactual{bundleName:app.bundleName,versionName:app.versionName,versionCode:app.versionCode,product:process.env.TARGET_PRODUCT??default,buildMode:process.env.TARGET_BUILD_MODE??debug}我特意没有在 mismatch 时写回文件。发布脚本如果一边检查一边修改很容易把问题“修没了”却没人知道源配置原本不一致。这里更适合失败得清楚一点。接下来用一个很朴素的assertEqual累积差异consterrors[]functionassertEqual(label,actual,expected){if(actual!expected){errors.push(${label}: expected${expected}, actual${actual})}else{console.log([ReleaseGate]${label}OK -${actual})}}assertEqual(bundleName,actual.bundleName,expected.bundleName)assertEqual(versionName,actual.versionName,expected.versionName)assertEqual(versionCode,actual.versionCode,expected.versionCode)assertEqual(product,actual.product,expected.product)assertEqual(buildMode,actual.buildMode,expected.buildMode)if(errors.length0){console.error([ReleaseGate] Found drift:)errors.forEach(itemconsole.error(-${item}))process.exit(1)}console.log([ReleaseGate] drift0)console.log([ReleaseGate] RESULTPASS)process.exit(0)这里最重要的是退出码。日志写得再漂亮如果 mismatch 后还是返回 0CI 仍然会继续往下构建。脚本只有两个结果PASS 继续FAIL 阻断。四、buildMode 要作为发布条件而不是开发者默认值官方构建资料里明确区分了 debug 和 release 两类编译行为buildMode也可以用来定制差异。对我来说这意味着 release gate 不能只对版本号还必须检查“这次真的准备按 release 走吗”。开发机上最容易出现的情况是版本号已经升了开发者却仍然在 debug 条件下验证然后把“能运行”误当成“release 配置没问题”。所以我在调用 gate 时显式传入exportTARGET_PRODUCTdefaultexportTARGET_BUILD_MODEreleasenodetools/release-gate.mjs# gate 返回 0 后再进入项目现有的 hvigor release 构建步骤./hvigorw--modemodule\-pmoduleentrydefault\-pproductdefault\assembleHap--info--no-daemon这里我故意把“校验”和“构建”分成两条命令。项目到底通过 build-profile、流水线参数还是 IDE 配置选择 release可以继续沿用现有方案gate 只负责确认流水线声明的目标 buildMode 是 release并确保版本元数据没有漂移。如果你们团队还有渠道号、环境名称、后端域名、feature flag也可以放进同一套 expected/actual 对账里但不要一口气做成几十项“万能配置中心”。我更倾向于只放那些一旦错了就不应该继续发版的硬条件。五、为什么我还做了一个运行页而不是只看终端脚本本身其实几十行就够了但我还是给 Demo 做了一个Release Gate Lab页面。原因很简单发布检查经常需要跨角色确认。开发看终端没问题测试和内容负责人未必会去读 CI 日志。页面把同一组数据直接展示出来bundleName、versionName、versionCode、product、buildMode、drift、status、checkedAt。这次终端输出固定为[ReleaseGate] bundleName OK - com.leoyou.releasegate [ReleaseGate] versionName2.8.1 [ReleaseGate] versionCode20801 [ReleaseGate] buildModerelease [ReleaseGate] drift0 [ReleaseGate] RESULTPASSDevEco 图里我把versionCode那行和PASS特意标出来因为实际出问题最多的往往不是 bundleName而是版本号在分支、流水线和发布单之间没同步。六、失败场景比 PASS 更值得设计Gate 工具真正有价值的不是 PASS而是失败时能不能让人十秒内知道问题在哪。我测试过三类失败第一类是versionName已经改成 2.8.1但versionCode还停在 20800。脚本会只报 versionCode drift不把所有项都刷成失败。第二类是工程配置已经是 2.8.1 / 20801但流水线 expected 仍然是旧值。这时不是自动改工程而是让发布负责人确认“到底哪份是正确发布目标”。第三类是版本完全一致但TARGET_BUILD_MODEdebug。这种情况同样直接 FAIL因为当前流程的目标就是 release 构建。正式项目里我还会要求失败日志带上 commit SHA 和 branch避免同一个脚本在多个工作目录跑时搞不清来源。七、手机运行结果只证明“这次对账通过”不替代上架审核最终 Demo 页面的数据是Build ID release_gate_20261001_08Bundle Name com.leoyou.releasegateVersion Name 2.8.1Version Code 20801Product defaultBuild Mode releaseExpected Version 2.8.1 / 20801Drift 0Status PASSChecked At 08:05:26这里的 PASS 只表示“我们定义的发布元数据一致性规则通过”。它不代表应用一定能过市场审核也不代表签名、隐私、权限、兼容性都已经没问题。这个边界一定要讲清楚否则一个绿色 PASS 很容易被误解成“全部合规”。我的做法是把它放在正式构建之前后面继续走现有的签名、构建、安装验证、测试和上架自检。它更像一道很窄的门只拦版本元数据和构建目标明显不一致的情况。八、Gate 还要防一类很隐蔽的“环境漂移”我后来又遇到一种更隐蔽的情况仓库里的app.json5没问题release.expected.json5也没问题真正出错的是 CI 节点沿用了上一次任务的环境变量。比如上一条流水线构建测试渠道把TARGET_PRODUCT留成qa下一条 release 任务复用了同一个 shell脚本如果只看配置文件就会给出一个虚假的 PASS。所以实际接入时我不会让 gate 自己去“猜”环境。需要参与发布判断的环境变量必须显式传进来并在日志中完整打印。这样失败时能看到expecteddefault, actualqa而不是只得到一句“构建失败”。此外敏感变量不要写入日志。Gate 需要的是 product、buildMode、版本号这类发布元数据不应该顺手把签名密码、token、client secret 一起 dump。工具越接近流水线越要控制日志边界。九、我把失败输出做成机器和人都能读的两层结果终端文本适合人看但流水线后续如果还要生成报告最好同时产出一个结构化结果。Demo 的正式版会在tools/out/release-gate-result.json写入{buildId:release_gate_20261001_08,drift:0,status:PASS,checkedAt:08:05:26,checks:{bundleName:true,versionName:true,versionCode:true,product:true,buildMode:true}}失败时同样写文件只是statusFAIL并记录 mismatch 数组。这样 CI 可以在后续阶段直接上传这份报告而不是再去正则解析 console。这里还有一个工程取舍结果文件必须生成在临时产物目录不要提交回仓库。它属于一次构建的证据不是源代码配置。否则多人并行发版时仓库会不停出现无意义的时间戳和 buildId 变更。十、接入团队流程后谁来维护 expected 也要讲清楚工具做完以后我专门和测试同事讨论过“expected 到底谁改”。如果谁都能随手改那 gate 只是把口头确认换成了文件确认价值有限。我们最后更倾向于让发布单或流水线参数生成 expected开发仓库只保留读取逻辑。版本负责人确定本轮2.8.1 / 20801CI 把它注入工程当前值如果不一致开发去改工程并重新提交。这样“发布目标”和“代码实际配置”来自两个独立来源才真正形成交叉校验。对于个人项目可以简单一点expected 文件跟 release 分支一起维护就够了。但即使只有一个人我也建议保留这种双来源思路因为它能防住最常见的复制旧分支、忘记升 versionCode、debug/release 模式混淆。十一、这套 Gate 最容易被忽略的是“测试用例”脚本看起来只是几个字符串比较但我还是给它补了最基本的自动测试。原因是发布工具一旦自己出错后果比业务页面 bug 更难发现。我至少覆盖五组输入完全一致时返回 0versionCode 少 1 时返回 1versionName 漂移时只报告对应字段buildModedebug 时阻断expected 文件缺字段时直接报配置错误而不是把undefined当成一个普通值继续比较。还有一组我认为很重要versionCode在 JSON5 里必须保持 number。有人为了方便从环境变量写入最后变成字符串20801。如果比较逻辑使用宽松等于这种类型问题会被悄悄放过。所以 Demo 里使用严格比较并在读取 expected 后做 schema 校验。发布工具宁可在数据类型不明确时失败也不要“智能兼容”。实际接 CI 后我会把 gate 的单元测试放在工具自身的提交检查里。改release-gate.mjs的人必须先让测试通过真正发版时再用真实 app.json5 和 expected 跑一次集成校验。这样工具逻辑和项目配置分开验证不会等到发版窗口才发现脚本本身坏了。十二、版本回滚也不能简单把 versionCode 改回去发布事故里另一个容易踩的点是“回滚”。业务想回到 2.8.0 的代码不代表可以把versionCode也退回 20800。版本管理通常要求新提交继续使用新的版本序列因此回滚代码和回滚版本号是两件事。这也是 expected 文件比“从 git tag 猜版本”更可靠的地方本轮即使回滚到旧功能也可以明确声明versionName2.8.0-hotfix、versionCode20802。Gate 不评价版本策略本身只检查工程实际配置是不是和已经批准的发布目标一致。换句话说它不是版本号生成器而是一致性检查器。把职责收窄反而更适合长期留在流水线里。十三、最后保留下来的不是一个脚本而是一条发布习惯这个小工具做完以后我反而更在意流程上的变化。以前发版前大家会口头确认“版本号改了吧”“应该是 release 吧”“包名没动吧”这些问题每次都问却没有一个机器可验证的答案。现在我希望发布动作是确定的先生成本轮 expected跑 gatedrift 0才继续任何 mismatch 都在真正编译之前停下来。这样失败得更早也更便宜。脚本本身没有复杂算法价值在于把“记得检查”变成“没通过就不能继续”。对 HarmonyOS 项目来说随着 product、buildMode、渠道和多模块越来越多这种小型工程闸门很适合放进日常工具链里。如果后面继续扩展我会优先加两项一是检查当前 git 是否干净避免带未提交修改出包二是把校验结果生成 JSON 归档到流水线产物中。但依旧遵守同一个原则——只检查和记录不在发布时偷偷帮人改配置。
返回列表